Coverage Report

Created: 2025-08-26 06:34

/src/tinyusb/lib/networking/dhserver.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * The MIT License (MIT)
3
 *
4
 * Copyright (c) 2015 by Sergey Fetisov <fsenok@gmail.com>
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
#include "dhserver.h"
26
27
/* DHCP message type */
28
0
#define DHCP_DISCOVER       1
29
0
#define DHCP_OFFER          2
30
0
#define DHCP_REQUEST        3
31
#define DHCP_DECLINE        4
32
0
#define DHCP_ACK            5
33
#define DHCP_NAK            6
34
#define DHCP_RELEASE        7
35
#define DHCP_INFORM         8
36
37
/* DHCP options */
38
enum DHCP_OPTIONS
39
{
40
  DHCP_PAD                    = 0,
41
  DHCP_SUBNETMASK             = 1,
42
  DHCP_ROUTER                 = 3,
43
  DHCP_DNSSERVER              = 6,
44
  DHCP_HOSTNAME               = 12,
45
  DHCP_DNSDOMAIN              = 15,
46
  DHCP_MTU                    = 26,
47
  DHCP_BROADCAST              = 28,
48
  DHCP_PERFORMROUTERDISC      = 31,
49
  DHCP_STATICROUTE            = 33,
50
  DHCP_NISDOMAIN              = 40,
51
  DHCP_NISSERVER              = 41,
52
  DHCP_NTPSERVER              = 42,
53
  DHCP_VENDOR                 = 43,
54
  DHCP_IPADDRESS              = 50,
55
  DHCP_LEASETIME              = 51,
56
  DHCP_OPTIONSOVERLOADED      = 52,
57
  DHCP_MESSAGETYPE            = 53,
58
  DHCP_SERVERID               = 54,
59
  DHCP_PARAMETERREQUESTLIST   = 55,
60
  DHCP_MESSAGE                = 56,
61
  DHCP_MAXMESSAGESIZE         = 57,
62
  DHCP_RENEWALTIME            = 58,
63
  DHCP_REBINDTIME             = 59,
64
  DHCP_CLASSID                = 60,
65
  DHCP_CLIENTID               = 61,
66
  DHCP_USERCLASS              = 77,  /* RFC 3004 */
67
  DHCP_FQDN                   = 81,
68
  DHCP_DNSSEARCH              = 119, /* RFC 3397 */
69
  DHCP_CSR                    = 121, /* RFC 3442 */
70
  DHCP_MSCSR                  = 249, /* MS code for RFC 3442 */
71
  DHCP_END                    = 255
72
};
73
74
typedef struct
75
{
76
    uint8_t  dp_op;           /* packet opcode type */
77
    uint8_t  dp_htype;        /* hardware addr type */
78
    uint8_t  dp_hlen;         /* hardware addr length */
79
    uint8_t  dp_hops;         /* gateway hops */
80
    uint32_t dp_xid;          /* transaction ID */
81
    uint16_t dp_secs;         /* seconds since boot began */
82
    uint16_t dp_flags;
83
    uint8_t  dp_ciaddr[4];    /* client IP address */
84
    uint8_t  dp_yiaddr[4];    /* 'your' IP address */
85
    uint8_t  dp_siaddr[4];    /* server IP address */
86
    uint8_t  dp_giaddr[4];    /* gateway IP address */
87
    uint8_t  dp_chaddr[16];   /* client hardware address */
88
    uint8_t  dp_legacy[192];
89
    uint8_t  dp_magic[4];
90
    uint8_t  dp_options[275]; /* options area */
91
} DHCP_TYPE;
92
93
DHCP_TYPE dhcp_data;
94
static struct udp_pcb *pcb = NULL;
95
static const dhcp_config_t *config = NULL;
96
97
char magic_cookie[] = {0x63,0x82,0x53,0x63};
98
99
static ip4_addr_t get_ip(const uint8_t *pnt)
100
0
{
101
0
  ip4_addr_t result;
102
0
  memcpy(&result, pnt, sizeof(result));
103
0
  return result;
104
0
}
105
106
static void set_ip(uint8_t *pnt, ip4_addr_t value)
107
0
{
108
0
  memcpy(pnt, &value.addr, sizeof(value.addr));
109
0
}
110
111
static dhcp_entry_t *entry_by_ip(ip4_addr_t ip)
112
0
{
113
0
  int i;
114
0
  for (i = 0; i < config->num_entry; i++)
115
0
    if (config->entries[i].addr.addr == ip.addr)
116
0
      return &config->entries[i];
117
0
  return NULL;
118
0
}
119
120
static dhcp_entry_t *entry_by_mac(uint8_t *mac)
121
0
{
122
0
  int i;
123
0
  for (i = 0; i < config->num_entry; i++)
124
0
    if (memcmp(config->entries[i].mac, mac, 6) == 0)
125
0
      return &config->entries[i];
126
0
  return NULL;
127
0
}
128
129
static __inline bool is_vacant(dhcp_entry_t *entry)
130
0
{
131
0
  return memcmp("\0\0\0\0\0", entry->mac, 6) == 0;
132
0
}
133
134
static dhcp_entry_t *vacant_address(void)
135
0
{
136
0
  int i;
137
0
  for (i = 0; i < config->num_entry; i++)
138
0
    if (is_vacant(config->entries + i))
139
0
      return config->entries + i;
140
0
  return NULL;
141
0
}
142
143
static __inline void free_entry(dhcp_entry_t *entry)
144
0
{
145
0
  memset(entry->mac, 0, 6);
146
0
}
147
148
uint8_t *find_dhcp_option(uint8_t *attrs, int size, uint8_t attr)
149
0
{
150
0
  int i = 0;
151
0
  while ((i + 1) < size)
152
0
  {
153
0
    int next = i + attrs[i + 1] + 2;
154
0
    if (next > size) return NULL;
155
0
    if (attrs[i] == attr)
156
0
      return attrs + i;
157
0
    i = next;
158
0
  }
159
0
  return NULL;
160
0
}
161
162
int fill_options(void *dest,
163
  uint8_t msg_type,
164
  const char *domain,
165
  ip4_addr_t dns,
166
  int lease_time,
167
  ip4_addr_t serverid,
168
  ip4_addr_t router,
169
  ip4_addr_t subnet)
170
0
{
171
0
  uint8_t *ptr = (uint8_t *)dest;
172
  /* ACK message type */
173
0
  *ptr++ = 53;
174
0
  *ptr++ = 1;
175
0
  *ptr++ = msg_type;
176
177
  /* dhcp server identifier */
178
0
  *ptr++ = DHCP_SERVERID;
179
0
  *ptr++ = 4;
180
0
  set_ip(ptr, serverid);
181
0
  ptr += 4;
182
183
  /* lease time */
184
0
  *ptr++ = DHCP_LEASETIME;
185
0
  *ptr++ = 4;
186
0
  *ptr++ = (lease_time >> 24) & 0xFF;
187
0
  *ptr++ = (lease_time >> 16) & 0xFF;
188
0
  *ptr++ = (lease_time >> 8) & 0xFF;
189
0
  *ptr++ = (lease_time >> 0) & 0xFF;
190
191
  /* subnet mask */
192
0
  *ptr++ = DHCP_SUBNETMASK;
193
0
  *ptr++ = 4;
194
0
  set_ip(ptr, subnet);
195
0
  ptr += 4;
196
197
  /* router */
198
0
  if (router.addr != 0)
199
0
  {
200
0
    *ptr++ = DHCP_ROUTER;
201
0
    *ptr++ = 4;
202
0
    set_ip(ptr, router);
203
0
    ptr += 4;
204
0
  }
205
206
  /* domain name */
207
0
  if (domain != NULL)
208
0
  {
209
0
    int len = strlen(domain);
210
0
    *ptr++ = DHCP_DNSDOMAIN;
211
0
    *ptr++ = len;
212
0
    memcpy(ptr, domain, len);
213
0
    ptr += len;
214
0
  }
215
216
  /* domain name server (DNS) */
217
0
  if (dns.addr != 0)
218
0
  {
219
0
    *ptr++ = DHCP_DNSSERVER;
220
0
    *ptr++ = 4;
221
0
    set_ip(ptr, dns);
222
0
    ptr += 4;
223
0
  }
224
225
  /* end */
226
0
  *ptr++ = DHCP_END;
227
0
  return ptr - (uint8_t *)dest;
228
0
}
229
230
231
/*
232
 * RFC 2131 Section 4.1 compliant destination address selection
233
 */
234
static ip_addr_t get_dhcp_destination(struct netif *netif, const DHCP_TYPE *dhcp,
235
                                const ip4_addr_t *yiaddr, bool is_nak)
236
0
{
237
0
    ip4_addr_t giaddr = get_ip(dhcp->dp_giaddr);
238
0
    ip4_addr_t ciaddr = get_ip(dhcp->dp_ciaddr);
239
0
    bool giaddr_zero = ip4_addr_isany_val(giaddr);
240
0
    bool ciaddr_zero = ip4_addr_isany_val(ciaddr);
241
0
    bool broadcast_flag = (dhcp->dp_flags & htons(0x8000)) != 0;
242
0
  ip_addr_t dest_addr;
243
244
0
    if (!giaddr_zero) {
245
        // If giaddr is not zero, send to giaddr (relay agent)
246
0
        ip_addr_set_ip4_u32(&dest_addr, giaddr.addr);
247
0
        return dest_addr;
248
0
    }
249
250
0
    if (is_nak) {
251
        // RFC 2131: "In all cases, when 'giaddr' is zero,
252
        // the server broadcasts any DHCPNAK messages to 0xffffffff"
253
0
        goto dest_broadcast;
254
0
    }
255
256
0
    if (!ciaddr_zero) {
257
        // RFC 2131: "If the 'giaddr' field is zero and the 'ciaddr' field is nonzero,
258
        // then the server unicasts DHCPOFFER and DHCPACK messages to the address in 'ciaddr'"
259
0
        ip_addr_set_ip4_u32(&dest_addr, ciaddr.addr);
260
0
        return dest_addr;
261
0
    }
262
263
0
    if (broadcast_flag) {
264
        // RFC 2131: "If 'giaddr' is zero and 'ciaddr' is zero, and the broadcast bit is set,
265
        // then the server broadcasts DHCPOFFER and DHCPACK messages to 0xffffffff"
266
0
        goto dest_broadcast;
267
0
    }
268
269
    // RFC 2131: "If the broadcast bit is not set and 'giaddr' is zero and 'ciaddr' is zero,
270
    // then the server unicasts DHCPOFFER and DHCPACK messages to the client's hardware
271
    // address and 'yiaddr' address"
272
0
    if (yiaddr && !ip4_addr_isany(yiaddr)) {
273
0
        ip_addr_set_ip4_u32(&dest_addr, yiaddr->addr);
274
        // TODO: This requires ARP table manipulation to associate yiaddr with client MAC
275
        // For now, fall back to broadcast as this is complex to implement correctly
276
0
        goto dest_broadcast;
277
0
    }
278
279
0
dest_broadcast:
280
0
    ip_addr_set_ip4_u32(&dest_addr,
281
0
        ip4_addr_get_u32(netif_ip4_addr(netif)) | ~ip4_addr_get_u32(netif_ip4_netmask(netif)));
282
0
    return dest_addr;
283
284
0
}
285
286
static void udp_recv_proc(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
287
0
{
288
0
  uint8_t *ptr;
289
0
  dhcp_entry_t *entry;
290
0
  struct pbuf *pp;
291
0
  struct netif *netif = netif_get_by_index(p->if_idx);
292
0
  ip_addr_t dest_addr;
293
294
0
  (void)arg;
295
0
  (void)addr;
296
297
0
  unsigned n = p->len;
298
0
  if (n > sizeof(dhcp_data)) n = sizeof(dhcp_data);
299
0
  memcpy(&dhcp_data, p->payload, n);
300
301
0
  ptr = find_dhcp_option(dhcp_data.dp_options, sizeof(dhcp_data.dp_options), DHCP_MESSAGETYPE);
302
0
  if (ptr == NULL)
303
0
  {
304
0
    pbuf_free(p);
305
0
    return;
306
0
  }
307
308
0
  switch (ptr[2])
309
0
  {
310
0
    case DHCP_DISCOVER:
311
0
      entry = entry_by_mac(dhcp_data.dp_chaddr);
312
0
      if (entry == NULL) entry = vacant_address();
313
0
      if (entry == NULL) break;
314
0
      dhcp_data.dp_op = 2; /* reply */
315
0
      dhcp_data.dp_secs = 0;
316
0
      dhcp_data.dp_flags = 0;
317
0
      set_ip(dhcp_data.dp_yiaddr, entry->addr);
318
0
      memcpy(dhcp_data.dp_magic, magic_cookie, 4);
319
320
0
      memset(dhcp_data.dp_options, 0, sizeof(dhcp_data.dp_options));
321
322
0
      fill_options(dhcp_data.dp_options,
323
0
        DHCP_OFFER,
324
0
        config->domain,
325
0
        config->dns,
326
0
        entry->lease,
327
0
        *netif_ip4_addr(netif),
328
0
        config->router,
329
0
        *netif_ip4_netmask(netif));
330
331
0
      pp = pbuf_alloc(PBUF_TRANSPORT, sizeof(dhcp_data), PBUF_POOL);
332
0
      if (pp == NULL) break;
333
0
      memcpy(pp->payload, &dhcp_data, sizeof(dhcp_data));
334
      // RFC 2131 compliant destination selection for DHCP OFFER
335
0
      dest_addr = get_dhcp_destination(netif, &dhcp_data, &entry->addr, false);
336
0
      udp_sendto(upcb, pp, &dest_addr, port);
337
0
      pbuf_free(pp);
338
0
      break;
339
340
0
    case DHCP_REQUEST:
341
      /* 1. find requested ipaddr in option list */
342
0
      ptr = find_dhcp_option(dhcp_data.dp_options, sizeof(dhcp_data.dp_options), DHCP_IPADDRESS);
343
0
      if (ptr == NULL) break;
344
0
      if (ptr[1] != 4) break;
345
0
      ptr += 2;
346
347
      /* 2. does hw-address registered? */
348
0
      entry = entry_by_mac(dhcp_data.dp_chaddr);
349
0
      if (entry != NULL) free_entry(entry);
350
351
      /* 3. find requested ipaddr */
352
0
      entry = entry_by_ip(get_ip(ptr));
353
0
      if (entry == NULL) break;
354
0
      if (!is_vacant(entry)) break;
355
356
      /* 4. fill struct fields */
357
0
      memcpy(dhcp_data.dp_yiaddr, ptr, 4);
358
0
      dhcp_data.dp_op = 2; /* reply */
359
0
      dhcp_data.dp_secs = 0;
360
0
      dhcp_data.dp_flags = 0;
361
0
      memcpy(dhcp_data.dp_magic, magic_cookie, 4);
362
363
      /* 5. fill options */
364
0
      memset(dhcp_data.dp_options, 0, sizeof(dhcp_data.dp_options));
365
366
0
      fill_options(dhcp_data.dp_options,
367
0
        DHCP_ACK,
368
0
        config->domain,
369
0
        config->dns,
370
0
        entry->lease,
371
0
        *netif_ip4_addr(netif),
372
0
        config->router,
373
0
        *netif_ip4_netmask(netif));
374
375
      /* 6. send ACK */
376
0
      pp = pbuf_alloc(PBUF_TRANSPORT, sizeof(dhcp_data), PBUF_POOL);
377
0
      if (pp == NULL) break;
378
0
      memcpy(entry->mac, dhcp_data.dp_chaddr, 6);
379
0
      memcpy(pp->payload, &dhcp_data, sizeof(dhcp_data));
380
      // RFC 2131 compliant destination selection for DHCP ACK
381
0
      dest_addr = get_dhcp_destination(netif, &dhcp_data, &entry->addr, false);
382
0
      udp_sendto(upcb, pp, &dest_addr, port);
383
0
      pbuf_free(pp);
384
0
      break;
385
386
0
    default:
387
0
        break;
388
0
  }
389
0
  pbuf_free(p);
390
0
}
391
392
err_t dhserv_init(const dhcp_config_t *c)
393
0
{
394
0
  err_t err;
395
0
  udp_init();
396
0
  dhserv_free();
397
0
  pcb = udp_new();
398
0
  if (pcb == NULL)
399
0
    return ERR_MEM;
400
0
  err = udp_bind(pcb, IP_ADDR_ANY, c->port);
401
0
  if (err != ERR_OK)
402
0
  {
403
0
    dhserv_free();
404
0
    return err;
405
0
  }
406
0
  udp_recv(pcb, udp_recv_proc, NULL);
407
0
  config = c;
408
0
  return ERR_OK;
409
0
}
410
411
void dhserv_free(void)
412
0
{
413
0
  if (pcb == NULL) return;
414
0
  udp_remove(pcb);
415
0
  pcb = NULL;
416
0
}