2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.network.internal.dhcp;
15 import java.io.ByteArrayInputStream;
16 import java.io.DataInputStream;
17 import java.io.IOException;
18 import java.net.DatagramPacket;
19 import java.net.InetAddress;
20 import java.net.UnknownHostException;
21 import java.util.LinkedHashMap;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
28 * Parses a dhcp packet and extracts the OP code and all DHCP Options.
31 * DatagramSocket socket = new DatagramSocket(67);
33 * DatagramPacket packet = new DatagramPacket(new byte[1500], 1500);
34 * socket.receive(packet);
35 * DHCPPacket dhcp = new DHCPPacket(packet);
36 * InetAddress requestedAddress = dhcp.getRequestedIPAddress();
39 * If used this way, beware that a <tt>BadPacketExpcetion</tt> is thrown
40 * if the datagram contains invalid DHCP data.
42 * @author David Graeff - Initial contribution
46 /** DHCP BOOTP CODES **/
47 static final byte BOOTREQUEST = 1;
48 static final byte BOOTREPLY = 2;
50 /** DHCP MESSAGE CODES **/
51 static final byte DHCPDISCOVER = 1;
53 static final byte DHCPREQUEST = 3;
54 static final byte DHCPDECLINE = 4;
56 static final byte DHCPRELEASE = 7;
57 static final byte DHCPINFORM = 8;
59 /** DHCP OPTIONS CODE **/
60 static final byte DHO_PAD = 0;
62 static final byte DHO_DHCP_REQUESTED_ADDRESS = 50;
64 static final byte DHO_DHCP_MESSAGE_TYPE = 53;
66 static final byte DHO_END = -1;
68 static final int BOOTP_ABSOLUTE_MIN_LEN = 236;
69 static final int DHCP_MAX_MTU = 1500;
72 static final int MAGIC_COOKIE = 0x63825363;
75 * If a DHCP datagram is malformed this Exception is thrown.
77 * It inherits from <tt>IllegalArgumentException</tt> and <tt>RuntimeException</tt>
78 * so it doesn't need to be explicitly caught.
80 * @author David Gräff
82 static class BadPacketException extends IllegalArgumentException {
83 private static final long serialVersionUID = 5866225879843384688L;
85 BadPacketException(String message) {
89 BadPacketException(String message, Throwable cause) {
90 super(message, cause);
95 private Map<Byte, byte[]> options;
98 * Package private constructor for test suite.
100 DHCPPacket(byte[] messageType, byte @Nullable [] requestedIP) {
101 this.op = BOOTREQUEST;
102 this.options = new LinkedHashMap<>();
103 options.put(DHO_DHCP_MESSAGE_TYPE, messageType);
104 if (requestedIP != null) {
105 options.put(DHO_DHCP_REQUESTED_ADDRESS, requestedIP);
110 * Constructor for the <tt>DHCPPacket</tt> class. Parses the given datagram.
112 public DHCPPacket(DatagramPacket datagram) throws BadPacketException, IOException {
114 this.options = new LinkedHashMap<>();
116 byte[] buffer = datagram.getData();
117 int offset = datagram.getOffset();
118 int length = datagram.getLength();
120 // absolute minimum size for a valid packet
121 if (length < BOOTP_ABSOLUTE_MIN_LEN) {
122 throw new BadPacketException(
123 "DHCP Packet too small (" + length + ") absolute minimum is " + BOOTP_ABSOLUTE_MIN_LEN);
125 // maximum size for a valid DHCP packet
126 if (length > DHCP_MAX_MTU) {
127 throw new BadPacketException("DHCP Packet too big (" + length + ") max MTU is " + DHCP_MAX_MTU);
130 // turn buffer into a readable stream
131 ByteArrayInputStream inBStream = new ByteArrayInputStream(buffer, offset, length);
132 DataInputStream inStream = new DataInputStream(inBStream);
134 byte[] dummy = new byte[128];
136 // parse static part of packet
137 this.op = inStream.readByte();
138 inStream.readByte(); // read hardware type (ETHERNET)
139 inStream.readByte(); // read hardware address length (6 bytes)
140 inStream.readByte(); // read hops
141 inStream.readInt(); // read transaction id
142 inStream.readShort(); // read secsonds elapsed
143 inStream.readShort(); // read flags
144 inStream.readFully(dummy, 0, 4); // ciaddr
145 inStream.readFully(dummy, 0, 4); // yiaddr
146 inStream.readFully(dummy, 0, 4); // siaddr
147 inStream.readFully(dummy, 0, 4); // giaddr
148 inStream.readFully(dummy, 0, 16); // chaddr
149 inStream.readFully(dummy, 0, 64); // sname
150 inStream.readFully(dummy, 0, 128); // file
152 // check for DHCP MAGIC_COOKIE
153 inBStream.mark(4); // read ahead 4 bytes
154 if (inStream.readInt() != MAGIC_COOKIE) {
155 throw new BadPacketException("Packet seams to be truncated");
158 // DHCP Packet: parsing options
162 int r = inBStream.read();
169 if (type == DHO_PAD) {
172 if (type == DHO_END) {
174 } // break if end of options
176 r = inBStream.read();
181 int len = Math.min(r, inBStream.available());
182 byte[] unitOpt = new byte[len];
183 inBStream.read(unitOpt);
185 this.options.put((byte) type, unitOpt);
187 if (type != DHO_END) {
188 throw new BadPacketException("Packet seams to be truncated");
193 * Returns the op field (Message op code).
196 * @return the op field.
198 public byte getOp() {
203 * Return the DHCP Option Type.
206 * This is a short-cut for <tt>getOptionAsByte(DHO_DHCP_MESSAGE_TYPE)</tt>.
208 * @return option type, of <tt>null</tt> if not present.
210 public @Nullable Byte getDHCPMessageType() {
211 byte[] opt = options.get(DHO_DHCP_MESSAGE_TYPE);
215 if (opt.length != 1) {
216 throw new BadPacketException(
217 "option " + DHO_DHCP_MESSAGE_TYPE + " is wrong size:" + opt.length + " should be 1");
223 * Returns the requested IP address of a BOOTREQUEST packet.
225 public @Nullable InetAddress getRequestedIPAddress() throws IllegalArgumentException, UnknownHostException {
226 byte[] opt = options.get(DHO_DHCP_REQUESTED_ADDRESS);
230 if (opt.length != 4) {
231 throw new BadPacketException(
232 "option " + DHO_DHCP_REQUESTED_ADDRESS + " is wrong size:" + opt.length + " should be 4");
234 return InetAddress.getByAddress(opt);