blob: 8dba13c593e13f16e3b5eb62e1e28c54e64879a4 [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
27/**
28 *
alshabibc4901cd2014-09-05 16:50:40 -070029 */
30public class DHCP extends BasePacket {
31 /**
32 * Dynamic Host Configuration Protocol packet.
33 * ------------------------------------------ |op (1) | htype(1) | hlen(1) |
34 * hops(1) | ------------------------------------------ | xid (4) |
35 * ------------------------------------------ | secs (2) | flags (2) |
36 * ------------------------------------------ | ciaddr (4) |
37 * ------------------------------------------ | yiaddr (4) |
38 * ------------------------------------------ | siaddr (4) |
39 * ------------------------------------------ | giaddr (4) |
40 * ------------------------------------------ | chaddr (16) |
41 * ------------------------------------------ | sname (64) |
42 * ------------------------------------------ | file (128) |
43 * ------------------------------------------ | options (312) |
44 * ------------------------------------------
45 *
46 */
47 // Header + magic without options
48 public static final int MIN_HEADER_LENGTH = 240;
49 public static final byte OPCODE_REQUEST = 0x1;
50 public static final byte OPCODE_REPLY = 0x2;
51
52 public static final byte HWTYPE_ETHERNET = 0x1;
53
54 public enum DHCPOptionCode {
55 OptionCode_SubnetMask((byte) 1), OptionCode_RequestedIP((byte) 50), OptionCode_LeaseTime(
56 (byte) 51), OptionCode_MessageType((byte) 53), OptionCode_DHCPServerIp(
57 (byte) 54), OptionCode_RequestedParameters((byte) 55), OptionCode_RenewalTime(
58 (byte) 58), OPtionCode_RebindingTime((byte) 59), OptionCode_ClientID(
59 (byte) 61), OptionCode_END((byte) 255);
60
61 protected byte value;
62
63 private DHCPOptionCode(final byte value) {
64 this.value = value;
65 }
66
67 public byte getValue() {
68 return this.value;
69 }
70 }
71
72 protected byte opCode;
73 protected byte hardwareType;
74 protected byte hardwareAddressLength;
75 protected byte hops;
76 protected int transactionId;
77 protected short seconds;
78 protected short flags;
79 protected int clientIPAddress;
80 protected int yourIPAddress;
81 protected int serverIPAddress;
82 protected int gatewayIPAddress;
83 protected byte[] clientHardwareAddress;
84 protected String serverName;
85 protected String bootFileName;
86 protected List<DHCPOption> options = new ArrayList<DHCPOption>();
87
88 /**
89 * @return the opCode
90 */
91 public byte getOpCode() {
92 return this.opCode;
93 }
94
95 /**
96 * @param opCode
97 * the opCode to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -080098 * @return this
alshabibc4901cd2014-09-05 16:50:40 -070099 */
100 public DHCP setOpCode(final byte opCode) {
101 this.opCode = opCode;
102 return this;
103 }
104
105 /**
106 * @return the hardwareType
107 */
108 public byte getHardwareType() {
109 return this.hardwareType;
110 }
111
112 /**
113 * @param hardwareType
114 * the hardwareType to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800115 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700116 */
117 public DHCP setHardwareType(final byte hardwareType) {
118 this.hardwareType = hardwareType;
119 return this;
120 }
121
122 /**
123 * @return the hardwareAddressLength
124 */
125 public byte getHardwareAddressLength() {
126 return this.hardwareAddressLength;
127 }
128
129 /**
130 * @param hardwareAddressLength
131 * the hardwareAddressLength to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800132 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700133 */
134 public DHCP setHardwareAddressLength(final byte hardwareAddressLength) {
135 this.hardwareAddressLength = hardwareAddressLength;
136 return this;
137 }
138
139 /**
140 * @return the hops
141 */
142 public byte getHops() {
143 return this.hops;
144 }
145
146 /**
147 * @param hops
148 * the hops to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800149 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700150 */
151 public DHCP setHops(final byte hops) {
152 this.hops = hops;
153 return this;
154 }
155
156 /**
157 * @return the transactionId
158 */
159 public int getTransactionId() {
160 return this.transactionId;
161 }
162
163 /**
164 * @param transactionId
165 * the transactionId to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800166 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700167 */
168 public DHCP setTransactionId(final int transactionId) {
169 this.transactionId = transactionId;
170 return this;
171 }
172
173 /**
174 * @return the seconds
175 */
176 public short getSeconds() {
177 return this.seconds;
178 }
179
180 /**
181 * @param seconds
182 * the seconds to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800183 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700184 */
185 public DHCP setSeconds(final short seconds) {
186 this.seconds = seconds;
187 return this;
188 }
189
190 /**
191 * @return the flags
192 */
193 public short getFlags() {
194 return this.flags;
195 }
196
197 /**
198 * @param flags
199 * the flags to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800200 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700201 */
202 public DHCP setFlags(final short flags) {
203 this.flags = flags;
204 return this;
205 }
206
207 /**
208 * @return the clientIPAddress
209 */
210 public int getClientIPAddress() {
211 return this.clientIPAddress;
212 }
213
214 /**
215 * @param clientIPAddress
216 * the clientIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800217 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700218 */
219 public DHCP setClientIPAddress(final int clientIPAddress) {
220 this.clientIPAddress = clientIPAddress;
221 return this;
222 }
223
224 /**
225 * @return the yourIPAddress
226 */
227 public int getYourIPAddress() {
228 return this.yourIPAddress;
229 }
230
231 /**
232 * @param yourIPAddress
233 * the yourIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800234 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700235 */
236 public DHCP setYourIPAddress(final int yourIPAddress) {
237 this.yourIPAddress = yourIPAddress;
238 return this;
239 }
240
241 /**
242 * @return the serverIPAddress
243 */
244 public int getServerIPAddress() {
245 return this.serverIPAddress;
246 }
247
248 /**
249 * @param serverIPAddress
250 * the serverIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800251 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700252 */
253 public DHCP setServerIPAddress(final int serverIPAddress) {
254 this.serverIPAddress = serverIPAddress;
255 return this;
256 }
257
258 /**
259 * @return the gatewayIPAddress
260 */
261 public int getGatewayIPAddress() {
262 return this.gatewayIPAddress;
263 }
264
265 /**
266 * @param gatewayIPAddress
267 * the gatewayIPAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800268 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700269 */
270 public DHCP setGatewayIPAddress(final int gatewayIPAddress) {
271 this.gatewayIPAddress = gatewayIPAddress;
272 return this;
273 }
274
275 /**
276 * @return the clientHardwareAddress
277 */
278 public byte[] getClientHardwareAddress() {
279 return this.clientHardwareAddress;
280 }
281
282 /**
283 * @param clientHardwareAddress
284 * the clientHardwareAddress to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800285 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700286 */
287 public DHCP setClientHardwareAddress(final byte[] clientHardwareAddress) {
288 this.clientHardwareAddress = clientHardwareAddress;
289 return this;
290 }
291
292 /**
293 * Gets a specific DHCP option parameter.
294 *
tom5f18cf32014-09-13 14:10:57 -0700295 * @param optionCode
alshabibc4901cd2014-09-05 16:50:40 -0700296 * The option code to get
297 * @return The value of the option if it exists, null otherwise
298 */
299 public DHCPOption getOption(final DHCPOptionCode optionCode) {
300 for (final DHCPOption opt : this.options) {
301 if (opt.code == optionCode.value) {
302 return opt;
303 }
304 }
305 return null;
306 }
307
308 /**
309 * @return the options
310 */
311 public List<DHCPOption> getOptions() {
312 return this.options;
313 }
314
315 /**
316 * @param options
317 * the options to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800318 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700319 */
320 public DHCP setOptions(final List<DHCPOption> options) {
321 this.options = options;
322 return this;
323 }
324
325 /**
326 * @return the packetType base on option 53
327 */
328 public DHCPPacketType getPacketType() {
329 final ListIterator<DHCPOption> lit = this.options.listIterator();
330 while (lit.hasNext()) {
331 final DHCPOption option = lit.next();
332 // only care option 53
333 if (option.getCode() == 53) {
334 return DHCPPacketType.getType(option.getData()[0]);
335 }
336 }
337 return null;
338 }
339
340 /**
341 * @return the serverName
342 */
343 public String getServerName() {
344 return this.serverName;
345 }
346
347 /**
348 * @param server
349 * the serverName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800350 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700351 */
352 public DHCP setServerName(final String server) {
353 this.serverName = server;
354 return this;
355 }
356
357 /**
358 * @return the bootFileName
359 */
360 public String getBootFileName() {
361 return this.bootFileName;
362 }
363
364 /**
365 * @param bootFile
366 * the bootFileName to set
Yuta HIGUCHI2281b3f2014-11-04 00:20:48 -0800367 * @return this
alshabibc4901cd2014-09-05 16:50:40 -0700368 */
369 public DHCP setBootFileName(final String bootFile) {
370 this.bootFileName = bootFile;
371 return this;
372 }
373
374 @Override
375 public byte[] serialize() {
376 // not guaranteed to retain length/exact format
377 this.resetChecksum();
378
379 // minimum size 240 including magic cookie, options generally padded to
380 // 300
381 int optionsLength = 0;
382 for (final DHCPOption option : this.options) {
Yuta HIGUCHIe5ca93b2014-10-23 09:49:00 -0700383 if (option.getCode() == 0 || option.getCode() == ((byte) 255)) {
alshabibc4901cd2014-09-05 16:50:40 -0700384 optionsLength += 1;
385 } else {
386 optionsLength += 2 + (0xff & option.getLength());
387 }
388 }
389 int optionsPadLength = 0;
390 if (optionsLength < 60) {
391 optionsPadLength = 60 - optionsLength;
392 }
393
394 final byte[] data = new byte[240 + optionsLength + optionsPadLength];
395 final ByteBuffer bb = ByteBuffer.wrap(data);
396 bb.put(this.opCode);
397 bb.put(this.hardwareType);
398 bb.put(this.hardwareAddressLength);
399 bb.put(this.hops);
400 bb.putInt(this.transactionId);
401 bb.putShort(this.seconds);
402 bb.putShort(this.flags);
403 bb.putInt(this.clientIPAddress);
404 bb.putInt(this.yourIPAddress);
405 bb.putInt(this.serverIPAddress);
406 bb.putInt(this.gatewayIPAddress);
407 bb.put(this.clientHardwareAddress);
408 if (this.clientHardwareAddress.length < 16) {
409 for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) {
410 bb.put((byte) 0x0);
411 }
412 }
413 this.writeString(this.serverName, bb, 64);
414 this.writeString(this.bootFileName, bb, 128);
415 // magic cookie
416 bb.put((byte) 0x63);
417 bb.put((byte) 0x82);
418 bb.put((byte) 0x53);
419 bb.put((byte) 0x63);
420 for (final DHCPOption option : this.options) {
421 final int code = option.getCode() & 0xff;
422 bb.put((byte) code);
423 if (code != 0 && code != 255) {
424 bb.put(option.getLength());
425 bb.put(option.getData());
426 }
427 }
428 // assume the rest is padded out with zeroes
429 return data;
430 }
431
432 protected void writeString(final String string, final ByteBuffer bb,
433 final int maxLength) {
434 if (string == null) {
435 for (int i = 0; i < maxLength; ++i) {
436 bb.put((byte) 0x0);
437 }
438 } else {
439 byte[] bytes = null;
440 try {
441 bytes = string.getBytes("ascii");
442 } catch (final UnsupportedEncodingException e) {
443 throw new RuntimeException("Failure encoding server name", e);
444 }
445 int writeLength = bytes.length;
446 if (writeLength > maxLength) {
447 writeLength = maxLength;
448 }
449 bb.put(bytes, 0, writeLength);
450 for (int i = writeLength; i < maxLength; ++i) {
451 bb.put((byte) 0x0);
452 }
453 }
454 }
455
456 @Override
457 public IPacket deserialize(final byte[] data, final int offset,
458 final int length) {
459 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
460 if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
461 return this;
462 }
463
464 this.opCode = bb.get();
465 this.hardwareType = bb.get();
466 this.hardwareAddressLength = bb.get();
467 this.hops = bb.get();
468 this.transactionId = bb.getInt();
469 this.seconds = bb.getShort();
470 this.flags = bb.getShort();
471 this.clientIPAddress = bb.getInt();
472 this.yourIPAddress = bb.getInt();
473 this.serverIPAddress = bb.getInt();
474 this.gatewayIPAddress = bb.getInt();
475 final int hardwareAddressLength = 0xff & this.hardwareAddressLength;
476 this.clientHardwareAddress = new byte[hardwareAddressLength];
477
478 bb.get(this.clientHardwareAddress);
479 for (int i = hardwareAddressLength; i < 16; ++i) {
480 bb.get();
481 }
482 this.serverName = this.readString(bb, 64);
483 this.bootFileName = this.readString(bb, 128);
484 // read the magic cookie
485 // magic cookie
486 bb.get();
487 bb.get();
488 bb.get();
489 bb.get();
490 // read options
491 while (bb.hasRemaining()) {
492 final DHCPOption option = new DHCPOption();
493 int code = 0xff & bb.get(); // convert signed byte to int in range
494 // [0,255]
495 option.setCode((byte) code);
496 if (code == 0) {
497 // skip these
498 continue;
499 } else if (code != 255) {
500 if (bb.hasRemaining()) {
501 final int l = 0xff & bb.get(); // convert signed byte to
502 // int in range [0,255]
503 option.setLength((byte) l);
504 if (bb.remaining() >= l) {
505 final byte[] optionData = new byte[l];
506 bb.get(optionData);
507 option.setData(optionData);
508 } else {
509 // Skip the invalid option and set the END option
510 code = 0xff;
511 option.setCode((byte) code);
512 option.setLength((byte) 0);
513 }
514 } else {
515 // Skip the invalid option and set the END option
516 code = 0xff;
517 option.setCode((byte) code);
518 option.setLength((byte) 0);
519 }
520 }
521 this.options.add(option);
522 if (code == 255) {
523 // remaining bytes are supposed to be 0, but ignore them just in
524 // case
525 break;
526 }
527 }
528
529 return this;
530 }
531
532 protected String readString(final ByteBuffer bb, final int maxLength) {
533 final byte[] bytes = new byte[maxLength];
534 bb.get(bytes);
535 String result = null;
536 try {
537 result = new String(bytes, "ascii").trim();
538 } catch (final UnsupportedEncodingException e) {
539 throw new RuntimeException("Failure decoding string", e);
540 }
541 return result;
542 }
543}