blob: 79a39ef7324962336ef0c5c29fb3697c7d4a5855 [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07002 * Copyright 2014 Open Networking Laboratory
alshabibc4901cd2014-09-05 16:50:40 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
alshabibc4901cd2014-09-05 16:50:40 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska24c849c2014-10-27 09:53:05 -070015 */
16
17
alshabibc4901cd2014-09-05 16:50:40 -070018
19package org.onlab.packet;
20
21import java.io.UnsupportedEncodingException;
22import java.nio.ByteBuffer;
23import java.util.ArrayList;
24import java.util.List;
25import java.util.ListIterator;
26
Jonathan Hart2a655752015-04-07 16:46:33 -070027import static org.onlab.packet.PacketUtils.*;
28
alshabibc4901cd2014-09-05 16:50:40 -070029/**
30 *
alshabibc4901cd2014-09-05 16:50:40 -070031 */
32public class DHCP extends BasePacket {
33 /**
34 * Dynamic Host Configuration Protocol packet.
35 * ------------------------------------------ |op (1) | htype(1) | hlen(1) |
36 * hops(1) | ------------------------------------------ | xid (4) |
37 * ------------------------------------------ | secs (2) | flags (2) |
38 * ------------------------------------------ | ciaddr (4) |
39 * ------------------------------------------ | yiaddr (4) |
40 * ------------------------------------------ | siaddr (4) |
41 * ------------------------------------------ | giaddr (4) |
42 * ------------------------------------------ | chaddr (16) |
43 * ------------------------------------------ | sname (64) |
44 * ------------------------------------------ | file (128) |
45 * ------------------------------------------ | options (312) |
46 * ------------------------------------------
47 *
48 */
49 // Header + magic without options
50 public static final int MIN_HEADER_LENGTH = 240;
51 public static final byte OPCODE_REQUEST = 0x1;
52 public static final byte OPCODE_REPLY = 0x2;
53
54 public static final byte HWTYPE_ETHERNET = 0x1;
55
56 public enum DHCPOptionCode {
samanwita palb300ca82015-07-16 14:36:05 -070057 OptionCode_SubnetMask((byte) 1), OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6),
58 OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15), OptionCode_BroadcastAddress((byte) 28),
59 OptionCode_RequestedIP((byte) 50), OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53),
60 OptionCode_DHCPServerIp((byte) 54), OptionCode_RequestedParameters((byte) 55),
61 OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59), OptionCode_ClientID((byte) 61),
62 OptionCode_END((byte) 255);
alshabibc4901cd2014-09-05 16:50:40 -070063
64 protected byte value;
65
66 private DHCPOptionCode(final byte value) {
67 this.value = value;
68 }
69
70 public byte getValue() {
71 return this.value;
72 }
73 }
74
samanwita palb300ca82015-07-16 14:36:05 -070075 public enum DHCPMessageType {
76 MessageType_Discover((byte) 1), MessageType_Offer((byte) 2), MessageType_Request((byte) 3),
77 MessageType_Decline((byte) 4), MessageType_ACK((byte) 5), MessageType_Nak((byte) 6),
78 MessageType_Release((byte) 7), MessageType_Inform((byte) 8);
79
80 protected byte value;
81
82 private DHCPMessageType(final byte value) {
83 this.value = value;
84 }
85
86 public byte getValue() {
87 return this.value;
88 }
89 }
90
alshabibc4901cd2014-09-05 16:50:40 -070091 protected byte opCode;
92 protected byte hardwareType;
93 protected byte hardwareAddressLength;
94 protected byte hops;
95 protected int transactionId;
96 protected short seconds;
97 protected short flags;
98 protected int clientIPAddress;
99 protected int yourIPAddress;
100 protected int serverIPAddress;
101 protected int gatewayIPAddress;
102 protected byte[] clientHardwareAddress;
103 protected String serverName;
104 protected String bootFileName;
105 protected List<DHCPOption> options = new ArrayList<DHCPOption>();
106
107 /**
108 * @return the opCode
109 */
110 public byte getOpCode() {
111 return this.opCode;
112 }
113
114 /**
115 * @param opCode
116 * the opCode to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800117 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700118 */
119 public DHCP setOpCode(final byte opCode) {
120 this.opCode = opCode;
121 return this;
122 }
123
124 /**
125 * @return the hardwareType
126 */
127 public byte getHardwareType() {
128 return this.hardwareType;
129 }
130
131 /**
132 * @param hardwareType
133 * the hardwareType to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800134 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700135 */
136 public DHCP setHardwareType(final byte hardwareType) {
137 this.hardwareType = hardwareType;
138 return this;
139 }
140
141 /**
142 * @return the hardwareAddressLength
143 */
144 public byte getHardwareAddressLength() {
145 return this.hardwareAddressLength;
146 }
147
148 /**
149 * @param hardwareAddressLength
150 * the hardwareAddressLength to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800151 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700152 */
153 public DHCP setHardwareAddressLength(final byte hardwareAddressLength) {
154 this.hardwareAddressLength = hardwareAddressLength;
155 return this;
156 }
157
158 /**
159 * @return the hops
160 */
161 public byte getHops() {
162 return this.hops;
163 }
164
165 /**
166 * @param hops
167 * the hops to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800168 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700169 */
170 public DHCP setHops(final byte hops) {
171 this.hops = hops;
172 return this;
173 }
174
175 /**
176 * @return the transactionId
177 */
178 public int getTransactionId() {
179 return this.transactionId;
180 }
181
182 /**
183 * @param transactionId
184 * the transactionId to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800185 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700186 */
187 public DHCP setTransactionId(final int transactionId) {
188 this.transactionId = transactionId;
189 return this;
190 }
191
192 /**
193 * @return the seconds
194 */
195 public short getSeconds() {
196 return this.seconds;
197 }
198
199 /**
200 * @param seconds
201 * the seconds to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800202 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700203 */
204 public DHCP setSeconds(final short seconds) {
205 this.seconds = seconds;
206 return this;
207 }
208
209 /**
210 * @return the flags
211 */
212 public short getFlags() {
213 return this.flags;
214 }
215
216 /**
217 * @param flags
218 * the flags to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800219 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700220 */
221 public DHCP setFlags(final short flags) {
222 this.flags = flags;
223 return this;
224 }
225
226 /**
227 * @return the clientIPAddress
228 */
229 public int getClientIPAddress() {
230 return this.clientIPAddress;
231 }
232
233 /**
234 * @param clientIPAddress
235 * the clientIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800236 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700237 */
238 public DHCP setClientIPAddress(final int clientIPAddress) {
239 this.clientIPAddress = clientIPAddress;
240 return this;
241 }
242
243 /**
244 * @return the yourIPAddress
245 */
246 public int getYourIPAddress() {
247 return this.yourIPAddress;
248 }
249
250 /**
251 * @param yourIPAddress
252 * the yourIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800253 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700254 */
255 public DHCP setYourIPAddress(final int yourIPAddress) {
256 this.yourIPAddress = yourIPAddress;
257 return this;
258 }
259
260 /**
261 * @return the serverIPAddress
262 */
263 public int getServerIPAddress() {
264 return this.serverIPAddress;
265 }
266
267 /**
268 * @param serverIPAddress
269 * the serverIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800270 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700271 */
272 public DHCP setServerIPAddress(final int serverIPAddress) {
273 this.serverIPAddress = serverIPAddress;
274 return this;
275 }
276
277 /**
278 * @return the gatewayIPAddress
279 */
280 public int getGatewayIPAddress() {
281 return this.gatewayIPAddress;
282 }
283
284 /**
285 * @param gatewayIPAddress
286 * the gatewayIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800287 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700288 */
289 public DHCP setGatewayIPAddress(final int gatewayIPAddress) {
290 this.gatewayIPAddress = gatewayIPAddress;
291 return this;
292 }
293
294 /**
295 * @return the clientHardwareAddress
296 */
297 public byte[] getClientHardwareAddress() {
298 return this.clientHardwareAddress;
299 }
300
301 /**
302 * @param clientHardwareAddress
303 * the clientHardwareAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800304 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700305 */
306 public DHCP setClientHardwareAddress(final byte[] clientHardwareAddress) {
307 this.clientHardwareAddress = clientHardwareAddress;
308 return this;
309 }
310
311 /**
312 * Gets a specific DHCP option parameter.
313 *
tom5f18cf32014-09-13 14:10:57 -0700314 * @param optionCode
alshabibc4901cd2014-09-05 16:50:40 -0700315 * The option code to get
316 * @return The value of the option if it exists, null otherwise
317 */
318 public DHCPOption getOption(final DHCPOptionCode optionCode) {
319 for (final DHCPOption opt : this.options) {
320 if (opt.code == optionCode.value) {
321 return opt;
322 }
323 }
324 return null;
325 }
326
327 /**
328 * @return the options
329 */
330 public List<DHCPOption> getOptions() {
331 return this.options;
332 }
333
334 /**
335 * @param options
336 * the options to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800337 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700338 */
339 public DHCP setOptions(final List<DHCPOption> options) {
340 this.options = options;
341 return this;
342 }
343
344 /**
345 * @return the packetType base on option 53
346 */
347 public DHCPPacketType getPacketType() {
348 final ListIterator<DHCPOption> lit = this.options.listIterator();
349 while (lit.hasNext()) {
350 final DHCPOption option = lit.next();
351 // only care option 53
352 if (option.getCode() == 53) {
353 return DHCPPacketType.getType(option.getData()[0]);
354 }
355 }
356 return null;
357 }
358
359 /**
360 * @return the serverName
361 */
362 public String getServerName() {
363 return this.serverName;
364 }
365
366 /**
367 * @param server
368 * the serverName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800369 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700370 */
371 public DHCP setServerName(final String server) {
372 this.serverName = server;
373 return this;
374 }
375
376 /**
377 * @return the bootFileName
378 */
379 public String getBootFileName() {
380 return this.bootFileName;
381 }
382
383 /**
384 * @param bootFile
385 * the bootFileName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800386 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700387 */
388 public DHCP setBootFileName(final String bootFile) {
389 this.bootFileName = bootFile;
390 return this;
391 }
392
393 @Override
394 public byte[] serialize() {
395 // not guaranteed to retain length/exact format
396 this.resetChecksum();
397
398 // minimum size 240 including magic cookie, options generally padded to
399 // 300
400 int optionsLength = 0;
401 for (final DHCPOption option : this.options) {
Yuta HIGUCHIe5ca93b2014-10-23 09:49:00 -0700402 if (option.getCode() == 0 || option.getCode() == ((byte) 255)) {
alshabibc4901cd2014-09-05 16:50:40 -0700403 optionsLength += 1;
404 } else {
405 optionsLength += 2 + (0xff & option.getLength());
406 }
407 }
408 int optionsPadLength = 0;
409 if (optionsLength < 60) {
410 optionsPadLength = 60 - optionsLength;
411 }
412
413 final byte[] data = new byte[240 + optionsLength + optionsPadLength];
414 final ByteBuffer bb = ByteBuffer.wrap(data);
415 bb.put(this.opCode);
416 bb.put(this.hardwareType);
417 bb.put(this.hardwareAddressLength);
418 bb.put(this.hops);
419 bb.putInt(this.transactionId);
420 bb.putShort(this.seconds);
421 bb.putShort(this.flags);
422 bb.putInt(this.clientIPAddress);
423 bb.putInt(this.yourIPAddress);
424 bb.putInt(this.serverIPAddress);
425 bb.putInt(this.gatewayIPAddress);
426 bb.put(this.clientHardwareAddress);
427 if (this.clientHardwareAddress.length < 16) {
428 for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) {
429 bb.put((byte) 0x0);
430 }
431 }
432 this.writeString(this.serverName, bb, 64);
433 this.writeString(this.bootFileName, bb, 128);
434 // magic cookie
435 bb.put((byte) 0x63);
436 bb.put((byte) 0x82);
437 bb.put((byte) 0x53);
438 bb.put((byte) 0x63);
439 for (final DHCPOption option : this.options) {
440 final int code = option.getCode() & 0xff;
441 bb.put((byte) code);
442 if (code != 0 && code != 255) {
443 bb.put(option.getLength());
444 bb.put(option.getData());
445 }
446 }
447 // assume the rest is padded out with zeroes
448 return data;
449 }
450
alshabibc4901cd2014-09-05 16:50:40 -0700451 @Override
452 public IPacket deserialize(final byte[] data, final int offset,
Jonathan Hart2a655752015-04-07 16:46:33 -0700453 final int length) {
alshabibc4901cd2014-09-05 16:50:40 -0700454 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
455 if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
456 return this;
457 }
458
459 this.opCode = bb.get();
460 this.hardwareType = bb.get();
461 this.hardwareAddressLength = bb.get();
462 this.hops = bb.get();
463 this.transactionId = bb.getInt();
464 this.seconds = bb.getShort();
465 this.flags = bb.getShort();
466 this.clientIPAddress = bb.getInt();
467 this.yourIPAddress = bb.getInt();
468 this.serverIPAddress = bb.getInt();
469 this.gatewayIPAddress = bb.getInt();
470 final int hardwareAddressLength = 0xff & this.hardwareAddressLength;
471 this.clientHardwareAddress = new byte[hardwareAddressLength];
472
473 bb.get(this.clientHardwareAddress);
474 for (int i = hardwareAddressLength; i < 16; ++i) {
475 bb.get();
476 }
477 this.serverName = this.readString(bb, 64);
478 this.bootFileName = this.readString(bb, 128);
479 // read the magic cookie
480 // magic cookie
481 bb.get();
482 bb.get();
483 bb.get();
484 bb.get();
485 // read options
486 while (bb.hasRemaining()) {
487 final DHCPOption option = new DHCPOption();
488 int code = 0xff & bb.get(); // convert signed byte to int in range
489 // [0,255]
490 option.setCode((byte) code);
491 if (code == 0) {
492 // skip these
493 continue;
494 } else if (code != 255) {
495 if (bb.hasRemaining()) {
496 final int l = 0xff & bb.get(); // convert signed byte to
497 // int in range [0,255]
498 option.setLength((byte) l);
499 if (bb.remaining() >= l) {
500 final byte[] optionData = new byte[l];
501 bb.get(optionData);
502 option.setData(optionData);
503 } else {
504 // Skip the invalid option and set the END option
505 code = 0xff;
506 option.setCode((byte) code);
507 option.setLength((byte) 0);
508 }
509 } else {
510 // Skip the invalid option and set the END option
511 code = 0xff;
512 option.setCode((byte) code);
513 option.setLength((byte) 0);
514 }
515 }
516 this.options.add(option);
517 if (code == 255) {
518 // remaining bytes are supposed to be 0, but ignore them just in
519 // case
520 break;
521 }
522 }
523
524 return this;
525 }
526
Jonathan Hart2a655752015-04-07 16:46:33 -0700527 protected void writeString(final String string, final ByteBuffer bb,
528 final int maxLength) {
529 if (string == null) {
530 for (int i = 0; i < maxLength; ++i) {
531 bb.put((byte) 0x0);
532 }
533 } else {
534 byte[] bytes = null;
535 try {
536 bytes = string.getBytes("ascii");
537 } catch (final UnsupportedEncodingException e) {
538 throw new RuntimeException("Failure encoding server name", e);
539 }
540 int writeLength = bytes.length;
541 if (writeLength > maxLength) {
542 writeLength = maxLength;
543 }
544 bb.put(bytes, 0, writeLength);
545 for (int i = writeLength; i < maxLength; ++i) {
546 bb.put((byte) 0x0);
547 }
548 }
549 }
550
551 private static String readString(final ByteBuffer bb, final int maxLength) {
alshabibc4901cd2014-09-05 16:50:40 -0700552 final byte[] bytes = new byte[maxLength];
553 bb.get(bytes);
554 String result = null;
555 try {
556 result = new String(bytes, "ascii").trim();
557 } catch (final UnsupportedEncodingException e) {
558 throw new RuntimeException("Failure decoding string", e);
559 }
560 return result;
561 }
Jonathan Hart2a655752015-04-07 16:46:33 -0700562
563 /**
564 * Deserializer function for DHCP packets.
565 *
566 * @return deserializer function
567 */
568 public static Deserializer<DHCP> deserializer() {
569 return (data, offset, length) -> {
570 checkInput(data, offset, length, MIN_HEADER_LENGTH);
571
572 ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
573 DHCP dhcp = new DHCP();
574
575 dhcp.opCode = bb.get();
576 dhcp.hardwareType = bb.get();
577 dhcp.hardwareAddressLength = bb.get();
578 dhcp.hops = bb.get();
579 dhcp.transactionId = bb.getInt();
580 dhcp.seconds = bb.getShort();
581 dhcp.flags = bb.getShort();
582 dhcp.clientIPAddress = bb.getInt();
583 dhcp.yourIPAddress = bb.getInt();
584 dhcp.serverIPAddress = bb.getInt();
585 dhcp.gatewayIPAddress = bb.getInt();
586 final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength;
587 dhcp.clientHardwareAddress = new byte[hardwareAddressLength];
588
589 bb.get(dhcp.clientHardwareAddress);
590 for (int i = hardwareAddressLength; i < 16; ++i) {
591 bb.get();
592 }
593 dhcp.serverName = readString(bb, 64);
594 dhcp.bootFileName = readString(bb, 128);
595 // read the magic cookie
596 // magic cookie
597 bb.get();
598 bb.get();
599 bb.get();
600 bb.get();
601
602 // read options
603 boolean foundEndOptionsMarker = false;
604 while (bb.hasRemaining()) {
605 final DHCPOption option = new DHCPOption();
606 int code = 0xff & bb.get(); // convert signed byte to int in range
607 // [0,255]
608 option.setCode((byte) code);
609 if (code == 0) {
610 // skip these
611 continue;
612 } else if (code != 255) {
613 if (bb.hasRemaining()) {
614 final int l = 0xff & bb.get(); // convert signed byte to
615 // int in range [0,255]
616 option.setLength((byte) l);
617 if (bb.remaining() >= l) {
618 final byte[] optionData = new byte[l];
619 bb.get(optionData);
620 option.setData(optionData);
621 dhcp.options.add(option);
622 } else {
623 throw new DeserializationException(
624 "Buffer underflow while reading DHCP option");
625 }
626 }
627 } else if (code == 255) {
628 // remaining bytes are supposed to be 0, but ignore them just in
629 // case
630 foundEndOptionsMarker = true;
631 break;
632 }
633 }
634
635 if (!foundEndOptionsMarker) {
636 throw new DeserializationException("DHCP End options marker was missing");
637 }
638
639 return dhcp;
640 };
641 }
alshabibc4901cd2014-09-05 16:50:40 -0700642}