1 /*
2 * Copyright (c) 2014 Brian Swetland
3 * Copyright (c) 2021 Travis Geiselbrecht
4 *
5 * Use of this source code is governed by a MIT-style
6 * license that can be found in the LICENSE file or at
7 * https://opensource.org/licenses/MIT
8 */
9
10 #include "minip-internal.h"
11
12 #include <endian.h>
13 #include <kernel/mutex.h>
14 #include <kernel/thread.h>
15 #include <lk/debug.h>
16 #include <lk/err.h>
17 #include <lk/trace.h>
18 #include <malloc.h>
19 #include <platform.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/types.h>
24
25 #define TRACE_DHCP 0
26
27 namespace {
28
29 typedef struct dhcp_msg {
30 u8 opcode;
31 u8 hwtype; // hw addr type
32 u8 hwalen; // hw addr length
33 u8 hops;
34 u32 xid; // txn id
35 u16 secs; // seconds since dhcp process start
36 u16 flags;
37 u32 ciaddr; // Client IP Address
38 u32 yiaddr; // Your IP Address
39 u32 siaddr; // Server IP Address
40 u32 giaddr; // Gateway IP Address
41 u8 chaddr[16]; // Client HW Address
42 u8 sname[64]; // Server Hostname, AsciiZ
43 u8 file[128]; // Boot File Name, AsciiZ
44 u32 cookie;
45 u8 options[0];
46 } dhcp_msg_t;
47
48 #define DHCP_FLAG_BROADCAST 0x8000
49
50 #define DHCP_REQUEST 1
51 #define DHCP_REPLY 2
52
53 #define OP_DHCPDISCOVER 1 // Client: Broadcast to find Server
54 #define OP_DHCPOFFER 2 // Server response to Discover
55 #define OP_DHCPREQUEST 3 // Client accepts offer
56 #define OP_DHCPDECLINE 4 // Client notifies address already in use
57 #define OP_DHCPACK 5 // Server confirms accept
58 #define OP_DHCPNAK 6 // Server disconfirms or lease expires
59 #define OP_DHCPRELEASE 7 // Client releases address
60
61 #define OPT_NET_MASK 1 // len 4, mask
62 #define OPT_ROUTERS 3 // len 4n, gateway0, ...
63 #define OPT_DNS 6 // len 4n, nameserver0, ...
64 #define OPT_HOSTNAME 12
65 #define OPT_REQUEST_IP 50 // len 4
66 #define OPT_MSG_TYPE 53 // len 1, type same as op
67 #define OPT_SERVER_ID 54 // len 4, server ident ipaddr
68 #define OPT_DONE 255
69
70 #define DHCP_CLIENT_PORT 68
71 #define DHCP_SERVER_PORT 67
72
73 #define HW_ETHERNET 1
74
75 class dhcp {
76 public:
77 dhcp() = default;
78 ~dhcp() = default;
79
80 status_t start();
81
82 private:
83 // must call the following two with the lock held
84 status_t send_discover();
85 status_t send_request(u32 server, u32 reqip);
86
87 void udp_callback(void *data, size_t sz, uint32_t srcip, uint16_t srcport);
88 static void dhcp_cb(void *data, size_t sz, uint32_t srcip, uint16_t srcport, void *arg);
89
90 static int dhcp_thread(void *arg);
91
92
93 Mutex lock_;
94 udp_socket_t *dhcp_udp_handle_ = nullptr;
95 thread_t *dhcp_thr_ = nullptr;
96
97 volatile bool configured_ = false;
98 uint32_t xid_ = rand();
99 enum {
100 INITIAL = 0,
101 DISCOVER_SENT,
102 RECV_OFFER,
103 REQUEST_SENT,
104 CONFIGURED,
105 } state_ = INITIAL;
106 };
107
send_discover()108 status_t dhcp::send_discover() {
109 DEBUG_ASSERT(lock_.is_held());
110
111 struct {
112 dhcp_msg_t msg;
113 u8 opt[128];
114 } s = {};
115 u8 *opt = s.opt;
116 s.msg.opcode = DHCP_REQUEST;
117 s.msg.hwtype = HW_ETHERNET;
118 s.msg.hwalen = 6;
119 s.msg.xid = xid_;
120 s.msg.cookie = 0x63538263;
121 minip_get_macaddr(s.msg.chaddr);
122
123 *opt++ = OPT_MSG_TYPE;
124 *opt++ = 1;
125 *opt++ = OP_DHCPDISCOVER;
126
127 const char *hostname = minip_get_hostname();
128 if (hostname && hostname[0]) {
129 size_t len = strlen(hostname);
130 *opt++ = OPT_HOSTNAME;
131 *opt++ = len;
132 memcpy(opt, hostname, len);
133 opt += len;
134 }
135
136 *opt++ = OPT_DONE;
137
138 #if TRACE_DHCP
139 printf("sending dhcp discover\n");
140 #endif
141 state_ = DISCOVER_SENT;
142 status_t ret = udp_send(&s.msg, sizeof(dhcp_msg_t) + (opt - s.opt), dhcp_udp_handle_);
143 if (ret != NO_ERROR) {
144 printf("DHCP_DISCOVER failed: %d\n", ret);
145 }
146
147 return ret;
148 }
149
send_request(u32 server,u32 reqip)150 status_t dhcp::send_request(u32 server, u32 reqip) {
151 DEBUG_ASSERT(lock_.is_held());
152
153 struct {
154 dhcp_msg_t msg;
155 u8 opt[128];
156 } s = {};
157 u8 *opt = s.opt;
158 s.msg.opcode = DHCP_REQUEST;
159 s.msg.hwtype = HW_ETHERNET;
160 s.msg.hwalen = 6;
161 s.msg.xid = xid_;
162 s.msg.cookie = 0x63538263;
163 minip_get_macaddr(s.msg.chaddr);
164
165 *opt++ = OPT_MSG_TYPE;
166 *opt++ = 1;
167 *opt++ = OP_DHCPREQUEST;
168
169 *opt++ = OPT_SERVER_ID;
170 *opt++ = 4;
171 memcpy(opt, &server, 4);
172 opt += 4;
173
174 *opt++ = OPT_REQUEST_IP;
175 *opt++ = 4;
176 memcpy(opt, &reqip, 4);
177 opt += 4;
178
179 const char *hostname = minip_get_hostname();
180 if (hostname && hostname[0]) {
181 size_t len = strlen(hostname);
182 *opt++ = OPT_HOSTNAME;
183 *opt++ = len;
184 memcpy(opt, hostname, len);
185 opt += len;
186 }
187
188 *opt++ = OPT_DONE;
189
190 #if TRACE_DHCP
191 printf("sending dhcp request\n");
192 #endif
193 state_ = REQUEST_SENT;
194 status_t ret = udp_send(&s.msg, sizeof(dhcp_msg_t) + (opt - s.opt), dhcp_udp_handle_);
195 if (ret != NO_ERROR) {
196 printf("DHCP_REQUEST failed: %d\n", ret);
197 }
198
199 return ret;
200 }
201
dhcp_cb(void * data,size_t sz,uint32_t srcip,uint16_t srcport,void * arg)202 void dhcp::dhcp_cb(void *data, size_t sz, uint32_t srcip, uint16_t srcport, void *arg) {
203 dhcp *d = (dhcp *)arg;
204
205 d->udp_callback(data, sz, srcip, srcport);
206 }
207
udp_callback(void * data,size_t sz,uint32_t srcip,uint16_t srcport)208 void dhcp::udp_callback(void *data, size_t sz, uint32_t srcip, uint16_t srcport) {
209 const dhcp_msg_t *msg = (dhcp_msg_t *)data;
210 const u8 *opt;
211 u32 netmask = 0;
212 u32 gateway = 0;
213 u32 dns = 0;
214 u32 server = 0;
215 int op = -1;
216
217 AutoLock guard(lock_);
218
219 // lossy testing for state machine transitions
220 if (false) {
221 int r = rand();
222 if (r % 3) {
223 printf("dropping packet for testing r %#x\n", r);
224 return;
225 }
226 }
227
228 if (sz < sizeof(dhcp_msg_t)) return;
229
230 uint8_t mac[6];
231 minip_get_macaddr(mac);
232 if (memcmp(msg->chaddr, mac, 6)) return;
233
234 #if TRACE_DHCP
235 printf("received DHCP op %d, len %zu, from p %d, ip=", msg->opcode, sz, srcport);
236 printip(srcip);
237 printf("\n");
238 #endif
239
240 if (configured_) {
241 printf("already configured\n");
242 return;
243 }
244 #if TRACE_DHCP
245 printip_named("\tciaddr", msg->ciaddr);
246 printip_named(" yiaddr", msg->yiaddr);
247 printip_named(" siaddr", msg->siaddr);
248 printip_named(" giaddr", msg->giaddr);
249 printf(" chaddr %02x:%02x:%02x:%02x:%02x:%02x\n",
250 msg->chaddr[0], msg->chaddr[1], msg->chaddr[2],
251 msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
252 printf("\toptions: ");
253 #endif
254 sz -= sizeof(dhcp_msg_t);
255 opt = msg->options;
256 #if TRACE_DHCP
257 printf("\toptions: ");
258 #endif
259 while (sz >= 2) {
260 sz -= 2;
261 if (opt[1] > sz) {
262 break;
263 }
264 #if TRACE_DHCP
265 printf("#%d (%d), ", opt[0], opt[1]);
266 #endif
267 switch (opt[0]) {
268 case OPT_MSG_TYPE:
269 if (opt[1] == 1) op = opt[2];
270 break;
271 case OPT_NET_MASK:
272 if (opt[1] == 4) memcpy(&netmask, opt + 2, 4);
273 break;
274 case OPT_ROUTERS:
275 if (opt[1] >= 4) memcpy(&gateway, opt + 2, 4);
276 break;
277 case OPT_DNS:
278 if (opt[1] >= 4) memcpy(&dns, opt + 2, 4);
279 break;
280 case OPT_SERVER_ID:
281 if (opt[1] == 4) memcpy(&server, opt + 2, 4);
282 break;
283 case OPT_DONE:
284 goto done;
285 }
286 opt += opt[1] + 2;
287 sz -= opt[1];
288 }
289 done:
290 #if TRACE_DHCP
291 printf("\n\t");
292 if (server) printip_named("server", server);
293 if (netmask) printip_named(" netmask", netmask);
294 if (gateway) printip_named(" gateway", gateway);
295 if (dns) printip_named(" dns", dns);
296 printf("\n");
297 #endif
298 if (state_ == DISCOVER_SENT) {
299 if (op == OP_DHCPOFFER) {
300 #if TRACE_DHCP
301 printip_named("dhcp: offer:", msg->yiaddr);
302 printf("\n");
303 #endif
304 if (server) {
305 state_ = RECV_OFFER;
306 send_request(server, msg->yiaddr);
307 }
308 }
309 } else if (state_ == REQUEST_SENT) {
310 if (op == OP_DHCPACK) {
311 #if TRACE_DHCP
312 printip_named("dhcp: ack:", msg->yiaddr);
313 printf("\n");
314 #endif
315 printf("DHCP configured\n");
316 minip_set_ipaddr(msg->yiaddr);
317 if (netmask) {
318 minip_set_netmask(netmask);
319 }
320 if (gateway) {
321 minip_set_gateway(gateway);
322 }
323 state_ = CONFIGURED;
324 configured_ = true;
325
326 // signal that minip is ready to be used
327 minip_set_configured();
328 }
329 }
330 }
331
dhcp_thread(void * arg)332 int dhcp::dhcp_thread(void *arg) {
333 dhcp *d = (dhcp *)arg;
334
335 auto worker = [&]() {
336 while (!d->configured_) {
337 {
338 AutoLock guard(d->lock_);
339 switch (d->state_) {
340 case INITIAL:
341 case DISCOVER_SENT:
342 // for these two states, start off by sending a discover packet
343 d->send_discover();
344 break;
345 case REQUEST_SENT:
346 // if we're still in this state after some period of time,
347 // switch back to the INITIAL state and start over.
348 d->state_ = INITIAL;
349 break;
350 default:
351 ;
352 }
353 }
354 thread_sleep(500);
355 }
356 };
357
358 worker();
359 return 0;
360 }
361
start()362 status_t dhcp::start() {
363 AutoLock guard(lock_);
364
365 int ret = udp_open(IPV4_BCAST, DHCP_CLIENT_PORT, DHCP_SERVER_PORT, &dhcp_udp_handle_);
366 if (ret != NO_ERROR) {
367 printf("DHCP: error opening udp socket\n");
368 return ret;
369 }
370
371 udp_listen(DHCP_CLIENT_PORT, dhcp::dhcp_cb, this);
372
373 dhcp_thr_ = thread_create("dhcp", dhcp::dhcp_thread, this, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
374 thread_detach_and_resume(dhcp_thr_);
375
376 return NO_ERROR;
377 }
378
379 } // anonymous namespace
380
minip_start_dhcp()381 void minip_start_dhcp() {
382 static dhcp d;
383 d.start();
384 }
385