blob: a22f4d2d0c37200a1ef9a09c629ccb146042ee66 [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
samanwita pal3ec8aff2015-08-03 18:04:27 -070027import static com.google.common.base.Preconditions.checkArgument;
28import static org.onlab.packet.PacketUtils.checkInput;
Jonathan Hart2a655752015-04-07 16:46:33 -070029
alshabibc4901cd2014-09-05 16:50:40 -070030/**
31 *
alshabibc4901cd2014-09-05 16:50:40 -070032 */
33public class DHCP extends BasePacket {
34 /**
35 * Dynamic Host Configuration Protocol packet.
36 * ------------------------------------------ |op (1) | htype(1) | hlen(1) |
37 * hops(1) | ------------------------------------------ | xid (4) |
38 * ------------------------------------------ | secs (2) | flags (2) |
39 * ------------------------------------------ | ciaddr (4) |
40 * ------------------------------------------ | yiaddr (4) |
41 * ------------------------------------------ | siaddr (4) |
42 * ------------------------------------------ | giaddr (4) |
43 * ------------------------------------------ | chaddr (16) |
44 * ------------------------------------------ | sname (64) |
45 * ------------------------------------------ | file (128) |
46 * ------------------------------------------ | options (312) |
47 * ------------------------------------------
48 *
49 */
50 // Header + magic without options
51 public static final int MIN_HEADER_LENGTH = 240;
52 public static final byte OPCODE_REQUEST = 0x1;
53 public static final byte OPCODE_REPLY = 0x2;
54
55 public static final byte HWTYPE_ETHERNET = 0x1;
56
57 public enum DHCPOptionCode {
samanwita palb300ca82015-07-16 14:36:05 -070058 OptionCode_SubnetMask((byte) 1), OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6),
59 OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15), OptionCode_BroadcastAddress((byte) 28),
60 OptionCode_RequestedIP((byte) 50), OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53),
61 OptionCode_DHCPServerIp((byte) 54), OptionCode_RequestedParameters((byte) 55),
62 OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59), OptionCode_ClientID((byte) 61),
63 OptionCode_END((byte) 255);
alshabibc4901cd2014-09-05 16:50:40 -070064
65 protected byte value;
66
67 private DHCPOptionCode(final byte value) {
68 this.value = value;
69 }
70
71 public byte getValue() {
72 return this.value;
73 }
74 }
75
samanwita palb300ca82015-07-16 14:36:05 -070076 public enum DHCPMessageType {
77 MessageType_Discover((byte) 1), MessageType_Offer((byte) 2), MessageType_Request((byte) 3),
78 MessageType_Decline((byte) 4), MessageType_ACK((byte) 5), MessageType_Nak((byte) 6),
79 MessageType_Release((byte) 7), MessageType_Inform((byte) 8);
80
81 protected byte value;
82
83 private DHCPMessageType(final byte value) {
84 this.value = value;
85 }
86
87 public byte getValue() {
88 return this.value;
89 }
90 }
91
alshabibc4901cd2014-09-05 16:50:40 -070092 protected byte opCode;
93 protected byte hardwareType;
94 protected byte hardwareAddressLength;
95 protected byte hops;
96 protected int transactionId;
97 protected short seconds;
98 protected short flags;
99 protected int clientIPAddress;
100 protected int yourIPAddress;
101 protected int serverIPAddress;
102 protected int gatewayIPAddress;
103 protected byte[] clientHardwareAddress;
104 protected String serverName;
105 protected String bootFileName;
106 protected List<DHCPOption> options = new ArrayList<DHCPOption>();
107
108 /**
109 * @return the opCode
110 */
111 public byte getOpCode() {
112 return this.opCode;
113 }
114
115 /**
116 * @param opCode
117 * the opCode to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800118 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700119 */
120 public DHCP setOpCode(final byte opCode) {
121 this.opCode = opCode;
122 return this;
123 }
124
125 /**
126 * @return the hardwareType
127 */
128 public byte getHardwareType() {
129 return this.hardwareType;
130 }
131
132 /**
133 * @param hardwareType
134 * the hardwareType to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800135 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700136 */
137 public DHCP setHardwareType(final byte hardwareType) {
138 this.hardwareType = hardwareType;
139 return this;
140 }
141
142 /**
143 * @return the hardwareAddressLength
144 */
145 public byte getHardwareAddressLength() {
146 return this.hardwareAddressLength;
147 }
148
149 /**
150 * @param hardwareAddressLength
151 * the hardwareAddressLength to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800152 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700153 */
154 public DHCP setHardwareAddressLength(final byte hardwareAddressLength) {
155 this.hardwareAddressLength = hardwareAddressLength;
156 return this;
157 }
158
159 /**
160 * @return the hops
161 */
162 public byte getHops() {
163 return this.hops;
164 }
165
166 /**
167 * @param hops
168 * the hops to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800169 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700170 */
171 public DHCP setHops(final byte hops) {
172 this.hops = hops;
173 return this;
174 }
175
176 /**
177 * @return the transactionId
178 */
179 public int getTransactionId() {
180 return this.transactionId;
181 }
182
183 /**
184 * @param transactionId
185 * the transactionId to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800186 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700187 */
188 public DHCP setTransactionId(final int transactionId) {
189 this.transactionId = transactionId;
190 return this;
191 }
192
193 /**
194 * @return the seconds
195 */
196 public short getSeconds() {
197 return this.seconds;
198 }
199
200 /**
201 * @param seconds
202 * the seconds to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800203 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700204 */
205 public DHCP setSeconds(final short seconds) {
206 this.seconds = seconds;
207 return this;
208 }
209
210 /**
211 * @return the flags
212 */
213 public short getFlags() {
214 return this.flags;
215 }
216
217 /**
218 * @param flags
219 * the flags to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800220 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700221 */
222 public DHCP setFlags(final short flags) {
223 this.flags = flags;
224 return this;
225 }
226
227 /**
228 * @return the clientIPAddress
229 */
230 public int getClientIPAddress() {
231 return this.clientIPAddress;
232 }
233
234 /**
235 * @param clientIPAddress
236 * the clientIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800237 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700238 */
239 public DHCP setClientIPAddress(final int clientIPAddress) {
240 this.clientIPAddress = clientIPAddress;
241 return this;
242 }
243
244 /**
245 * @return the yourIPAddress
246 */
247 public int getYourIPAddress() {
248 return this.yourIPAddress;
249 }
250
251 /**
252 * @param yourIPAddress
253 * the yourIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800254 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700255 */
256 public DHCP setYourIPAddress(final int yourIPAddress) {
257 this.yourIPAddress = yourIPAddress;
258 return this;
259 }
260
261 /**
262 * @return the serverIPAddress
263 */
264 public int getServerIPAddress() {
265 return this.serverIPAddress;
266 }
267
268 /**
269 * @param serverIPAddress
270 * the serverIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800271 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700272 */
273 public DHCP setServerIPAddress(final int serverIPAddress) {
274 this.serverIPAddress = serverIPAddress;
275 return this;
276 }
277
278 /**
279 * @return the gatewayIPAddress
280 */
281 public int getGatewayIPAddress() {
282 return this.gatewayIPAddress;
283 }
284
285 /**
286 * @param gatewayIPAddress
287 * the gatewayIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800288 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700289 */
290 public DHCP setGatewayIPAddress(final int gatewayIPAddress) {
291 this.gatewayIPAddress = gatewayIPAddress;
292 return this;
293 }
294
295 /**
296 * @return the clientHardwareAddress
297 */
298 public byte[] getClientHardwareAddress() {
299 return this.clientHardwareAddress;
300 }
301
302 /**
303 * @param clientHardwareAddress
304 * the clientHardwareAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800305 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700306 */
307 public DHCP setClientHardwareAddress(final byte[] clientHardwareAddress) {
308 this.clientHardwareAddress = clientHardwareAddress;
309 return this;
310 }
311
312 /**
313 * Gets a specific DHCP option parameter.
314 *
tom5f18cf32014-09-13 14:10:57 -0700315 * @param optionCode
alshabibc4901cd2014-09-05 16:50:40 -0700316 * The option code to get
317 * @return The value of the option if it exists, null otherwise
318 */
319 public DHCPOption getOption(final DHCPOptionCode optionCode) {
320 for (final DHCPOption opt : this.options) {
321 if (opt.code == optionCode.value) {
322 return opt;
323 }
324 }
325 return null;
326 }
327
328 /**
329 * @return the options
330 */
331 public List<DHCPOption> getOptions() {
332 return this.options;
333 }
334
335 /**
336 * @param options
337 * the options to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800338 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700339 */
340 public DHCP setOptions(final List<DHCPOption> options) {
341 this.options = options;
342 return this;
343 }
344
345 /**
346 * @return the packetType base on option 53
347 */
348 public DHCPPacketType getPacketType() {
349 final ListIterator<DHCPOption> lit = this.options.listIterator();
350 while (lit.hasNext()) {
351 final DHCPOption option = lit.next();
352 // only care option 53
353 if (option.getCode() == 53) {
354 return DHCPPacketType.getType(option.getData()[0]);
355 }
356 }
357 return null;
358 }
359
360 /**
361 * @return the serverName
362 */
363 public String getServerName() {
364 return this.serverName;
365 }
366
367 /**
368 * @param server
369 * the serverName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800370 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700371 */
372 public DHCP setServerName(final String server) {
373 this.serverName = server;
374 return this;
375 }
376
377 /**
378 * @return the bootFileName
379 */
380 public String getBootFileName() {
381 return this.bootFileName;
382 }
383
384 /**
385 * @param bootFile
386 * the bootFileName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800387 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700388 */
389 public DHCP setBootFileName(final String bootFile) {
390 this.bootFileName = bootFile;
391 return this;
392 }
393
394 @Override
395 public byte[] serialize() {
396 // not guaranteed to retain length/exact format
397 this.resetChecksum();
398
399 // minimum size 240 including magic cookie, options generally padded to
400 // 300
401 int optionsLength = 0;
402 for (final DHCPOption option : this.options) {
Yuta HIGUCHIe5ca93b2014-10-23 09:49:00 -0700403 if (option.getCode() == 0 || option.getCode() == ((byte) 255)) {
alshabibc4901cd2014-09-05 16:50:40 -0700404 optionsLength += 1;
405 } else {
406 optionsLength += 2 + (0xff & option.getLength());
407 }
408 }
409 int optionsPadLength = 0;
410 if (optionsLength < 60) {
411 optionsPadLength = 60 - optionsLength;
412 }
413
414 final byte[] data = new byte[240 + optionsLength + optionsPadLength];
415 final ByteBuffer bb = ByteBuffer.wrap(data);
416 bb.put(this.opCode);
417 bb.put(this.hardwareType);
418 bb.put(this.hardwareAddressLength);
419 bb.put(this.hops);
420 bb.putInt(this.transactionId);
421 bb.putShort(this.seconds);
422 bb.putShort(this.flags);
423 bb.putInt(this.clientIPAddress);
424 bb.putInt(this.yourIPAddress);
425 bb.putInt(this.serverIPAddress);
426 bb.putInt(this.gatewayIPAddress);
samanwita pal3ec8aff2015-08-03 18:04:27 -0700427 checkArgument(this.clientHardwareAddress.length <= 16,
428 "Hardware address is too long (%s bytes)", this.clientHardwareAddress.length);
alshabibc4901cd2014-09-05 16:50:40 -0700429 bb.put(this.clientHardwareAddress);
430 if (this.clientHardwareAddress.length < 16) {
431 for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) {
432 bb.put((byte) 0x0);
433 }
434 }
435 this.writeString(this.serverName, bb, 64);
436 this.writeString(this.bootFileName, bb, 128);
437 // magic cookie
438 bb.put((byte) 0x63);
439 bb.put((byte) 0x82);
440 bb.put((byte) 0x53);
441 bb.put((byte) 0x63);
442 for (final DHCPOption option : this.options) {
443 final int code = option.getCode() & 0xff;
444 bb.put((byte) code);
445 if (code != 0 && code != 255) {
446 bb.put(option.getLength());
447 bb.put(option.getData());
448 }
449 }
450 // assume the rest is padded out with zeroes
451 return data;
452 }
453
alshabibc4901cd2014-09-05 16:50:40 -0700454 @Override
455 public IPacket deserialize(final byte[] data, final int offset,
Jonathan Hart2a655752015-04-07 16:46:33 -0700456 final int length) {
alshabibc4901cd2014-09-05 16:50:40 -0700457 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
458 if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
459 return this;
460 }
461
462 this.opCode = bb.get();
463 this.hardwareType = bb.get();
464 this.hardwareAddressLength = bb.get();
465 this.hops = bb.get();
466 this.transactionId = bb.getInt();
467 this.seconds = bb.getShort();
468 this.flags = bb.getShort();
469 this.clientIPAddress = bb.getInt();
470 this.yourIPAddress = bb.getInt();
471 this.serverIPAddress = bb.getInt();
472 this.gatewayIPAddress = bb.getInt();
473 final int hardwareAddressLength = 0xff & this.hardwareAddressLength;
474 this.clientHardwareAddress = new byte[hardwareAddressLength];
475
476 bb.get(this.clientHardwareAddress);
477 for (int i = hardwareAddressLength; i < 16; ++i) {
478 bb.get();
479 }
480 this.serverName = this.readString(bb, 64);
481 this.bootFileName = this.readString(bb, 128);
482 // read the magic cookie
483 // magic cookie
484 bb.get();
485 bb.get();
486 bb.get();
487 bb.get();
488 // read options
489 while (bb.hasRemaining()) {
490 final DHCPOption option = new DHCPOption();
491 int code = 0xff & bb.get(); // convert signed byte to int in range
492 // [0,255]
493 option.setCode((byte) code);
494 if (code == 0) {
495 // skip these
496 continue;
497 } else if (code != 255) {
498 if (bb.hasRemaining()) {
499 final int l = 0xff & bb.get(); // convert signed byte to
500 // int in range [0,255]
501 option.setLength((byte) l);
502 if (bb.remaining() >= l) {
503 final byte[] optionData = new byte[l];
504 bb.get(optionData);
505 option.setData(optionData);
506 } else {
507 // Skip the invalid option and set the END option
508 code = 0xff;
509 option.setCode((byte) code);
510 option.setLength((byte) 0);
511 }
512 } else {
513 // Skip the invalid option and set the END option
514 code = 0xff;
515 option.setCode((byte) code);
516 option.setLength((byte) 0);
517 }
518 }
519 this.options.add(option);
520 if (code == 255) {
521 // remaining bytes are supposed to be 0, but ignore them just in
522 // case
523 break;
524 }
525 }
526
527 return this;
528 }
529
Jonathan Hart2a655752015-04-07 16:46:33 -0700530 protected void writeString(final String string, final ByteBuffer bb,
531 final int maxLength) {
532 if (string == null) {
533 for (int i = 0; i < maxLength; ++i) {
534 bb.put((byte) 0x0);
535 }
536 } else {
537 byte[] bytes = null;
538 try {
539 bytes = string.getBytes("ascii");
540 } catch (final UnsupportedEncodingException e) {
541 throw new RuntimeException("Failure encoding server name", e);
542 }
543 int writeLength = bytes.length;
544 if (writeLength > maxLength) {
545 writeLength = maxLength;
546 }
547 bb.put(bytes, 0, writeLength);
548 for (int i = writeLength; i < maxLength; ++i) {
549 bb.put((byte) 0x0);
550 }
551 }
552 }
553
554 private static String readString(final ByteBuffer bb, final int maxLength) {
alshabibc4901cd2014-09-05 16:50:40 -0700555 final byte[] bytes = new byte[maxLength];
556 bb.get(bytes);
557 String result = null;
558 try {
559 result = new String(bytes, "ascii").trim();
560 } catch (final UnsupportedEncodingException e) {
561 throw new RuntimeException("Failure decoding string", e);
562 }
563 return result;
564 }
Jonathan Hart2a655752015-04-07 16:46:33 -0700565
566 /**
567 * Deserializer function for DHCP packets.
568 *
569 * @return deserializer function
570 */
571 public static Deserializer<DHCP> deserializer() {
572 return (data, offset, length) -> {
573 checkInput(data, offset, length, MIN_HEADER_LENGTH);
574
575 ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
576 DHCP dhcp = new DHCP();
577
578 dhcp.opCode = bb.get();
579 dhcp.hardwareType = bb.get();
580 dhcp.hardwareAddressLength = bb.get();
581 dhcp.hops = bb.get();
582 dhcp.transactionId = bb.getInt();
583 dhcp.seconds = bb.getShort();
584 dhcp.flags = bb.getShort();
585 dhcp.clientIPAddress = bb.getInt();
586 dhcp.yourIPAddress = bb.getInt();
587 dhcp.serverIPAddress = bb.getInt();
588 dhcp.gatewayIPAddress = bb.getInt();
589 final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength;
590 dhcp.clientHardwareAddress = new byte[hardwareAddressLength];
591
592 bb.get(dhcp.clientHardwareAddress);
593 for (int i = hardwareAddressLength; i < 16; ++i) {
594 bb.get();
595 }
596 dhcp.serverName = readString(bb, 64);
597 dhcp.bootFileName = readString(bb, 128);
598 // read the magic cookie
599 // magic cookie
600 bb.get();
601 bb.get();
602 bb.get();
603 bb.get();
604
605 // read options
606 boolean foundEndOptionsMarker = false;
607 while (bb.hasRemaining()) {
608 final DHCPOption option = new DHCPOption();
609 int code = 0xff & bb.get(); // convert signed byte to int in range
610 // [0,255]
611 option.setCode((byte) code);
612 if (code == 0) {
613 // skip these
614 continue;
615 } else if (code != 255) {
616 if (bb.hasRemaining()) {
617 final int l = 0xff & bb.get(); // convert signed byte to
618 // int in range [0,255]
619 option.setLength((byte) l);
620 if (bb.remaining() >= l) {
621 final byte[] optionData = new byte[l];
622 bb.get(optionData);
623 option.setData(optionData);
624 dhcp.options.add(option);
625 } else {
626 throw new DeserializationException(
627 "Buffer underflow while reading DHCP option");
628 }
629 }
630 } else if (code == 255) {
631 // remaining bytes are supposed to be 0, but ignore them just in
632 // case
633 foundEndOptionsMarker = true;
634 break;
635 }
636 }
637
638 if (!foundEndOptionsMarker) {
639 throw new DeserializationException("DHCP End options marker was missing");
640 }
641
642 return dhcp;
643 };
644 }
alshabibc4901cd2014-09-05 16:50:40 -0700645}