blob: de5b43fadfc52a5ad117845e0097544cb1e684a5 [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
76 protected byte opCode;
77 protected byte hardwareType;
78 protected byte hardwareAddressLength;
79 protected byte hops;
80 protected int transactionId;
81 protected short seconds;
82 protected short flags;
83 protected int clientIPAddress;
84 protected int yourIPAddress;
85 protected int serverIPAddress;
86 protected int gatewayIPAddress;
87 protected byte[] clientHardwareAddress;
88 protected String serverName;
89 protected String bootFileName;
90 protected List<DHCPOption> options = new ArrayList<DHCPOption>();
91
92 /**
93 * @return the opCode
94 */
95 public byte getOpCode() {
96 return this.opCode;
97 }
98
99 /**
100 * @param opCode
101 * the opCode to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800102 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700103 */
104 public DHCP setOpCode(final byte opCode) {
105 this.opCode = opCode;
106 return this;
107 }
108
109 /**
110 * @return the hardwareType
111 */
112 public byte getHardwareType() {
113 return this.hardwareType;
114 }
115
116 /**
117 * @param hardwareType
118 * the hardwareType to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800119 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700120 */
121 public DHCP setHardwareType(final byte hardwareType) {
122 this.hardwareType = hardwareType;
123 return this;
124 }
125
126 /**
127 * @return the hardwareAddressLength
128 */
129 public byte getHardwareAddressLength() {
130 return this.hardwareAddressLength;
131 }
132
133 /**
134 * @param hardwareAddressLength
135 * the hardwareAddressLength to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800136 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700137 */
138 public DHCP setHardwareAddressLength(final byte hardwareAddressLength) {
139 this.hardwareAddressLength = hardwareAddressLength;
140 return this;
141 }
142
143 /**
144 * @return the hops
145 */
146 public byte getHops() {
147 return this.hops;
148 }
149
150 /**
151 * @param hops
152 * the hops to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800153 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700154 */
155 public DHCP setHops(final byte hops) {
156 this.hops = hops;
157 return this;
158 }
159
160 /**
161 * @return the transactionId
162 */
163 public int getTransactionId() {
164 return this.transactionId;
165 }
166
167 /**
168 * @param transactionId
169 * the transactionId to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800170 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700171 */
172 public DHCP setTransactionId(final int transactionId) {
173 this.transactionId = transactionId;
174 return this;
175 }
176
177 /**
178 * @return the seconds
179 */
180 public short getSeconds() {
181 return this.seconds;
182 }
183
184 /**
185 * @param seconds
186 * the seconds to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800187 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700188 */
189 public DHCP setSeconds(final short seconds) {
190 this.seconds = seconds;
191 return this;
192 }
193
194 /**
195 * @return the flags
196 */
197 public short getFlags() {
198 return this.flags;
199 }
200
201 /**
202 * @param flags
203 * the flags to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800204 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700205 */
206 public DHCP setFlags(final short flags) {
207 this.flags = flags;
208 return this;
209 }
210
211 /**
212 * @return the clientIPAddress
213 */
214 public int getClientIPAddress() {
215 return this.clientIPAddress;
216 }
217
218 /**
219 * @param clientIPAddress
220 * the clientIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800221 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700222 */
223 public DHCP setClientIPAddress(final int clientIPAddress) {
224 this.clientIPAddress = clientIPAddress;
225 return this;
226 }
227
228 /**
229 * @return the yourIPAddress
230 */
231 public int getYourIPAddress() {
232 return this.yourIPAddress;
233 }
234
235 /**
236 * @param yourIPAddress
237 * the yourIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800238 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700239 */
240 public DHCP setYourIPAddress(final int yourIPAddress) {
241 this.yourIPAddress = yourIPAddress;
242 return this;
243 }
244
245 /**
246 * @return the serverIPAddress
247 */
248 public int getServerIPAddress() {
249 return this.serverIPAddress;
250 }
251
252 /**
253 * @param serverIPAddress
254 * the serverIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800255 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700256 */
257 public DHCP setServerIPAddress(final int serverIPAddress) {
258 this.serverIPAddress = serverIPAddress;
259 return this;
260 }
261
262 /**
263 * @return the gatewayIPAddress
264 */
265 public int getGatewayIPAddress() {
266 return this.gatewayIPAddress;
267 }
268
269 /**
270 * @param gatewayIPAddress
271 * the gatewayIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800272 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700273 */
274 public DHCP setGatewayIPAddress(final int gatewayIPAddress) {
275 this.gatewayIPAddress = gatewayIPAddress;
276 return this;
277 }
278
279 /**
280 * @return the clientHardwareAddress
281 */
282 public byte[] getClientHardwareAddress() {
283 return this.clientHardwareAddress;
284 }
285
286 /**
287 * @param clientHardwareAddress
288 * the clientHardwareAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800289 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700290 */
291 public DHCP setClientHardwareAddress(final byte[] clientHardwareAddress) {
292 this.clientHardwareAddress = clientHardwareAddress;
293 return this;
294 }
295
296 /**
297 * Gets a specific DHCP option parameter.
298 *
tom5f18cf32014-09-13 14:10:57 -0700299 * @param optionCode
alshabibc4901cd2014-09-05 16:50:40 -0700300 * The option code to get
301 * @return The value of the option if it exists, null otherwise
302 */
303 public DHCPOption getOption(final DHCPOptionCode optionCode) {
304 for (final DHCPOption opt : this.options) {
305 if (opt.code == optionCode.value) {
306 return opt;
307 }
308 }
309 return null;
310 }
311
312 /**
313 * @return the options
314 */
315 public List<DHCPOption> getOptions() {
316 return this.options;
317 }
318
319 /**
320 * @param options
321 * the options to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800322 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700323 */
324 public DHCP setOptions(final List<DHCPOption> options) {
325 this.options = options;
326 return this;
327 }
328
329 /**
330 * @return the packetType base on option 53
331 */
332 public DHCPPacketType getPacketType() {
333 final ListIterator<DHCPOption> lit = this.options.listIterator();
334 while (lit.hasNext()) {
335 final DHCPOption option = lit.next();
336 // only care option 53
337 if (option.getCode() == 53) {
338 return DHCPPacketType.getType(option.getData()[0]);
339 }
340 }
341 return null;
342 }
343
344 /**
345 * @return the serverName
346 */
347 public String getServerName() {
348 return this.serverName;
349 }
350
351 /**
352 * @param server
353 * the serverName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800354 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700355 */
356 public DHCP setServerName(final String server) {
357 this.serverName = server;
358 return this;
359 }
360
361 /**
362 * @return the bootFileName
363 */
364 public String getBootFileName() {
365 return this.bootFileName;
366 }
367
368 /**
369 * @param bootFile
370 * the bootFileName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800371 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700372 */
373 public DHCP setBootFileName(final String bootFile) {
374 this.bootFileName = bootFile;
375 return this;
376 }
377
378 @Override
379 public byte[] serialize() {
380 // not guaranteed to retain length/exact format
381 this.resetChecksum();
382
383 // minimum size 240 including magic cookie, options generally padded to
384 // 300
385 int optionsLength = 0;
386 for (final DHCPOption option : this.options) {
Yuta HIGUCHIe5ca93b2014-10-23 09:49:00 -0700387 if (option.getCode() == 0 || option.getCode() == ((byte) 255)) {
alshabibc4901cd2014-09-05 16:50:40 -0700388 optionsLength += 1;
389 } else {
390 optionsLength += 2 + (0xff & option.getLength());
391 }
392 }
393 int optionsPadLength = 0;
394 if (optionsLength < 60) {
395 optionsPadLength = 60 - optionsLength;
396 }
397
398 final byte[] data = new byte[240 + optionsLength + optionsPadLength];
399 final ByteBuffer bb = ByteBuffer.wrap(data);
400 bb.put(this.opCode);
401 bb.put(this.hardwareType);
402 bb.put(this.hardwareAddressLength);
403 bb.put(this.hops);
404 bb.putInt(this.transactionId);
405 bb.putShort(this.seconds);
406 bb.putShort(this.flags);
407 bb.putInt(this.clientIPAddress);
408 bb.putInt(this.yourIPAddress);
409 bb.putInt(this.serverIPAddress);
410 bb.putInt(this.gatewayIPAddress);
samanwita pal3ec8aff2015-08-03 18:04:27 -0700411 checkArgument(this.clientHardwareAddress.length <= 16,
412 "Hardware address is too long (%s bytes)", this.clientHardwareAddress.length);
alshabibc4901cd2014-09-05 16:50:40 -0700413 bb.put(this.clientHardwareAddress);
414 if (this.clientHardwareAddress.length < 16) {
415 for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) {
416 bb.put((byte) 0x0);
417 }
418 }
419 this.writeString(this.serverName, bb, 64);
420 this.writeString(this.bootFileName, bb, 128);
421 // magic cookie
422 bb.put((byte) 0x63);
423 bb.put((byte) 0x82);
424 bb.put((byte) 0x53);
425 bb.put((byte) 0x63);
426 for (final DHCPOption option : this.options) {
427 final int code = option.getCode() & 0xff;
428 bb.put((byte) code);
429 if (code != 0 && code != 255) {
430 bb.put(option.getLength());
431 bb.put(option.getData());
432 }
433 }
434 // assume the rest is padded out with zeroes
435 return data;
436 }
437
alshabibc4901cd2014-09-05 16:50:40 -0700438 @Override
439 public IPacket deserialize(final byte[] data, final int offset,
Jonathan Hart2a655752015-04-07 16:46:33 -0700440 final int length) {
alshabibc4901cd2014-09-05 16:50:40 -0700441 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
442 if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
443 return this;
444 }
445
446 this.opCode = bb.get();
447 this.hardwareType = bb.get();
448 this.hardwareAddressLength = bb.get();
449 this.hops = bb.get();
450 this.transactionId = bb.getInt();
451 this.seconds = bb.getShort();
452 this.flags = bb.getShort();
453 this.clientIPAddress = bb.getInt();
454 this.yourIPAddress = bb.getInt();
455 this.serverIPAddress = bb.getInt();
456 this.gatewayIPAddress = bb.getInt();
457 final int hardwareAddressLength = 0xff & this.hardwareAddressLength;
458 this.clientHardwareAddress = new byte[hardwareAddressLength];
459
460 bb.get(this.clientHardwareAddress);
461 for (int i = hardwareAddressLength; i < 16; ++i) {
462 bb.get();
463 }
464 this.serverName = this.readString(bb, 64);
465 this.bootFileName = this.readString(bb, 128);
466 // read the magic cookie
467 // magic cookie
468 bb.get();
469 bb.get();
470 bb.get();
471 bb.get();
472 // read options
473 while (bb.hasRemaining()) {
474 final DHCPOption option = new DHCPOption();
475 int code = 0xff & bb.get(); // convert signed byte to int in range
476 // [0,255]
477 option.setCode((byte) code);
478 if (code == 0) {
479 // skip these
480 continue;
481 } else if (code != 255) {
482 if (bb.hasRemaining()) {
483 final int l = 0xff & bb.get(); // convert signed byte to
484 // int in range [0,255]
485 option.setLength((byte) l);
486 if (bb.remaining() >= l) {
487 final byte[] optionData = new byte[l];
488 bb.get(optionData);
489 option.setData(optionData);
490 } else {
491 // Skip the invalid option and set the END option
492 code = 0xff;
493 option.setCode((byte) code);
494 option.setLength((byte) 0);
495 }
496 } else {
497 // Skip the invalid option and set the END option
498 code = 0xff;
499 option.setCode((byte) code);
500 option.setLength((byte) 0);
501 }
502 }
503 this.options.add(option);
504 if (code == 255) {
505 // remaining bytes are supposed to be 0, but ignore them just in
506 // case
507 break;
508 }
509 }
510
511 return this;
512 }
513
Jonathan Hart2a655752015-04-07 16:46:33 -0700514 protected void writeString(final String string, final ByteBuffer bb,
515 final int maxLength) {
516 if (string == null) {
517 for (int i = 0; i < maxLength; ++i) {
518 bb.put((byte) 0x0);
519 }
520 } else {
521 byte[] bytes = null;
522 try {
523 bytes = string.getBytes("ascii");
524 } catch (final UnsupportedEncodingException e) {
525 throw new RuntimeException("Failure encoding server name", e);
526 }
527 int writeLength = bytes.length;
528 if (writeLength > maxLength) {
529 writeLength = maxLength;
530 }
531 bb.put(bytes, 0, writeLength);
532 for (int i = writeLength; i < maxLength; ++i) {
533 bb.put((byte) 0x0);
534 }
535 }
536 }
537
538 private static String readString(final ByteBuffer bb, final int maxLength) {
alshabibc4901cd2014-09-05 16:50:40 -0700539 final byte[] bytes = new byte[maxLength];
540 bb.get(bytes);
541 String result = null;
542 try {
543 result = new String(bytes, "ascii").trim();
544 } catch (final UnsupportedEncodingException e) {
545 throw new RuntimeException("Failure decoding string", e);
546 }
547 return result;
548 }
Jonathan Hart2a655752015-04-07 16:46:33 -0700549
550 /**
551 * Deserializer function for DHCP packets.
552 *
553 * @return deserializer function
554 */
555 public static Deserializer<DHCP> deserializer() {
556 return (data, offset, length) -> {
557 checkInput(data, offset, length, MIN_HEADER_LENGTH);
558
559 ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
560 DHCP dhcp = new DHCP();
561
562 dhcp.opCode = bb.get();
563 dhcp.hardwareType = bb.get();
564 dhcp.hardwareAddressLength = bb.get();
565 dhcp.hops = bb.get();
566 dhcp.transactionId = bb.getInt();
567 dhcp.seconds = bb.getShort();
568 dhcp.flags = bb.getShort();
569 dhcp.clientIPAddress = bb.getInt();
570 dhcp.yourIPAddress = bb.getInt();
571 dhcp.serverIPAddress = bb.getInt();
572 dhcp.gatewayIPAddress = bb.getInt();
573 final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength;
574 dhcp.clientHardwareAddress = new byte[hardwareAddressLength];
575
576 bb.get(dhcp.clientHardwareAddress);
577 for (int i = hardwareAddressLength; i < 16; ++i) {
578 bb.get();
579 }
580 dhcp.serverName = readString(bb, 64);
581 dhcp.bootFileName = readString(bb, 128);
582 // read the magic cookie
583 // magic cookie
584 bb.get();
585 bb.get();
586 bb.get();
587 bb.get();
588
589 // read options
590 boolean foundEndOptionsMarker = false;
591 while (bb.hasRemaining()) {
592 final DHCPOption option = new DHCPOption();
593 int code = 0xff & bb.get(); // convert signed byte to int in range
594 // [0,255]
595 option.setCode((byte) code);
596 if (code == 0) {
597 // skip these
598 continue;
599 } else if (code != 255) {
600 if (bb.hasRemaining()) {
601 final int l = 0xff & bb.get(); // convert signed byte to
602 // int in range [0,255]
603 option.setLength((byte) l);
604 if (bb.remaining() >= l) {
605 final byte[] optionData = new byte[l];
606 bb.get(optionData);
607 option.setData(optionData);
608 dhcp.options.add(option);
609 } else {
610 throw new DeserializationException(
611 "Buffer underflow while reading DHCP option");
612 }
613 }
614 } else if (code == 255) {
alshabib0588e572015-09-04 12:00:42 -0700615 DHCPOption end = new DHCPOption();
616 end.setCode((byte) 255);
617 dhcp.options.add(end);
Jonathan Hart2a655752015-04-07 16:46:33 -0700618 // remaining bytes are supposed to be 0, but ignore them just in
619 // case
620 foundEndOptionsMarker = true;
621 break;
622 }
623 }
624
625 if (!foundEndOptionsMarker) {
626 throw new DeserializationException("DHCP End options marker was missing");
627 }
628
629 return dhcp;
630 };
631 }
alshabibc4901cd2014-09-05 16:50:40 -0700632}