OpenVPN
dhcp.c
Go to the documentation of this file.
1/*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single TCP/UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27#include "syshead.h"
28
29#include "dhcp.h"
30#include "socket_util.h"
31#include "error.h"
32
33#include "memdbg.h"
34
35static int
36get_dhcp_message_type(const struct dhcp *dhcp, const int optlen)
37{
38 const uint8_t *p = (uint8_t *)(dhcp + 1);
39 int i;
40
41 for (i = 0; i < optlen; ++i)
42 {
43 const uint8_t type = p[i];
44 const int room = optlen - i;
45 if (type == DHCP_END) /* didn't find what we were looking for */
46 {
47 return -1;
48 }
49 else if (type == DHCP_PAD) /* no-operation */
50 {
51 }
52 else if (type == DHCP_MSG_TYPE) /* what we are looking for */
53 {
54 if (room >= 3)
55 {
56 if (p[i + 1] == 1) /* option length should be 1 */
57 {
58 return p[i + 2]; /* return message type */
59 }
60 }
61 return -1;
62 }
63 else /* some other option */
64 {
65 if (room >= 2)
66 {
67 const int len = p[i + 1]; /* get option length */
68 i += (len + 1); /* advance to next option */
69 }
70 }
71 }
72 return -1;
73}
74
75static in_addr_t
76do_extract(struct dhcp *dhcp, int optlen)
77{
78 uint8_t *p = (uint8_t *)(dhcp + 1);
79 int i;
80 in_addr_t ret = 0;
81
82 for (i = 0; i < optlen;)
83 {
84 const uint8_t type = p[i];
85 const int room = optlen - i;
86 if (type == DHCP_END)
87 {
88 break;
89 }
90 else if (type == DHCP_PAD)
91 {
92 ++i;
93 }
94 else if (type == DHCP_ROUTER)
95 {
96 if (room >= 2)
97 {
98 const int len = p[i + 1]; /* get option length */
99 if (len <= (room - 2))
100 {
101 /* get router IP address */
102 if (!ret && len >= 4 && (len & 3) == 0)
103 {
104 memcpy(&ret, p + i + 2, 4);
105 ret = ntohl(ret);
106 }
107 {
108 /* delete the router option */
109 uint8_t *dest = p + i;
110 const int owlen = len + 2; /* len of data to overwrite */
111 uint8_t *src = dest + owlen;
112 uint8_t *end = p + optlen;
113 const intptr_t movlen = end - src;
114 if (movlen > 0)
115 {
116 memmove(dest, src, movlen); /* overwrite router option */
117 }
118 memset(end - owlen, DHCP_PAD, owlen); /* pad tail */
119 }
120 }
121 else
122 {
123 break;
124 }
125 }
126 else
127 {
128 break;
129 }
130 }
131 else /* some other option */
132 {
133 if (room >= 2)
134 {
135 const int len = p[i + 1]; /* get option length */
136 i += (len + 2); /* advance to next option */
137 }
138 else
139 {
140 break;
141 }
142 }
143 }
144 return ret;
145}
146
149{
150 struct dhcp_full *df = (struct dhcp_full *)BPTR(ipbuf);
151 const int optlen =
152 BLEN(ipbuf)
153 - (int)(sizeof(struct openvpn_iphdr) + sizeof(struct openvpn_udphdr) + sizeof(struct dhcp));
154
155 if (optlen >= 0 && df->ip.protocol == OPENVPN_IPPROTO_UDP
156 && df->udp.source == htons(BOOTPS_PORT) && df->udp.dest == htons(BOOTPC_PORT)
157 && df->dhcp.op == BOOTREPLY)
158 {
159 const int message_type = get_dhcp_message_type(&df->dhcp, optlen);
161 {
162 /* get the router IP address while padding out all DHCP router options */
163 const in_addr_t ret = do_extract(&df->dhcp, optlen);
164
165 /* recompute the UDP checksum */
166 df->udp.check = 0;
167 df->udp.check = htons(ip_checksum(
168 AF_INET, (uint8_t *)&df->udp,
169 sizeof(struct openvpn_udphdr) + sizeof(struct dhcp) + optlen,
170 (uint8_t *)&df->ip.saddr, (uint8_t *)&df->ip.daddr, OPENVPN_IPPROTO_UDP));
171
172 /* only return the extracted Router address if DHCPACK */
173 if (message_type == DHCPACK)
174 {
175 if (ret)
176 {
177 struct gc_arena gc = gc_new();
178 msg(D_ROUTE, "Extracted DHCP router address: %s", print_in_addr_t(ret, 0, &gc));
179 gc_free(&gc);
180 }
181
182 return ret;
183 }
184 }
185 }
186 return 0;
187}
188
189#if defined(_WIN32) || defined(DHCP_UNIT_TEST)
190
191/*
192 * Convert DHCP options from the command line / config file
193 * into a raw DHCP-format options string.
194 */
195
196static void
197write_dhcp_u8(struct buffer *buf, const uint8_t type, const uint8_t data, bool *error)
198{
199 if (!buf_safe(buf, 3))
200 {
201 *error = true;
202 msg(M_WARN, "write_dhcp_u8: buffer overflow building DHCP options");
203 return;
204 }
205 buf_write_u8(buf, type);
206 buf_write_u8(buf, 1);
207 buf_write_u8(buf, data);
208}
209
210static void
211write_dhcp_u32_array(struct buffer *buf, const uint8_t type, const uint32_t *data,
212 const unsigned int len, bool *error)
213{
214 if (len > 0)
215 {
216 const size_t size = len * sizeof(uint32_t);
217
218 if (!buf_safe(buf, 2 + size))
219 {
220 *error = true;
221 msg(M_WARN, "write_dhcp_u32_array: buffer overflow building DHCP options");
222 return;
223 }
224 if (size < 1 || size > 255)
225 {
226 *error = true;
227 msg(M_WARN, "write_dhcp_u32_array: size (%zu) must be > 0 and <= 255", size);
228 return;
229 }
230 buf_write_u8(buf, type);
231 buf_write_u8(buf, (uint8_t)size);
232 for (unsigned int i = 0; i < len; ++i)
233 {
234 buf_write_u32(buf, data[i]);
235 }
236 }
237}
238
239static void
240write_dhcp_str(struct buffer *buf, const uint8_t type, const char *str, bool *error)
241{
242 const size_t len = strlen(str);
243 if (!buf_safe(buf, 2 + len))
244 {
245 *error = true;
246 msg(M_WARN, "write_dhcp_str: buffer overflow building DHCP options");
247 return;
248 }
249 if (len < 1 || len > 255)
250 {
251 *error = true;
252 msg(M_WARN, "write_dhcp_str: string '%s' must be > 0 bytes and <= 255 bytes", str);
253 return;
254 }
255 buf_write_u8(buf, type);
256 buf_write_u8(buf, (uint8_t)len);
257 buf_write(buf, str, len);
258}
259
260/*
261 * RFC3397 states that multiple searchdomains are encoded as follows:
262 * - at start the length of the entire option is given
263 * - each subdomain is preceded by its length
264 * - each searchdomain is separated by a NUL character
265 * e.g. if you want "openvpn.net" and "duckduckgo.com" then you end up with
266 * 0x1D 0x7 openvpn 0x3 net 0x00 0x0A duckduckgo 0x3 com 0x00
267 */
268static void
269write_dhcp_search_str(struct buffer *buf, const uint8_t type, const char *const *str_array,
270 int array_len, bool *error)
271{
272 char tmp_buf[256];
273 size_t len = 0;
274
275 for (int i = 0; i < array_len; i++)
276 {
277 const char *ptr = str_array[i];
278
279 /* Each entry consumes strlen(ptr) + 2 bytes: one leading label length
280 * byte and one trailing NUL. */
281 if (strlen(ptr) + len + 2 > sizeof(tmp_buf))
282 {
283 *error = true;
284 msg(M_WARN, "write_dhcp_search_str: temp buffer overflow building DHCP options");
285 return;
286 }
287 /* Loop over all subdomains separated by a dot and replace the dot
288 * with the length of the subdomain */
289
290 /* label_length_pos points to the byte to be replaced by the length
291 * of the following domain label */
292 size_t label_length_pos = len++;
293
294 while (true)
295 {
296 if (*ptr == '.' || *ptr == '\0')
297 {
298 /* cast is protected by sizeof(tmp_buf) */
299 tmp_buf[label_length_pos] = (char)(len - label_length_pos - 1);
300 label_length_pos = len;
301 if (*ptr == '\0')
302 {
303 break;
304 }
305 }
306 tmp_buf[len++] = *ptr++;
307 }
308 /* And close off with an extra NUL char */
309 tmp_buf[len++] = 0;
310 }
311
312 if (!buf_safe(buf, 2 + len))
313 {
314 *error = true;
315 msg(M_WARN, "write_search_dhcp_str: buffer overflow building DHCP options");
316 return;
317 }
318 if (len > 255)
319 {
320 *error = true;
321 msg(M_WARN, "write_dhcp_search_str: search domain string must be <= 255 bytes");
322 return;
323 }
324
325 buf_write_u8(buf, type);
326 buf_write_u8(buf, (uint8_t)len);
327 buf_write(buf, tmp_buf, len);
328}
329
330bool
332{
333 bool error = false;
334 if (o->domain)
335 {
336 write_dhcp_str(buf, DHCP_DOMAIN_NAME, o->domain, &error);
337 }
338
339 if (o->netbios_scope)
340 {
342 }
343
344 if (o->netbios_node_type)
345 {
347 }
348
349 write_dhcp_u32_array(buf, DHCP_DOMAIN_SERVER, (uint32_t *)o->dns, o->dns_len, &error);
350 write_dhcp_u32_array(buf, DHCP_NETBIOS_DOMAIN_SERVER, (uint32_t *)o->wins, o->wins_len, &error);
351 write_dhcp_u32_array(buf, DHCP_NTP_SERVER, (uint32_t *)o->ntp, o->ntp_len, &error);
352 write_dhcp_u32_array(buf, DHCP_NETBIOS_DIST_SERVER, (uint32_t *)o->nbdd, o->nbdd_len, &error);
353
354 if (o->domain_search_list_len > 0)
355 {
357 }
358
359 /* the MS DHCP server option 'Disable Netbios-over-TCP/IP
360 * is implemented as vendor option 001, value 002.
361 * A value of 001 means 'leave NBT alone' which is the default */
362 if (o->disable_nbt)
363 {
364 if (!buf_safe(buf, 8))
365 {
366 msg(M_WARN, "build_dhcp_options_string: buffer overflow building DHCP options");
367 return false;
368 }
370 buf_write_u8(buf, 6); /* total length field */
371 buf_write_u8(buf, 0x001);
372 buf_write_u8(buf, 4); /* length of the vendor specified field */
373 buf_write_u32(buf, 0x002);
374 }
375 return !error;
376}
377
378#endif /* defined(_WIN32) */
#define BPTR(buf)
Return a pointer to the start of the buffer content.
Definition buffer.h:139
static bool buf_write_u32(struct buffer *dest, uint32_t data)
Append a uint32_t to a buffer in network byte order.
Definition buffer.h:1335
static bool buf_safe(const struct buffer *buf, size_t len)
Check whether len bytes can be appended to a buffer.
Definition buffer.h:1017
static bool buf_write(struct buffer *dest, const void *src, size_t size)
Append data to a buffer.
Definition buffer.h:1262
static bool buf_write_u8(struct buffer *dest, uint8_t data)
Append a uint8_t to a buffer.
Definition buffer.h:1306
#define BLEN(buf)
Return the length of the buffer content in bytes.
Definition buffer.h:151
static void gc_free(struct gc_arena *a)
Free all allocations in a garbage collection arena.
Definition buffer.h:1976
static struct gc_arena gc_new(void)
Allocate and return a new, empty garbage collection arena.
Definition buffer.h:1960
in_addr_t dhcp_extract_router_msg(struct buffer *ipbuf)
Definition dhcp.c:148
static in_addr_t do_extract(struct dhcp *dhcp, int optlen)
Definition dhcp.c:76
static void write_dhcp_search_str(struct buffer *buf, const uint8_t type, const char *const *str_array, int array_len, bool *error)
Definition dhcp.c:269
static void write_dhcp_u8(struct buffer *buf, const uint8_t type, const uint8_t data, bool *error)
Definition dhcp.c:197
static void write_dhcp_u32_array(struct buffer *buf, const uint8_t type, const uint32_t *data, const unsigned int len, bool *error)
Definition dhcp.c:211
static int get_dhcp_message_type(const struct dhcp *dhcp, const int optlen)
Definition dhcp.c:36
static void write_dhcp_str(struct buffer *buf, const uint8_t type, const char *str, bool *error)
Definition dhcp.c:240
bool build_dhcp_options_string(struct buffer *buf, const struct tuntap_options *o)
Definition dhcp.c:331
#define DHCP_NETBIOS_DOMAIN_SERVER
Definition dhcp.h:39
#define DHCP_DOMAIN_NAME
Definition dhcp.h:36
#define DHCP_PAD
Definition dhcp.h:33
#define DHCP_ROUTER
Definition dhcp.h:34
#define DHCP_MSG_TYPE
Definition dhcp.h:43
#define DHCPACK
Definition dhcp.h:52
#define BOOTPS_PORT
Definition dhcp.h:58
#define BOOTPC_PORT
Definition dhcp.h:59
#define BOOTREPLY
Definition dhcp.h:64
#define DHCP_NETBIOS_DIST_SERVER
Definition dhcp.h:40
#define DHCPOFFER
Definition dhcp.h:49
#define DHCP_END
Definition dhcp.h:45
#define DHCP_VENDOR
Definition dhcp.h:38
#define DHCP_NTP_SERVER
Definition dhcp.h:37
#define DHCP_NETBIOS_SCOPE
Definition dhcp.h:42
#define DHCP_DOMAIN_SEARCH
Definition dhcp.h:44
#define DHCP_NETBIOS_NODE_TYPE
Definition dhcp.h:41
#define DHCP_DOMAIN_SERVER
Definition dhcp.h:35
#define D_ROUTE
Definition errlevel.h:79
#define msg(flags,...)
Definition error.h:152
#define M_WARN
Definition error.h:92
uint16_t ip_checksum(const sa_family_t af, const uint8_t *payload, const int len_payload, const uint8_t *src_addr, const uint8_t *dest_addr, const int proto)
Calculates an IP or IPv6 checksum with a pseudo header as required by TCP, UDP and ICMPv6.
Definition proto.c:119
#define OPENVPN_IPPROTO_UDP
Definition proto.h:106
const char * print_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc)
Wrapper structure for dynamically allocated memory.
Definition buffer.h:71
Definition dhcp.h:62
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:127
uint16_t len
Definition proto.h:158
in_addr_t nbdd[N_DHCP_ADDR]
Definition tun.h:123
in_addr_t ntp[N_DHCP_ADDR]
Definition tun.h:119
unsigned int ntp_len
Definition tun.h:120
in_addr_t wins[N_DHCP_ADDR]
Definition tun.h:115
uint8_t netbios_node_type
Definition tun.h:105
unsigned int dns_len
Definition tun.h:112
in_addr_t dns[N_DHCP_ADDR]
Definition tun.h:111
const char * netbios_scope
Definition tun.h:103
unsigned int domain_search_list_len
Definition tun.h:130
const char * domain
Definition tun.h:101
const char * domain_search_list[N_SEARCH_LIST_LEN]
Definition tun.h:129
unsigned int nbdd_len
Definition tun.h:124
bool disable_nbt
Definition tun.h:133
unsigned int wins_len
Definition tun.h:116
uint32_t in_addr_t
Definition syshead.h:52
struct gc_arena gc
Definition test_ssl.c:122