blob: 491185d82561d35f7a977d8c072db94fd7c237a7 [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;
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),
65 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) {
429 final int code = option.getCode() & 0xff;
430 bb.put((byte) code);
431 if (code != 0 && code != 255) {
432 bb.put(option.getLength());
433 bb.put(option.getData());
434 }
435 }
436 // assume the rest is padded out with zeroes
437 return data;
438 }
439
alshabibc4901cd2014-09-05 16:50:40 -0700440 @Override
441 public IPacket deserialize(final byte[] data, final int offset,
Jonathan Hart2a655752015-04-07 16:46:33 -0700442 final int length) {
alshabibc4901cd2014-09-05 16:50:40 -0700443 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
444 if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
445 return this;
446 }
447
448 this.opCode = bb.get();
449 this.hardwareType = bb.get();
450 this.hardwareAddressLength = bb.get();
451 this.hops = bb.get();
452 this.transactionId = bb.getInt();
453 this.seconds = bb.getShort();
454 this.flags = bb.getShort();
455 this.clientIPAddress = bb.getInt();
456 this.yourIPAddress = bb.getInt();
457 this.serverIPAddress = bb.getInt();
458 this.gatewayIPAddress = bb.getInt();
459 final int hardwareAddressLength = 0xff & this.hardwareAddressLength;
460 this.clientHardwareAddress = new byte[hardwareAddressLength];
461
462 bb.get(this.clientHardwareAddress);
463 for (int i = hardwareAddressLength; i < 16; ++i) {
464 bb.get();
465 }
466 this.serverName = this.readString(bb, 64);
467 this.bootFileName = this.readString(bb, 128);
468 // read the magic cookie
469 // magic cookie
470 bb.get();
471 bb.get();
472 bb.get();
473 bb.get();
474 // read options
475 while (bb.hasRemaining()) {
476 final DHCPOption option = new DHCPOption();
477 int code = 0xff & bb.get(); // convert signed byte to int in range
478 // [0,255]
479 option.setCode((byte) code);
480 if (code == 0) {
481 // skip these
482 continue;
483 } else if (code != 255) {
484 if (bb.hasRemaining()) {
485 final int l = 0xff & bb.get(); // convert signed byte to
486 // int in range [0,255]
487 option.setLength((byte) l);
488 if (bb.remaining() >= l) {
489 final byte[] optionData = new byte[l];
490 bb.get(optionData);
491 option.setData(optionData);
492 } else {
493 // Skip the invalid option and set the END option
494 code = 0xff;
495 option.setCode((byte) code);
496 option.setLength((byte) 0);
497 }
498 } else {
499 // Skip the invalid option and set the END option
500 code = 0xff;
501 option.setCode((byte) code);
502 option.setLength((byte) 0);
503 }
504 }
505 this.options.add(option);
506 if (code == 255) {
507 // remaining bytes are supposed to be 0, but ignore them just in
508 // case
509 break;
510 }
511 }
512
513 return this;
514 }
515
Jonathan Hart2a655752015-04-07 16:46:33 -0700516 protected void writeString(final String string, final ByteBuffer bb,
517 final int maxLength) {
518 if (string == null) {
519 for (int i = 0; i < maxLength; ++i) {
520 bb.put((byte) 0x0);
521 }
522 } else {
523 byte[] bytes = null;
524 try {
525 bytes = string.getBytes("ascii");
526 } catch (final UnsupportedEncodingException e) {
527 throw new RuntimeException("Failure encoding server name", e);
528 }
529 int writeLength = bytes.length;
530 if (writeLength > maxLength) {
531 writeLength = maxLength;
532 }
533 bb.put(bytes, 0, writeLength);
534 for (int i = writeLength; i < maxLength; ++i) {
535 bb.put((byte) 0x0);
536 }
537 }
538 }
539
540 private static String readString(final ByteBuffer bb, final int maxLength) {
alshabibc4901cd2014-09-05 16:50:40 -0700541 final byte[] bytes = new byte[maxLength];
542 bb.get(bytes);
543 String result = null;
544 try {
545 result = new String(bytes, "ascii").trim();
546 } catch (final UnsupportedEncodingException e) {
547 throw new RuntimeException("Failure decoding string", e);
548 }
549 return result;
550 }
Jonathan Hart2a655752015-04-07 16:46:33 -0700551
552 /**
553 * Deserializer function for DHCP packets.
554 *
555 * @return deserializer function
556 */
557 public static Deserializer<DHCP> deserializer() {
558 return (data, offset, length) -> {
559 checkInput(data, offset, length, MIN_HEADER_LENGTH);
560
561 ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
562 DHCP dhcp = new DHCP();
563
564 dhcp.opCode = bb.get();
565 dhcp.hardwareType = bb.get();
566 dhcp.hardwareAddressLength = bb.get();
567 dhcp.hops = bb.get();
568 dhcp.transactionId = bb.getInt();
569 dhcp.seconds = bb.getShort();
570 dhcp.flags = bb.getShort();
571 dhcp.clientIPAddress = bb.getInt();
572 dhcp.yourIPAddress = bb.getInt();
573 dhcp.serverIPAddress = bb.getInt();
574 dhcp.gatewayIPAddress = bb.getInt();
575 final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength;
576 dhcp.clientHardwareAddress = new byte[hardwareAddressLength];
577
578 bb.get(dhcp.clientHardwareAddress);
579 for (int i = hardwareAddressLength; i < 16; ++i) {
580 bb.get();
581 }
582 dhcp.serverName = readString(bb, 64);
583 dhcp.bootFileName = readString(bb, 128);
584 // read the magic cookie
585 // magic cookie
586 bb.get();
587 bb.get();
588 bb.get();
589 bb.get();
590
591 // read options
592 boolean foundEndOptionsMarker = false;
593 while (bb.hasRemaining()) {
594 final DHCPOption option = new DHCPOption();
595 int code = 0xff & bb.get(); // convert signed byte to int in range
596 // [0,255]
597 option.setCode((byte) code);
598 if (code == 0) {
599 // skip these
600 continue;
601 } else if (code != 255) {
602 if (bb.hasRemaining()) {
603 final int l = 0xff & bb.get(); // convert signed byte to
604 // int in range [0,255]
605 option.setLength((byte) l);
606 if (bb.remaining() >= l) {
607 final byte[] optionData = new byte[l];
608 bb.get(optionData);
609 option.setData(optionData);
610 dhcp.options.add(option);
611 } else {
612 throw new DeserializationException(
613 "Buffer underflow while reading DHCP option");
614 }
615 }
616 } else if (code == 255) {
alshabib0588e572015-09-04 12:00:42 -0700617 DHCPOption end = new DHCPOption();
618 end.setCode((byte) 255);
619 dhcp.options.add(end);
Jonathan Hart2a655752015-04-07 16:46:33 -0700620 // remaining bytes are supposed to be 0, but ignore them just in
621 // case
622 foundEndOptionsMarker = true;
623 break;
624 }
625 }
626
627 if (!foundEndOptionsMarker) {
628 throw new DeserializationException("DHCP End options marker was missing");
629 }
630
631 return dhcp;
632 };
633 }
Jian Li5fc14292015-12-04 11:30:46 -0800634
635 @Override
636 public String toString() {
637 return toStringHelper(getClass())
638 .add("opCode", Byte.toString(opCode))
639 .add("hardwareType", Byte.toString(hardwareType))
640 .add("hardwareAddressLength", Byte.toString(hardwareAddressLength))
641 .add("hops", Byte.toString(hops))
642 .add("transactionId", Integer.toString(transactionId))
643 .add("seconds", Short.toString(seconds))
644 .add("flags", Short.toString(flags))
645 .add("clientIPAddress", Integer.toString(clientIPAddress))
646 .add("yourIPAddress", Integer.toString(yourIPAddress))
647 .add("serverIPAddress", Integer.toString(serverIPAddress))
648 .add("gatewayIPAddress", Integer.toString(gatewayIPAddress))
649 .add("clientHardwareAddress", Arrays.toString(clientHardwareAddress))
650 .add("serverName", serverName)
651 .add("bootFileName", bootFileName)
652 .toString();
653 // TODO: need to handle options
654 }
alshabibc4901cd2014-09-05 16:50:40 -0700655}