diff options
Diffstat (limited to 'networking/udhcp/dhcpd.c')
-rw-r--r-- | networking/udhcp/dhcpd.c | 502 |
1 files changed, 496 insertions, 6 deletions
diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c index e93a9f1da..56116d01f 100644 --- a/networking/udhcp/dhcpd.c +++ b/networking/udhcp/dhcpd.c | |||
@@ -33,11 +33,507 @@ | |||
33 | //usage: "\n -P N Use port N (default 67)" | 33 | //usage: "\n -P N Use port N (default 67)" |
34 | //usage: ) | 34 | //usage: ) |
35 | 35 | ||
36 | #include <netinet/ether.h> | ||
36 | #include <syslog.h> | 37 | #include <syslog.h> |
37 | #include "common.h" | 38 | #include "common.h" |
38 | #include "dhcpc.h" | 39 | #include "dhcpc.h" |
39 | #include "dhcpd.h" | 40 | #include "dhcpd.h" |
40 | 41 | ||
42 | /* globals */ | ||
43 | struct dyn_lease *g_leases; | ||
44 | /* struct server_config_t server_config is in bb_common_bufsiz1 */ | ||
45 | |||
46 | /* Takes the address of the pointer to the static_leases linked list, | ||
47 | * address to a 6 byte mac address, | ||
48 | * 4 byte IP address */ | ||
49 | static void add_static_lease(struct static_lease **st_lease_pp, | ||
50 | uint8_t *mac, | ||
51 | uint32_t nip) | ||
52 | { | ||
53 | struct static_lease *st_lease; | ||
54 | |||
55 | /* Find the tail of the list */ | ||
56 | while ((st_lease = *st_lease_pp) != NULL) { | ||
57 | st_lease_pp = &st_lease->next; | ||
58 | } | ||
59 | |||
60 | /* Add new node */ | ||
61 | *st_lease_pp = st_lease = xzalloc(sizeof(*st_lease)); | ||
62 | memcpy(st_lease->mac, mac, 6); | ||
63 | st_lease->nip = nip; | ||
64 | /*st_lease->next = NULL;*/ | ||
65 | } | ||
66 | |||
67 | /* Find static lease IP by mac */ | ||
68 | static uint32_t get_static_nip_by_mac(struct static_lease *st_lease, void *mac) | ||
69 | { | ||
70 | while (st_lease) { | ||
71 | if (memcmp(st_lease->mac, mac, 6) == 0) | ||
72 | return st_lease->nip; | ||
73 | st_lease = st_lease->next; | ||
74 | } | ||
75 | |||
76 | return 0; | ||
77 | } | ||
78 | |||
79 | static int is_nip_reserved(struct static_lease *st_lease, uint32_t nip) | ||
80 | { | ||
81 | while (st_lease) { | ||
82 | if (st_lease->nip == nip) | ||
83 | return 1; | ||
84 | st_lease = st_lease->next; | ||
85 | } | ||
86 | |||
87 | return 0; | ||
88 | } | ||
89 | |||
90 | #if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2 | ||
91 | /* Print out static leases just to check what's going on */ | ||
92 | /* Takes the address of the pointer to the static_leases linked list */ | ||
93 | static void log_static_leases(struct static_lease **st_lease_pp) | ||
94 | { | ||
95 | struct static_lease *cur; | ||
96 | |||
97 | if (dhcp_verbose < 2) | ||
98 | return; | ||
99 | |||
100 | cur = *st_lease_pp; | ||
101 | while (cur) { | ||
102 | bb_error_msg("static lease: mac:%02x:%02x:%02x:%02x:%02x:%02x nip:%x", | ||
103 | cur->mac[0], cur->mac[1], cur->mac[2], | ||
104 | cur->mac[3], cur->mac[4], cur->mac[5], | ||
105 | cur->nip | ||
106 | ); | ||
107 | cur = cur->next; | ||
108 | } | ||
109 | } | ||
110 | #else | ||
111 | # define log_static_leases(st_lease_pp) ((void)0) | ||
112 | #endif | ||
113 | |||
114 | /* Find the oldest expired lease, NULL if there are no expired leases */ | ||
115 | static struct dyn_lease *oldest_expired_lease(void) | ||
116 | { | ||
117 | struct dyn_lease *oldest_lease = NULL; | ||
118 | leasetime_t oldest_time = time(NULL); | ||
119 | unsigned i; | ||
120 | |||
121 | /* Unexpired leases have g_leases[i].expires >= current time | ||
122 | * and therefore can't ever match */ | ||
123 | for (i = 0; i < server_config.max_leases; i++) { | ||
124 | if (g_leases[i].expires == 0 /* empty entry */ | ||
125 | || g_leases[i].expires < oldest_time | ||
126 | ) { | ||
127 | oldest_time = g_leases[i].expires; | ||
128 | oldest_lease = &g_leases[i]; | ||
129 | } | ||
130 | } | ||
131 | return oldest_lease; | ||
132 | } | ||
133 | |||
134 | /* Clear out all leases with matching nonzero chaddr OR yiaddr. | ||
135 | * If chaddr == NULL, this is a conflict lease. | ||
136 | */ | ||
137 | static void clear_leases(const uint8_t *chaddr, uint32_t yiaddr) | ||
138 | { | ||
139 | unsigned i; | ||
140 | |||
141 | for (i = 0; i < server_config.max_leases; i++) { | ||
142 | if ((chaddr && memcmp(g_leases[i].lease_mac, chaddr, 6) == 0) | ||
143 | || (yiaddr && g_leases[i].lease_nip == yiaddr) | ||
144 | ) { | ||
145 | memset(&g_leases[i], 0, sizeof(g_leases[i])); | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | |||
150 | /* Add a lease into the table, clearing out any old ones. | ||
151 | * If chaddr == NULL, this is a conflict lease. | ||
152 | */ | ||
153 | static struct dyn_lease *add_lease( | ||
154 | const uint8_t *chaddr, uint32_t yiaddr, | ||
155 | leasetime_t leasetime, | ||
156 | const char *hostname, int hostname_len) | ||
157 | { | ||
158 | struct dyn_lease *oldest; | ||
159 | |||
160 | /* clean out any old ones */ | ||
161 | clear_leases(chaddr, yiaddr); | ||
162 | |||
163 | oldest = oldest_expired_lease(); | ||
164 | |||
165 | if (oldest) { | ||
166 | memset(oldest, 0, sizeof(*oldest)); | ||
167 | if (hostname) { | ||
168 | char *p; | ||
169 | |||
170 | hostname_len++; /* include NUL */ | ||
171 | if (hostname_len > sizeof(oldest->hostname)) | ||
172 | hostname_len = sizeof(oldest->hostname); | ||
173 | p = safe_strncpy(oldest->hostname, hostname, hostname_len); | ||
174 | /* | ||
175 | * Sanitization (s/bad_char/./g). | ||
176 | * The intent is not to allow only "DNS-valid" hostnames, | ||
177 | * but merely make dumpleases output safe for shells to use. | ||
178 | * We accept "0-9A-Za-z._-", all other chars turn to dots. | ||
179 | */ | ||
180 | while (*p) { | ||
181 | if (!isalnum(*p) && *p != '-' && *p != '_') | ||
182 | *p = '.'; | ||
183 | p++; | ||
184 | } | ||
185 | } | ||
186 | if (chaddr) | ||
187 | memcpy(oldest->lease_mac, chaddr, 6); | ||
188 | oldest->lease_nip = yiaddr; | ||
189 | oldest->expires = time(NULL) + leasetime; | ||
190 | } | ||
191 | |||
192 | return oldest; | ||
193 | } | ||
194 | |||
195 | /* True if a lease has expired */ | ||
196 | static int is_expired_lease(struct dyn_lease *lease) | ||
197 | { | ||
198 | return (lease->expires < (leasetime_t) time(NULL)); | ||
199 | } | ||
200 | |||
201 | /* Find the first lease that matches MAC, NULL if no match */ | ||
202 | static struct dyn_lease *find_lease_by_mac(const uint8_t *mac) | ||
203 | { | ||
204 | unsigned i; | ||
205 | |||
206 | for (i = 0; i < server_config.max_leases; i++) | ||
207 | if (memcmp(g_leases[i].lease_mac, mac, 6) == 0) | ||
208 | return &g_leases[i]; | ||
209 | |||
210 | return NULL; | ||
211 | } | ||
212 | |||
213 | /* Find the first lease that matches IP, NULL is no match */ | ||
214 | static struct dyn_lease *find_lease_by_nip(uint32_t nip) | ||
215 | { | ||
216 | unsigned i; | ||
217 | |||
218 | for (i = 0; i < server_config.max_leases; i++) | ||
219 | if (g_leases[i].lease_nip == nip) | ||
220 | return &g_leases[i]; | ||
221 | |||
222 | return NULL; | ||
223 | } | ||
224 | |||
225 | /* Check if the IP is taken; if it is, add it to the lease table */ | ||
226 | static int nobody_responds_to_arp(uint32_t nip, const uint8_t *safe_mac, unsigned arpping_ms) | ||
227 | { | ||
228 | struct in_addr temp; | ||
229 | int r; | ||
230 | |||
231 | r = arpping(nip, safe_mac, | ||
232 | server_config.server_nip, | ||
233 | server_config.server_mac, | ||
234 | server_config.interface, | ||
235 | arpping_ms); | ||
236 | if (r) | ||
237 | return r; | ||
238 | |||
239 | temp.s_addr = nip; | ||
240 | bb_error_msg("%s belongs to someone, reserving it for %u seconds", | ||
241 | inet_ntoa(temp), (unsigned)server_config.conflict_time); | ||
242 | add_lease(NULL, nip, server_config.conflict_time, NULL, 0); | ||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | /* Find a new usable (we think) address */ | ||
247 | static uint32_t find_free_or_expired_nip(const uint8_t *safe_mac, unsigned arpping_ms) | ||
248 | { | ||
249 | uint32_t addr; | ||
250 | struct dyn_lease *oldest_lease = NULL; | ||
251 | |||
252 | #if ENABLE_FEATURE_UDHCPD_BASE_IP_ON_MAC | ||
253 | uint32_t stop; | ||
254 | unsigned i, hash; | ||
255 | |||
256 | /* hash hwaddr: use the SDBM hashing algorithm. Seems to give good | ||
257 | * dispersal even with similarly-valued "strings". | ||
258 | */ | ||
259 | hash = 0; | ||
260 | for (i = 0; i < 6; i++) | ||
261 | hash += safe_mac[i] + (hash << 6) + (hash << 16) - hash; | ||
262 | |||
263 | /* pick a seed based on hwaddr then iterate until we find a free address. */ | ||
264 | addr = server_config.start_ip | ||
265 | + (hash % (1 + server_config.end_ip - server_config.start_ip)); | ||
266 | stop = addr; | ||
267 | #else | ||
268 | addr = server_config.start_ip; | ||
269 | #define stop (server_config.end_ip + 1) | ||
270 | #endif | ||
271 | do { | ||
272 | uint32_t nip; | ||
273 | struct dyn_lease *lease; | ||
274 | |||
275 | /* ie, 192.168.55.0 */ | ||
276 | if ((addr & 0xff) == 0) | ||
277 | goto next_addr; | ||
278 | /* ie, 192.168.55.255 */ | ||
279 | if ((addr & 0xff) == 0xff) | ||
280 | goto next_addr; | ||
281 | nip = htonl(addr); | ||
282 | /* skip our own address */ | ||
283 | if (nip == server_config.server_nip) | ||
284 | goto next_addr; | ||
285 | /* is this a static lease addr? */ | ||
286 | if (is_nip_reserved(server_config.static_leases, nip)) | ||
287 | goto next_addr; | ||
288 | |||
289 | lease = find_lease_by_nip(nip); | ||
290 | if (!lease) { | ||
291 | //TODO: DHCP servers do not always sit on the same subnet as clients: should *ping*, not arp-ping! | ||
292 | if (nobody_responds_to_arp(nip, safe_mac, arpping_ms)) | ||
293 | return nip; | ||
294 | } else { | ||
295 | if (!oldest_lease || lease->expires < oldest_lease->expires) | ||
296 | oldest_lease = lease; | ||
297 | } | ||
298 | |||
299 | next_addr: | ||
300 | addr++; | ||
301 | #if ENABLE_FEATURE_UDHCPD_BASE_IP_ON_MAC | ||
302 | if (addr > server_config.end_ip) | ||
303 | addr = server_config.start_ip; | ||
304 | #endif | ||
305 | } while (addr != stop); | ||
306 | |||
307 | if (oldest_lease | ||
308 | && is_expired_lease(oldest_lease) | ||
309 | && nobody_responds_to_arp(oldest_lease->lease_nip, safe_mac, arpping_ms) | ||
310 | ) { | ||
311 | return oldest_lease->lease_nip; | ||
312 | } | ||
313 | |||
314 | return 0; | ||
315 | } | ||
316 | |||
317 | /* On these functions, make sure your datatype matches */ | ||
318 | static int FAST_FUNC read_str(const char *line, void *arg) | ||
319 | { | ||
320 | char **dest = arg; | ||
321 | |||
322 | free(*dest); | ||
323 | *dest = xstrdup(line); | ||
324 | return 1; | ||
325 | } | ||
326 | |||
327 | static int FAST_FUNC read_u32(const char *line, void *arg) | ||
328 | { | ||
329 | *(uint32_t*)arg = bb_strtou32(line, NULL, 10); | ||
330 | return errno == 0; | ||
331 | } | ||
332 | |||
333 | static int FAST_FUNC read_staticlease(const char *const_line, void *arg) | ||
334 | { | ||
335 | char *line; | ||
336 | char *mac_string; | ||
337 | char *ip_string; | ||
338 | struct ether_addr mac_bytes; /* it's "struct { uint8_t mac[6]; }" */ | ||
339 | uint32_t nip; | ||
340 | |||
341 | /* Read mac */ | ||
342 | line = (char *) const_line; | ||
343 | mac_string = strtok_r(line, " \t", &line); | ||
344 | if (!mac_string || !ether_aton_r(mac_string, &mac_bytes)) | ||
345 | return 0; | ||
346 | |||
347 | /* Read ip */ | ||
348 | ip_string = strtok_r(NULL, " \t", &line); | ||
349 | if (!ip_string || !udhcp_str2nip(ip_string, &nip)) | ||
350 | return 0; | ||
351 | |||
352 | add_static_lease(arg, (uint8_t*) &mac_bytes, nip); | ||
353 | |||
354 | log_static_leases(arg); | ||
355 | |||
356 | return 1; | ||
357 | } | ||
358 | |||
359 | struct config_keyword { | ||
360 | const char *keyword; | ||
361 | int (*handler)(const char *line, void *var) FAST_FUNC; | ||
362 | unsigned ofs; | ||
363 | const char *def; | ||
364 | }; | ||
365 | |||
366 | #define OFS(field) offsetof(struct server_config_t, field) | ||
367 | |||
368 | static const struct config_keyword keywords[] = { | ||
369 | /* keyword handler variable address default */ | ||
370 | {"start" , udhcp_str2nip , OFS(start_ip ), "192.168.0.20"}, | ||
371 | {"end" , udhcp_str2nip , OFS(end_ip ), "192.168.0.254"}, | ||
372 | {"interface" , read_str , OFS(interface ), "eth0"}, | ||
373 | /* Avoid "max_leases value not sane" warning by setting default | ||
374 | * to default_end_ip - default_start_ip + 1: */ | ||
375 | {"max_leases" , read_u32 , OFS(max_leases ), "235"}, | ||
376 | {"auto_time" , read_u32 , OFS(auto_time ), "7200"}, | ||
377 | {"decline_time" , read_u32 , OFS(decline_time ), "3600"}, | ||
378 | {"conflict_time", read_u32 , OFS(conflict_time), "3600"}, | ||
379 | {"offer_time" , read_u32 , OFS(offer_time ), "60"}, | ||
380 | {"min_lease" , read_u32 , OFS(min_lease_sec), "60"}, | ||
381 | {"lease_file" , read_str , OFS(lease_file ), LEASES_FILE}, | ||
382 | {"pidfile" , read_str , OFS(pidfile ), "/var/run/udhcpd.pid"}, | ||
383 | {"siaddr" , udhcp_str2nip , OFS(siaddr_nip ), "0.0.0.0"}, | ||
384 | /* keywords with no defaults must be last! */ | ||
385 | {"option" , udhcp_str2optset, OFS(options ), ""}, | ||
386 | {"opt" , udhcp_str2optset, OFS(options ), ""}, | ||
387 | {"notify_file" , read_str , OFS(notify_file ), NULL}, | ||
388 | {"sname" , read_str , OFS(sname ), NULL}, | ||
389 | {"boot_file" , read_str , OFS(boot_file ), NULL}, | ||
390 | {"static_lease" , read_staticlease, OFS(static_leases), ""}, | ||
391 | }; | ||
392 | enum { KWS_WITH_DEFAULTS = ARRAY_SIZE(keywords) - 6 }; | ||
393 | |||
394 | static NOINLINE void read_config(const char *file) | ||
395 | { | ||
396 | parser_t *parser; | ||
397 | const struct config_keyword *k; | ||
398 | unsigned i; | ||
399 | char *token[2]; | ||
400 | |||
401 | for (i = 0; i < KWS_WITH_DEFAULTS; i++) | ||
402 | keywords[i].handler(keywords[i].def, (char*)&server_config + keywords[i].ofs); | ||
403 | |||
404 | parser = config_open(file); | ||
405 | while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) { | ||
406 | for (k = keywords, i = 0; i < ARRAY_SIZE(keywords); k++, i++) { | ||
407 | if (strcasecmp(token[0], k->keyword) == 0) { | ||
408 | if (!k->handler(token[1], (char*)&server_config + k->ofs)) { | ||
409 | bb_error_msg("can't parse line %u in %s", | ||
410 | parser->lineno, file); | ||
411 | /* reset back to the default value */ | ||
412 | k->handler(k->def, (char*)&server_config + k->ofs); | ||
413 | } | ||
414 | break; | ||
415 | } | ||
416 | } | ||
417 | } | ||
418 | config_close(parser); | ||
419 | |||
420 | server_config.start_ip = ntohl(server_config.start_ip); | ||
421 | server_config.end_ip = ntohl(server_config.end_ip); | ||
422 | } | ||
423 | |||
424 | static void write_leases(void) | ||
425 | { | ||
426 | int fd; | ||
427 | unsigned i; | ||
428 | leasetime_t curr; | ||
429 | int64_t written_at; | ||
430 | |||
431 | fd = open_or_warn(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC); | ||
432 | if (fd < 0) | ||
433 | return; | ||
434 | |||
435 | curr = written_at = time(NULL); | ||
436 | |||
437 | written_at = SWAP_BE64(written_at); | ||
438 | full_write(fd, &written_at, sizeof(written_at)); | ||
439 | |||
440 | for (i = 0; i < server_config.max_leases; i++) { | ||
441 | leasetime_t tmp_time; | ||
442 | |||
443 | if (g_leases[i].lease_nip == 0) | ||
444 | continue; | ||
445 | |||
446 | /* Screw with the time in the struct, for easier writing */ | ||
447 | tmp_time = g_leases[i].expires; | ||
448 | |||
449 | g_leases[i].expires -= curr; | ||
450 | if ((signed_leasetime_t) g_leases[i].expires < 0) | ||
451 | g_leases[i].expires = 0; | ||
452 | g_leases[i].expires = htonl(g_leases[i].expires); | ||
453 | |||
454 | /* No error check. If the file gets truncated, | ||
455 | * we lose some leases on restart. Oh well. */ | ||
456 | full_write(fd, &g_leases[i], sizeof(g_leases[i])); | ||
457 | |||
458 | /* Then restore it when done */ | ||
459 | g_leases[i].expires = tmp_time; | ||
460 | } | ||
461 | close(fd); | ||
462 | |||
463 | if (server_config.notify_file) { | ||
464 | char *argv[3]; | ||
465 | argv[0] = server_config.notify_file; | ||
466 | argv[1] = server_config.lease_file; | ||
467 | argv[2] = NULL; | ||
468 | spawn_and_wait(argv); | ||
469 | } | ||
470 | } | ||
471 | |||
472 | static NOINLINE void read_leases(const char *file) | ||
473 | { | ||
474 | struct dyn_lease lease; | ||
475 | int64_t written_at, time_passed; | ||
476 | int fd; | ||
477 | #if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1 | ||
478 | unsigned i = 0; | ||
479 | #endif | ||
480 | |||
481 | fd = open_or_warn(file, O_RDONLY); | ||
482 | if (fd < 0) | ||
483 | return; | ||
484 | |||
485 | if (full_read(fd, &written_at, sizeof(written_at)) != sizeof(written_at)) | ||
486 | goto ret; | ||
487 | written_at = SWAP_BE64(written_at); | ||
488 | |||
489 | time_passed = time(NULL) - written_at; | ||
490 | /* Strange written_at, or lease file from old version of udhcpd | ||
491 | * which had no "written_at" field? */ | ||
492 | if ((uint64_t)time_passed > 12 * 60 * 60) | ||
493 | goto ret; | ||
494 | |||
495 | while (full_read(fd, &lease, sizeof(lease)) == sizeof(lease)) { | ||
496 | uint32_t y = ntohl(lease.lease_nip); | ||
497 | if (y >= server_config.start_ip && y <= server_config.end_ip) { | ||
498 | signed_leasetime_t expires = ntohl(lease.expires) - (signed_leasetime_t)time_passed; | ||
499 | uint32_t static_nip; | ||
500 | |||
501 | if (expires <= 0) | ||
502 | /* We keep expired leases: add_lease() will add | ||
503 | * a lease with 0 seconds remaining. | ||
504 | * Fewer IP address changes this way for mass reboot scenario. | ||
505 | */ | ||
506 | expires = 0; | ||
507 | |||
508 | /* Check if there is a different static lease for this IP or MAC */ | ||
509 | static_nip = get_static_nip_by_mac(server_config.static_leases, lease.lease_mac); | ||
510 | if (static_nip) { | ||
511 | /* NB: we do not add lease even if static_nip == lease.lease_nip. | ||
512 | */ | ||
513 | continue; | ||
514 | } | ||
515 | if (is_nip_reserved(server_config.static_leases, lease.lease_nip)) | ||
516 | continue; | ||
517 | |||
518 | /* NB: add_lease takes "relative time", IOW, | ||
519 | * lease duration, not lease deadline. */ | ||
520 | if (add_lease(lease.lease_mac, lease.lease_nip, | ||
521 | expires, | ||
522 | lease.hostname, sizeof(lease.hostname) | ||
523 | ) == 0 | ||
524 | ) { | ||
525 | bb_error_msg("too many leases while loading %s", file); | ||
526 | break; | ||
527 | } | ||
528 | #if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1 | ||
529 | i++; | ||
530 | #endif | ||
531 | } | ||
532 | } | ||
533 | log1("read %d leases", i); | ||
534 | ret: | ||
535 | close(fd); | ||
536 | } | ||
41 | 537 | ||
42 | /* Send a packet to a specific mac address and ip address by creating our own ip packet */ | 538 | /* Send a packet to a specific mac address and ip address by creating our own ip packet */ |
43 | static void send_packet_to_client(struct dhcp_packet *dhcp_pkt, int force_broadcast) | 539 | static void send_packet_to_client(struct dhcp_packet *dhcp_pkt, int force_broadcast) |
@@ -290,12 +786,6 @@ static NOINLINE void send_inform(struct dhcp_packet *oldpacket) | |||
290 | send_packet(&packet, /*force_bcast:*/ 0); | 786 | send_packet(&packet, /*force_bcast:*/ 0); |
291 | } | 787 | } |
292 | 788 | ||
293 | |||
294 | /* globals */ | ||
295 | struct dyn_lease *g_leases; | ||
296 | /* struct server_config_t server_config is in bb_common_bufsiz1 */ | ||
297 | |||
298 | |||
299 | int udhcpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 789 | int udhcpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
300 | int udhcpd_main(int argc UNUSED_PARAM, char **argv) | 790 | int udhcpd_main(int argc UNUSED_PARAM, char **argv) |
301 | { | 791 | { |