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