]> git.basschouten.com Git - openhab-addons.git/blob
3545e45277d1cc4706a1c9df5e6c1262555860d4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.network.internal.dhcp;
14
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;
22 import java.util.Map;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26
27 /**
28  * Parses a dhcp packet and extracts the OP code and all DHCP Options.
29  *
30  * Example:
31  * DatagramSocket socket = new DatagramSocket(67);
32  * while (true) {
33  * DatagramPacket packet = new DatagramPacket(new byte[1500], 1500);
34  * socket.receive(packet);
35  * DHCPPacket dhcp = new DHCPPacket(packet);
36  * InetAddress requestedAddress = dhcp.getRequestedIPAddress();
37  * }
38  *
39  * If used this way, beware that a <tt>BadPacketExpcetion</tt> is thrown
40  * if the datagram contains invalid DHCP data.
41  *
42  * @author David Graeff - Initial contribution
43  */
44 @NonNullByDefault
45 class DHCPPacket {
46     /** DHCP BOOTP CODES **/
47     static final byte BOOTREQUEST = 1;
48     static final byte BOOTREPLY = 2;
49
50     /** DHCP MESSAGE CODES **/
51     static final byte DHCPDISCOVER = 1;
52
53     static final byte DHCPREQUEST = 3;
54     static final byte DHCPDECLINE = 4;
55
56     static final byte DHCPRELEASE = 7;
57     static final byte DHCPINFORM = 8;
58
59     /** DHCP OPTIONS CODE **/
60     static final byte DHO_PAD = 0;
61
62     static final byte DHO_DHCP_REQUESTED_ADDRESS = 50;
63
64     static final byte DHO_DHCP_MESSAGE_TYPE = 53;
65
66     static final byte DHO_END = -1;
67
68     static final int BOOTP_ABSOLUTE_MIN_LEN = 236;
69     static final int DHCP_MAX_MTU = 1500;
70
71     // Magic cookie
72     static final int MAGIC_COOKIE = 0x63825363;
73
74     /**
75      * If a DHCP datagram is malformed this Exception is thrown.
76      *
77      * It inherits from <tt>IllegalArgumentException</tt> and <tt>RuntimeException</tt>
78      * so it doesn't need to be explicitly caught.
79      *
80      * @author David Gräff
81      */
82     static class BadPacketException extends IllegalArgumentException {
83         private static final long serialVersionUID = 5866225879843384688L;
84
85         BadPacketException(String message) {
86             super(message);
87         }
88
89         BadPacketException(String message, Throwable cause) {
90             super(message, cause);
91         }
92     }
93
94     private byte op;
95     private Map<Byte, byte[]> options;
96
97     /**
98      * Package private constructor for test suite.
99      */
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);
106         }
107     }
108
109     /**
110      * Constructor for the <tt>DHCPPacket</tt> class. Parses the given datagram.
111      */
112     public DHCPPacket(DatagramPacket datagram) throws BadPacketException, IOException {
113         this.op = BOOTREPLY;
114         this.options = new LinkedHashMap<>();
115
116         byte[] buffer = datagram.getData();
117         int offset = datagram.getOffset();
118         int length = datagram.getLength();
119
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);
124         }
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);
128         }
129
130         // turn buffer into a readable stream
131         ByteArrayInputStream inBStream = new ByteArrayInputStream(buffer, offset, length);
132         DataInputStream inStream = new DataInputStream(inBStream);
133
134         byte[] dummy = new byte[128];
135
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
151
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");
156         }
157
158         // DHCP Packet: parsing options
159         int type = 0;
160
161         while (true) {
162             int r = inBStream.read();
163             if (r < 0) {
164                 break;
165             } // EOF
166
167             type = (byte) r;
168
169             if (type == DHO_PAD) {
170                 continue;
171             } // skip Padding
172             if (type == DHO_END) {
173                 break;
174             } // break if end of options
175
176             r = inBStream.read();
177             if (r < 0) {
178                 break;
179             } // EOF
180
181             int len = Math.min(r, inBStream.available());
182             byte[] unitOpt = new byte[len];
183             inBStream.read(unitOpt);
184
185             this.options.put((byte) type, unitOpt);
186         }
187         if (type != DHO_END) {
188             throw new BadPacketException("Packet seams to be truncated");
189         }
190     }
191
192     /**
193      * Returns the op field (Message op code).
194      *
195      *
196      * @return the op field.
197      */
198     public byte getOp() {
199         return this.op;
200     }
201
202     /**
203      * Return the DHCP Option Type.
204      *
205      * <p>
206      * This is a short-cut for <tt>getOptionAsByte(DHO_DHCP_MESSAGE_TYPE)</tt>.
207      *
208      * @return option type, of <tt>null</tt> if not present.
209      */
210     public @Nullable Byte getDHCPMessageType() {
211         byte[] opt = options.get(DHO_DHCP_MESSAGE_TYPE);
212         if (opt == null) {
213             return null;
214         }
215         if (opt.length != 1) {
216             throw new BadPacketException(
217                     "option " + DHO_DHCP_MESSAGE_TYPE + " is wrong size:" + opt.length + " should be 1");
218         }
219         return opt[0];
220     }
221
222     /**
223      * Returns the requested IP address of a BOOTREQUEST packet.
224      */
225     public @Nullable InetAddress getRequestedIPAddress() throws IllegalArgumentException, UnknownHostException {
226         byte[] opt = options.get(DHO_DHCP_REQUESTED_ADDRESS);
227         if (opt == null) {
228             return null;
229         }
230         if (opt.length != 4) {
231             throw new BadPacketException(
232                     "option " + DHO_DHCP_REQUESTED_ADDRESS + " is wrong size:" + opt.length + " should be 4");
233         }
234         return InetAddress.getByAddress(opt);
235     }
236 }