blob: 012443e4b655178b1e43daef8e88a2821571f0a4 [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2014-present 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;
Jian Li5fc14292015-12-04 11:30:46 -080024import java.util.Arrays;
alshabibc4901cd2014-09-05 16:50:40 -070025import java.util.List;
26import java.util.ListIterator;
27
samanwita pal3ec8aff2015-08-03 18:04:27 -070028import static com.google.common.base.Preconditions.checkArgument;
29import static org.onlab.packet.PacketUtils.checkInput;
Jian Li5fc14292015-12-04 11:30:46 -080030import static com.google.common.base.MoreObjects.toStringHelper;
Jonathan Hart2a655752015-04-07 16:46:33 -070031
alshabibc4901cd2014-09-05 16:50:40 -070032/**
Jian Li5fc14292015-12-04 11:30:46 -080033 * Representation of an DHCP Packet.
alshabibc4901cd2014-09-05 16:50:40 -070034 */
35public class DHCP extends BasePacket {
36 /**
37 * Dynamic Host Configuration Protocol packet.
38 * ------------------------------------------ |op (1) | htype(1) | hlen(1) |
39 * hops(1) | ------------------------------------------ | xid (4) |
40 * ------------------------------------------ | secs (2) | flags (2) |
41 * ------------------------------------------ | ciaddr (4) |
42 * ------------------------------------------ | yiaddr (4) |
43 * ------------------------------------------ | siaddr (4) |
44 * ------------------------------------------ | giaddr (4) |
45 * ------------------------------------------ | chaddr (16) |
46 * ------------------------------------------ | sname (64) |
47 * ------------------------------------------ | file (128) |
48 * ------------------------------------------ | options (312) |
49 * ------------------------------------------
50 *
51 */
52 // Header + magic without options
53 public static final int MIN_HEADER_LENGTH = 240;
54 public static final byte OPCODE_REQUEST = 0x1;
55 public static final byte OPCODE_REPLY = 0x2;
56
57 public static final byte HWTYPE_ETHERNET = 0x1;
58
59 public enum DHCPOptionCode {
samanwita palb300ca82015-07-16 14:36:05 -070060 OptionCode_SubnetMask((byte) 1), OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6),
61 OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15), OptionCode_BroadcastAddress((byte) 28),
62 OptionCode_RequestedIP((byte) 50), OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53),
63 OptionCode_DHCPServerIp((byte) 54), OptionCode_RequestedParameters((byte) 55),
64 OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59), OptionCode_ClientID((byte) 61),
gaurav4af95fb2016-07-07 01:48:44 +053065 OptionCode_CircuitID((byte) 82), OptionCode_END((byte) 255);
alshabibc4901cd2014-09-05 16:50:40 -070066
67 protected byte value;
68
69 private DHCPOptionCode(final byte value) {
70 this.value = value;
71 }
72
73 public byte getValue() {
74 return this.value;
75 }
76 }
77
78 protected byte opCode;
79 protected byte hardwareType;
80 protected byte hardwareAddressLength;
81 protected byte hops;
82 protected int transactionId;
83 protected short seconds;
84 protected short flags;
85 protected int clientIPAddress;
86 protected int yourIPAddress;
87 protected int serverIPAddress;
88 protected int gatewayIPAddress;
89 protected byte[] clientHardwareAddress;
90 protected String serverName;
91 protected String bootFileName;
92 protected List<DHCPOption> options = new ArrayList<DHCPOption>();
93
94 /**
95 * @return the opCode
96 */
97 public byte getOpCode() {
98 return this.opCode;
99 }
100
101 /**
102 * @param opCode
103 * the opCode to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800104 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700105 */
106 public DHCP setOpCode(final byte opCode) {
107 this.opCode = opCode;
108 return this;
109 }
110
111 /**
112 * @return the hardwareType
113 */
114 public byte getHardwareType() {
115 return this.hardwareType;
116 }
117
118 /**
119 * @param hardwareType
120 * the hardwareType to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800121 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700122 */
123 public DHCP setHardwareType(final byte hardwareType) {
124 this.hardwareType = hardwareType;
125 return this;
126 }
127
128 /**
129 * @return the hardwareAddressLength
130 */
131 public byte getHardwareAddressLength() {
132 return this.hardwareAddressLength;
133 }
134
135 /**
136 * @param hardwareAddressLength
137 * the hardwareAddressLength to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800138 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700139 */
140 public DHCP setHardwareAddressLength(final byte hardwareAddressLength) {
141 this.hardwareAddressLength = hardwareAddressLength;
142 return this;
143 }
144
145 /**
146 * @return the hops
147 */
148 public byte getHops() {
149 return this.hops;
150 }
151
152 /**
153 * @param hops
154 * the hops to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800155 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700156 */
157 public DHCP setHops(final byte hops) {
158 this.hops = hops;
159 return this;
160 }
161
162 /**
163 * @return the transactionId
164 */
165 public int getTransactionId() {
166 return this.transactionId;
167 }
168
169 /**
170 * @param transactionId
171 * the transactionId to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800172 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700173 */
174 public DHCP setTransactionId(final int transactionId) {
175 this.transactionId = transactionId;
176 return this;
177 }
178
179 /**
180 * @return the seconds
181 */
182 public short getSeconds() {
183 return this.seconds;
184 }
185
186 /**
187 * @param seconds
188 * the seconds to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800189 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700190 */
191 public DHCP setSeconds(final short seconds) {
192 this.seconds = seconds;
193 return this;
194 }
195
196 /**
197 * @return the flags
198 */
199 public short getFlags() {
200 return this.flags;
201 }
202
203 /**
204 * @param flags
205 * the flags to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800206 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700207 */
208 public DHCP setFlags(final short flags) {
209 this.flags = flags;
210 return this;
211 }
212
213 /**
214 * @return the clientIPAddress
215 */
216 public int getClientIPAddress() {
217 return this.clientIPAddress;
218 }
219
220 /**
221 * @param clientIPAddress
222 * the clientIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800223 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700224 */
225 public DHCP setClientIPAddress(final int clientIPAddress) {
226 this.clientIPAddress = clientIPAddress;
227 return this;
228 }
229
230 /**
231 * @return the yourIPAddress
232 */
233 public int getYourIPAddress() {
234 return this.yourIPAddress;
235 }
236
237 /**
238 * @param yourIPAddress
239 * the yourIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800240 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700241 */
242 public DHCP setYourIPAddress(final int yourIPAddress) {
243 this.yourIPAddress = yourIPAddress;
244 return this;
245 }
246
247 /**
248 * @return the serverIPAddress
249 */
250 public int getServerIPAddress() {
251 return this.serverIPAddress;
252 }
253
254 /**
255 * @param serverIPAddress
256 * the serverIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800257 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700258 */
259 public DHCP setServerIPAddress(final int serverIPAddress) {
260 this.serverIPAddress = serverIPAddress;
261 return this;
262 }
263
264 /**
265 * @return the gatewayIPAddress
266 */
267 public int getGatewayIPAddress() {
268 return this.gatewayIPAddress;
269 }
270
271 /**
272 * @param gatewayIPAddress
273 * the gatewayIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800274 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700275 */
276 public DHCP setGatewayIPAddress(final int gatewayIPAddress) {
277 this.gatewayIPAddress = gatewayIPAddress;
278 return this;
279 }
280
281 /**
282 * @return the clientHardwareAddress
283 */
284 public byte[] getClientHardwareAddress() {
285 return this.clientHardwareAddress;
286 }
287
288 /**
289 * @param clientHardwareAddress
290 * the clientHardwareAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800291 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700292 */
293 public DHCP setClientHardwareAddress(final byte[] clientHardwareAddress) {
294 this.clientHardwareAddress = clientHardwareAddress;
295 return this;
296 }
297
298 /**
299 * Gets a specific DHCP option parameter.
300 *
tom5f18cf32014-09-13 14:10:57 -0700301 * @param optionCode
alshabibc4901cd2014-09-05 16:50:40 -0700302 * The option code to get
303 * @return The value of the option if it exists, null otherwise
304 */
305 public DHCPOption getOption(final DHCPOptionCode optionCode) {
306 for (final DHCPOption opt : this.options) {
307 if (opt.code == optionCode.value) {
308 return opt;
309 }
310 }
311 return null;
312 }
313
314 /**
315 * @return the options
316 */
317 public List<DHCPOption> getOptions() {
318 return this.options;
319 }
320
321 /**
322 * @param options
323 * the options to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800324 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700325 */
326 public DHCP setOptions(final List<DHCPOption> options) {
327 this.options = options;
328 return this;
329 }
330
331 /**
332 * @return the packetType base on option 53
333 */
334 public DHCPPacketType getPacketType() {
335 final ListIterator<DHCPOption> lit = this.options.listIterator();
336 while (lit.hasNext()) {
337 final DHCPOption option = lit.next();
338 // only care option 53
339 if (option.getCode() == 53) {
340 return DHCPPacketType.getType(option.getData()[0]);
341 }
342 }
343 return null;
344 }
345
346 /**
347 * @return the serverName
348 */
349 public String getServerName() {
350 return this.serverName;
351 }
352
353 /**
354 * @param server
355 * the serverName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800356 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700357 */
358 public DHCP setServerName(final String server) {
359 this.serverName = server;
360 return this;
361 }
362
363 /**
364 * @return the bootFileName
365 */
366 public String getBootFileName() {
367 return this.bootFileName;
368 }
369
370 /**
371 * @param bootFile
372 * the bootFileName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800373 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700374 */
375 public DHCP setBootFileName(final String bootFile) {
376 this.bootFileName = bootFile;
377 return this;
378 }
379
380 @Override
381 public byte[] serialize() {
382 // not guaranteed to retain length/exact format
383 this.resetChecksum();
384
385 // minimum size 240 including magic cookie, options generally padded to
386 // 300
387 int optionsLength = 0;
388 for (final DHCPOption option : this.options) {
Yuta HIGUCHIe5ca93b2014-10-23 09:49:00 -0700389 if (option.getCode() == 0 || option.getCode() == ((byte) 255)) {
alshabibc4901cd2014-09-05 16:50:40 -0700390 optionsLength += 1;
391 } else {
392 optionsLength += 2 + (0xff & option.getLength());
393 }
394 }
395 int optionsPadLength = 0;
396 if (optionsLength < 60) {
397 optionsPadLength = 60 - optionsLength;
398 }
399
400 final byte[] data = new byte[240 + optionsLength + optionsPadLength];
401 final ByteBuffer bb = ByteBuffer.wrap(data);
402 bb.put(this.opCode);
403 bb.put(this.hardwareType);
404 bb.put(this.hardwareAddressLength);
405 bb.put(this.hops);
406 bb.putInt(this.transactionId);
407 bb.putShort(this.seconds);
408 bb.putShort(this.flags);
409 bb.putInt(this.clientIPAddress);
410 bb.putInt(this.yourIPAddress);
411 bb.putInt(this.serverIPAddress);
412 bb.putInt(this.gatewayIPAddress);
samanwita pal3ec8aff2015-08-03 18:04:27 -0700413 checkArgument(this.clientHardwareAddress.length <= 16,
414 "Hardware address is too long (%s bytes)", this.clientHardwareAddress.length);
alshabibc4901cd2014-09-05 16:50:40 -0700415 bb.put(this.clientHardwareAddress);
416 if (this.clientHardwareAddress.length < 16) {
417 for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) {
418 bb.put((byte) 0x0);
419 }
420 }
421 this.writeString(this.serverName, bb, 64);
422 this.writeString(this.bootFileName, bb, 128);
423 // magic cookie
424 bb.put((byte) 0x63);
425 bb.put((byte) 0x82);
426 bb.put((byte) 0x53);
427 bb.put((byte) 0x63);
428 for (final DHCPOption option : this.options) {
gaurav4af95fb2016-07-07 01:48:44 +0530429 dhcpOptionToByteArray(option, bb);
alshabibc4901cd2014-09-05 16:50:40 -0700430 }
431 // assume the rest is padded out with zeroes
432 return data;
433 }
434
gaurav4af95fb2016-07-07 01:48:44 +0530435 public static ByteBuffer dhcpOptionToByteArray(DHCPOption option, ByteBuffer bb) {
436 final int code = option.getCode() & 0xff;
437 bb.put((byte) code);
438 if (code != 0 && code != 255) {
439 bb.put(option.getLength());
440 bb.put(option.getData());
441 }
442 return bb;
443 }
444
alshabibc4901cd2014-09-05 16:50:40 -0700445 @Override
446 public IPacket deserialize(final byte[] data, final int offset,
Jonathan Hart2a655752015-04-07 16:46:33 -0700447 final int length) {
alshabibc4901cd2014-09-05 16:50:40 -0700448 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
449 if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
450 return this;
451 }
452
453 this.opCode = bb.get();
454 this.hardwareType = bb.get();
455 this.hardwareAddressLength = bb.get();
456 this.hops = bb.get();
457 this.transactionId = bb.getInt();
458 this.seconds = bb.getShort();
459 this.flags = bb.getShort();
460 this.clientIPAddress = bb.getInt();
461 this.yourIPAddress = bb.getInt();
462 this.serverIPAddress = bb.getInt();
463 this.gatewayIPAddress = bb.getInt();
464 final int hardwareAddressLength = 0xff & this.hardwareAddressLength;
465 this.clientHardwareAddress = new byte[hardwareAddressLength];
466
467 bb.get(this.clientHardwareAddress);
468 for (int i = hardwareAddressLength; i < 16; ++i) {
469 bb.get();
470 }
471 this.serverName = this.readString(bb, 64);
472 this.bootFileName = this.readString(bb, 128);
473 // read the magic cookie
474 // magic cookie
475 bb.get();
476 bb.get();
477 bb.get();
478 bb.get();
479 // read options
480 while (bb.hasRemaining()) {
481 final DHCPOption option = new DHCPOption();
482 int code = 0xff & bb.get(); // convert signed byte to int in range
483 // [0,255]
484 option.setCode((byte) code);
485 if (code == 0) {
486 // skip these
487 continue;
488 } else if (code != 255) {
489 if (bb.hasRemaining()) {
490 final int l = 0xff & bb.get(); // convert signed byte to
491 // int in range [0,255]
492 option.setLength((byte) l);
493 if (bb.remaining() >= l) {
494 final byte[] optionData = new byte[l];
495 bb.get(optionData);
496 option.setData(optionData);
497 } else {
498 // Skip the invalid option and set the END option
499 code = 0xff;
500 option.setCode((byte) code);
501 option.setLength((byte) 0);
502 }
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 }
510 this.options.add(option);
511 if (code == 255) {
512 // remaining bytes are supposed to be 0, but ignore them just in
513 // case
514 break;
515 }
516 }
517
518 return this;
519 }
520
Jonathan Hart2a655752015-04-07 16:46:33 -0700521 protected void writeString(final String string, final ByteBuffer bb,
522 final int maxLength) {
523 if (string == null) {
524 for (int i = 0; i < maxLength; ++i) {
525 bb.put((byte) 0x0);
526 }
527 } else {
528 byte[] bytes = null;
529 try {
530 bytes = string.getBytes("ascii");
531 } catch (final UnsupportedEncodingException e) {
532 throw new RuntimeException("Failure encoding server name", e);
533 }
534 int writeLength = bytes.length;
535 if (writeLength > maxLength) {
536 writeLength = maxLength;
537 }
538 bb.put(bytes, 0, writeLength);
539 for (int i = writeLength; i < maxLength; ++i) {
540 bb.put((byte) 0x0);
541 }
542 }
543 }
544
545 private static String readString(final ByteBuffer bb, final int maxLength) {
alshabibc4901cd2014-09-05 16:50:40 -0700546 final byte[] bytes = new byte[maxLength];
547 bb.get(bytes);
548 String result = null;
549 try {
550 result = new String(bytes, "ascii").trim();
551 } catch (final UnsupportedEncodingException e) {
552 throw new RuntimeException("Failure decoding string", e);
553 }
554 return result;
555 }
Jonathan Hart2a655752015-04-07 16:46:33 -0700556
557 /**
558 * Deserializer function for DHCP packets.
559 *
560 * @return deserializer function
561 */
562 public static Deserializer<DHCP> deserializer() {
563 return (data, offset, length) -> {
564 checkInput(data, offset, length, MIN_HEADER_LENGTH);
565
566 ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
567 DHCP dhcp = new DHCP();
568
569 dhcp.opCode = bb.get();
570 dhcp.hardwareType = bb.get();
571 dhcp.hardwareAddressLength = bb.get();
572 dhcp.hops = bb.get();
573 dhcp.transactionId = bb.getInt();
574 dhcp.seconds = bb.getShort();
575 dhcp.flags = bb.getShort();
576 dhcp.clientIPAddress = bb.getInt();
577 dhcp.yourIPAddress = bb.getInt();
578 dhcp.serverIPAddress = bb.getInt();
579 dhcp.gatewayIPAddress = bb.getInt();
580 final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength;
581 dhcp.clientHardwareAddress = new byte[hardwareAddressLength];
582
583 bb.get(dhcp.clientHardwareAddress);
584 for (int i = hardwareAddressLength; i < 16; ++i) {
585 bb.get();
586 }
587 dhcp.serverName = readString(bb, 64);
588 dhcp.bootFileName = readString(bb, 128);
589 // read the magic cookie
590 // magic cookie
591 bb.get();
592 bb.get();
593 bb.get();
594 bb.get();
595
596 // read options
597 boolean foundEndOptionsMarker = false;
598 while (bb.hasRemaining()) {
599 final DHCPOption option = new DHCPOption();
600 int code = 0xff & bb.get(); // convert signed byte to int in range
601 // [0,255]
602 option.setCode((byte) code);
603 if (code == 0) {
604 // skip these
605 continue;
606 } else if (code != 255) {
607 if (bb.hasRemaining()) {
608 final int l = 0xff & bb.get(); // convert signed byte to
609 // int in range [0,255]
610 option.setLength((byte) l);
611 if (bb.remaining() >= l) {
612 final byte[] optionData = new byte[l];
613 bb.get(optionData);
614 option.setData(optionData);
615 dhcp.options.add(option);
616 } else {
617 throw new DeserializationException(
618 "Buffer underflow while reading DHCP option");
619 }
620 }
621 } else if (code == 255) {
alshabib0588e572015-09-04 12:00:42 -0700622 DHCPOption end = new DHCPOption();
623 end.setCode((byte) 255);
624 dhcp.options.add(end);
Jonathan Hart2a655752015-04-07 16:46:33 -0700625 // remaining bytes are supposed to be 0, but ignore them just in
626 // case
627 foundEndOptionsMarker = true;
628 break;
629 }
630 }
631
632 if (!foundEndOptionsMarker) {
633 throw new DeserializationException("DHCP End options marker was missing");
634 }
635
636 return dhcp;
637 };
638 }
Jian Li5fc14292015-12-04 11:30:46 -0800639
640 @Override
641 public String toString() {
642 return toStringHelper(getClass())
643 .add("opCode", Byte.toString(opCode))
644 .add("hardwareType", Byte.toString(hardwareType))
645 .add("hardwareAddressLength", Byte.toString(hardwareAddressLength))
646 .add("hops", Byte.toString(hops))
647 .add("transactionId", Integer.toString(transactionId))
648 .add("seconds", Short.toString(seconds))
649 .add("flags", Short.toString(flags))
650 .add("clientIPAddress", Integer.toString(clientIPAddress))
651 .add("yourIPAddress", Integer.toString(yourIPAddress))
652 .add("serverIPAddress", Integer.toString(serverIPAddress))
653 .add("gatewayIPAddress", Integer.toString(gatewayIPAddress))
654 .add("clientHardwareAddress", Arrays.toString(clientHardwareAddress))
655 .add("serverName", serverName)
656 .add("bootFileName", bootFileName)
657 .toString();
658 // TODO: need to handle options
659 }
alshabibc4901cd2014-09-05 16:50:40 -0700660}