udhcpd: untangle incredibly messy handling of DHCPREQUEST
[oweals/busybox.git] / networking / udhcp / dhcpd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * udhcp Server
4  * Copyright (C) 1999 Matthew Ramsay <matthewr@moreton.com.au>
5  *                      Chris Trew <ctrew@moreton.com.au>
6  *
7  * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
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
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  */
23
24 #include <syslog.h>
25 #include "common.h"
26 #include "dhcpc.h"
27 #include "dhcpd.h"
28 #include "options.h"
29
30
31 /* Send a packet to a specific mac address and ip address by creating our own ip packet */
32 static void send_packet_to_client(struct dhcp_packet *dhcp_pkt, int force_broadcast)
33 {
34         const uint8_t *chaddr;
35         uint32_t ciaddr;
36
37         // Was:
38         //if (force_broadcast) { /* broadcast */ }
39         //else if (dhcp_pkt->ciaddr) { /* unicast to dhcp_pkt->ciaddr */ }
40         //else if (dhcp_pkt->flags & htons(BROADCAST_FLAG)) { /* broadcast */ }
41         //else { /* unicast to dhcp_pkt->yiaddr */ }
42         // But this is wrong: yiaddr is _our_ idea what client's IP is
43         // (for example, from lease file). Client may not know that,
44         // and may not have UDP socket listening on that IP!
45         // We should never unicast to dhcp_pkt->yiaddr!
46         // dhcp_pkt->ciaddr, OTOH, comes from client's request packet,
47         // and can be used.
48
49         if (force_broadcast
50          || (dhcp_pkt->flags & htons(BROADCAST_FLAG))
51          || !dhcp_pkt->ciaddr
52         ) {
53                 log1("Broadcasting packet to client");
54                 ciaddr = INADDR_BROADCAST;
55                 chaddr = MAC_BCAST_ADDR;
56         } else {
57                 log1("Unicasting packet to client ciaddr");
58                 ciaddr = dhcp_pkt->ciaddr;
59                 chaddr = dhcp_pkt->chaddr;
60         }
61
62         udhcp_send_raw_packet(dhcp_pkt,
63                 /*src*/ server_config.server_nip, SERVER_PORT,
64                 /*dst*/ ciaddr, CLIENT_PORT, chaddr,
65                 server_config.ifindex);
66 }
67
68 /* Send a packet to gateway_nip using the kernel ip stack */
69 static void send_packet_to_relay(struct dhcp_packet *dhcp_pkt)
70 {
71         log1("Forwarding packet to relay");
72
73         udhcp_send_kernel_packet(dhcp_pkt,
74                         server_config.server_nip, SERVER_PORT,
75                         dhcp_pkt->gateway_nip, SERVER_PORT);
76 }
77
78 static void send_packet(struct dhcp_packet *dhcp_pkt, int force_broadcast)
79 {
80         if (dhcp_pkt->gateway_nip)
81                 send_packet_to_relay(dhcp_pkt);
82         else
83                 send_packet_to_client(dhcp_pkt, force_broadcast);
84 }
85
86 static void init_packet(struct dhcp_packet *packet, struct dhcp_packet *oldpacket, char type)
87 {
88         /* Sets op, htype, hlen, cookie fields
89          * and adds DHCP_MESSAGE_TYPE option */
90         udhcp_init_header(packet, type);
91
92         packet->xid = oldpacket->xid;
93         memcpy(packet->chaddr, oldpacket->chaddr, sizeof(oldpacket->chaddr));
94         packet->flags = oldpacket->flags;
95         packet->gateway_nip = oldpacket->gateway_nip;
96         packet->ciaddr = oldpacket->ciaddr;
97         add_simple_option(packet->options, DHCP_SERVER_ID, server_config.server_nip);
98 }
99
100 /* Fill options field, siaddr_nip, and sname and boot_file fields.
101  * TODO: teach this code to use overload option.
102  */
103 static void add_server_options(struct dhcp_packet *packet)
104 {
105         struct option_set *curr = server_config.options;
106
107         while (curr) {
108                 if (curr->data[OPT_CODE] != DHCP_LEASE_TIME)
109                         add_option_string(packet->options, curr->data);
110                 curr = curr->next;
111         }
112
113         packet->siaddr_nip = server_config.siaddr_nip;
114
115         if (server_config.sname)
116                 strncpy((char*)packet->sname, server_config.sname, sizeof(packet->sname) - 1);
117         if (server_config.boot_file)
118                 strncpy((char*)packet->file, server_config.boot_file, sizeof(packet->file) - 1);
119 }
120
121 static uint32_t select_lease_time(struct dhcp_packet *packet)
122 {
123         uint32_t lease_time_sec = server_config.max_lease_sec;
124         uint8_t *lease_time_opt = get_option(packet, DHCP_LEASE_TIME);
125         if (lease_time_opt) {
126                 move_from_unaligned32(lease_time_sec, lease_time_opt);
127                 lease_time_sec = ntohl(lease_time_sec);
128                 if (lease_time_sec > server_config.max_lease_sec)
129                         lease_time_sec = server_config.max_lease_sec;
130                 if (lease_time_sec < server_config.min_lease_sec)
131                         lease_time_sec = server_config.min_lease_sec;
132         }
133         return lease_time_sec;
134 }
135
136 /* We got a DHCP DISCOVER. Send an OFFER. */
137 static void send_offer(struct dhcp_packet *oldpacket, uint32_t static_lease_nip, struct dyn_lease *lease)
138 {
139         struct dhcp_packet packet;
140         uint32_t lease_time_sec;
141         struct in_addr addr;
142
143         init_packet(&packet, oldpacket, DHCPOFFER);
144
145         /* If it is a static lease, use its IP */
146         packet.yiaddr = static_lease_nip;
147         /* Else: */
148         if (!static_lease_nip) {
149                 /* We have no static lease for client's chaddr */
150                 uint32_t req_nip;
151                 uint8_t *req_ip_opt;
152                 const char *p_host_name;
153
154                 if (lease) {
155                         /* We have a dynamic lease for client's chaddr.
156                          * Reuse its IP (even if lease is expired).
157                          * Note that we ignore requested IP in this case.
158                          */
159                         packet.yiaddr = lease->lease_nip;
160                 }
161                 /* Or: if client has requested an IP */
162                 else if ((req_ip_opt = get_option(oldpacket, DHCP_REQUESTED_IP)) != NULL
163                  /* (read IP) */
164                  && (move_from_unaligned32(req_nip, req_ip_opt), 1)
165                  /* and the IP is in the lease range */
166                  && ntohl(req_nip) >= server_config.start_ip
167                  && ntohl(req_nip) <= server_config.end_ip
168                  /* and */
169                  && (  !(lease = find_lease_by_nip(req_nip)) /* is not already taken */
170                     || is_expired_lease(lease) /* or is taken, but expired */
171                     )
172                 ) {
173                         packet.yiaddr = req_nip;
174                 }
175                 else {
176                         /* Otherwise, find a free IP */
177                         packet.yiaddr = find_free_or_expired_nip(oldpacket->chaddr);
178                 }
179
180                 if (!packet.yiaddr) {
181                         bb_error_msg("no free IP addresses. OFFER abandoned");
182                         return;
183                 }
184                 /* Reserve the IP for a short time hoping to get DHCPREQUEST soon */
185                 p_host_name = (const char*) get_option(oldpacket, DHCP_HOST_NAME);
186                 lease = add_lease(packet.chaddr, packet.yiaddr,
187                                 server_config.offer_time,
188                                 p_host_name,
189                                 p_host_name ? (unsigned char)p_host_name[OPT_LEN - OPT_DATA] : 0
190                 );
191                 if (!lease) {
192                         bb_error_msg("no free IP addresses. OFFER abandoned");
193                         return;
194                 }
195         }
196
197         lease_time_sec = select_lease_time(oldpacket);
198         add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_sec));
199         add_server_options(&packet);
200
201         addr.s_addr = packet.yiaddr;
202         bb_info_msg("Sending OFFER of %s", inet_ntoa(addr));
203         /* send_packet emits error message itself if it detects failure */
204         send_packet(&packet, /*force_bcast:*/ 0);
205 }
206
207 static void send_NAK(struct dhcp_packet *oldpacket)
208 {
209         struct dhcp_packet packet;
210
211         init_packet(&packet, oldpacket, DHCPNAK);
212
213         log1("Sending NAK");
214         send_packet(&packet, /*force_bcast:*/ 1);
215 }
216
217 static void send_ACK(struct dhcp_packet *oldpacket, uint32_t yiaddr)
218 {
219         struct dhcp_packet packet;
220         uint32_t lease_time_sec;
221         struct in_addr addr;
222         const char *p_host_name;
223
224         init_packet(&packet, oldpacket, DHCPACK);
225         packet.yiaddr = yiaddr;
226
227         lease_time_sec = select_lease_time(oldpacket);
228         add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_sec));
229
230         add_server_options(&packet);
231
232         addr.s_addr = yiaddr;
233         bb_info_msg("Sending ACK to %s", inet_ntoa(addr));
234         send_packet(&packet, /*force_bcast:*/ 0);
235
236         p_host_name = (const char*) get_option(oldpacket, DHCP_HOST_NAME);
237         add_lease(packet.chaddr, packet.yiaddr,
238                 lease_time_sec,
239                 p_host_name,
240                 p_host_name ? (unsigned char)p_host_name[OPT_LEN - OPT_DATA] : 0
241         );
242         if (ENABLE_FEATURE_UDHCPD_WRITE_LEASES_EARLY) {
243                 /* rewrite the file with leases at every new acceptance */
244                 write_leases();
245         }
246 }
247
248 static void send_inform(struct dhcp_packet *oldpacket)
249 {
250         struct dhcp_packet packet;
251
252         /* "The server responds to a DHCPINFORM message by sending a DHCPACK
253          * message directly to the address given in the 'ciaddr' field
254          * of the DHCPINFORM message.  The server MUST NOT send a lease
255          * expiration time to the client and SHOULD NOT fill in 'yiaddr'."
256          */
257         init_packet(&packet, oldpacket, DHCPACK);
258         add_server_options(&packet);
259
260         send_packet(&packet, /*force_bcast:*/ 0);
261 }
262
263
264 /* globals */
265 struct dyn_lease *g_leases;
266 /* struct server_config_t server_config is in bb_common_bufsiz1 */
267
268
269 int udhcpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
270 int udhcpd_main(int argc UNUSED_PARAM, char **argv)
271 {
272         fd_set rfds;
273         int server_socket = -1, retval, max_sock;
274         struct dhcp_packet packet;
275         uint8_t *state;
276         uint32_t static_lease_nip;
277         unsigned timeout_end;
278         unsigned num_ips;
279         unsigned opt;
280         struct option_set *option;
281         struct dyn_lease *lease, fake_lease;
282         IF_FEATURE_UDHCP_PORT(char *str_P;)
283
284 #if ENABLE_FEATURE_UDHCP_PORT
285         SERVER_PORT = 67;
286         CLIENT_PORT = 68;
287 #endif
288
289 #if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
290         opt_complementary = "vv";
291 #endif
292         opt = getopt32(argv, "fSv"
293                 IF_FEATURE_UDHCP_PORT("P:", &str_P)
294 #if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
295                 , &dhcp_verbose
296 #endif
297                 );
298         argv += optind;
299         if (!(opt & 1)) { /* no -f */
300                 bb_daemonize_or_rexec(0, argv);
301                 logmode = LOGMODE_NONE;
302         }
303         if (opt & 2) { /* -S */
304                 openlog(applet_name, LOG_PID, LOG_DAEMON);
305                 logmode |= LOGMODE_SYSLOG;
306         }
307 #if ENABLE_FEATURE_UDHCP_PORT
308         if (opt & 4) { /* -P */
309                 SERVER_PORT = xatou16(str_P);
310                 CLIENT_PORT = SERVER_PORT + 1;
311         }
312 #endif
313         /* Would rather not do read_config before daemonization -
314          * otherwise NOMMU machines will parse config twice */
315         read_config(argv[0] ? argv[0] : DHCPD_CONF_FILE);
316
317         /* Make sure fd 0,1,2 are open */
318         bb_sanitize_stdio();
319         /* Equivalent of doing a fflush after every \n */
320         setlinebuf(stdout);
321
322         /* Create pidfile */
323         write_pidfile(server_config.pidfile);
324         /* if (!..) bb_perror_msg("can't create pidfile %s", pidfile); */
325
326         bb_info_msg("%s (v"BB_VER") started", applet_name);
327
328         option = find_option(server_config.options, DHCP_LEASE_TIME);
329         server_config.max_lease_sec = DEFAULT_LEASE_TIME;
330         if (option) {
331                 move_from_unaligned32(server_config.max_lease_sec, option->data + OPT_DATA);
332                 server_config.max_lease_sec = ntohl(server_config.max_lease_sec);
333         }
334
335         /* Sanity check */
336         num_ips = server_config.end_ip - server_config.start_ip + 1;
337         if (server_config.max_leases > num_ips) {
338                 bb_error_msg("max_leases=%u is too big, setting to %u",
339                         (unsigned)server_config.max_leases, num_ips);
340                 server_config.max_leases = num_ips;
341         }
342
343         g_leases = xzalloc(server_config.max_leases * sizeof(g_leases[0]));
344         read_leases(server_config.lease_file);
345
346         if (udhcp_read_interface(server_config.interface,
347                         &server_config.ifindex,
348                         &server_config.server_nip,
349                         server_config.server_mac)
350         ) {
351                 retval = 1;
352                 goto ret;
353         }
354
355         /* Setup the signal pipe */
356         udhcp_sp_setup();
357
358         timeout_end = monotonic_sec() + server_config.auto_time;
359         while (1) { /* loop until universe collapses */
360                 int bytes;
361                 struct timeval tv;
362                 uint8_t *server_id_opt;
363                 uint8_t *requested_opt;
364                 uint32_t requested_nip = requested_nip; /* for compiler */
365
366                 if (server_socket < 0) {
367                         server_socket = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT,
368                                         server_config.interface);
369                 }
370
371                 max_sock = udhcp_sp_fd_set(&rfds, server_socket);
372                 if (server_config.auto_time) {
373                         tv.tv_sec = timeout_end - monotonic_sec();
374                         tv.tv_usec = 0;
375                 }
376                 retval = 0;
377                 if (!server_config.auto_time || tv.tv_sec > 0) {
378                         retval = select(max_sock + 1, &rfds, NULL, NULL,
379                                         server_config.auto_time ? &tv : NULL);
380                 }
381                 if (retval == 0) {
382                         write_leases();
383                         timeout_end = monotonic_sec() + server_config.auto_time;
384                         continue;
385                 }
386                 if (retval < 0 && errno != EINTR) {
387                         log1("Error on select");
388                         continue;
389                 }
390
391                 switch (udhcp_sp_read(&rfds)) {
392                 case SIGUSR1:
393                         bb_info_msg("Received a SIGUSR1");
394                         write_leases();
395                         /* why not just reset the timeout, eh */
396                         timeout_end = monotonic_sec() + server_config.auto_time;
397                         continue;
398                 case SIGTERM:
399                         bb_info_msg("Received a SIGTERM");
400                         goto ret0;
401                 case 0: /* no signal: read a packet */
402                         break;
403                 default: /* signal or error (probably EINTR): back to select */
404                         continue;
405                 }
406
407                 bytes = udhcp_recv_kernel_packet(&packet, server_socket);
408                 if (bytes < 0) {
409                         /* bytes can also be -2 ("bad packet data") */
410                         if (bytes == -1 && errno != EINTR) {
411                                 log1("Read error: %s, reopening socket", strerror(errno));
412                                 close(server_socket);
413                                 server_socket = -1;
414                         }
415                         continue;
416                 }
417                 if (packet.hlen != 6) {
418                         bb_error_msg("MAC length != 6, ignoring packet");
419                         continue;
420                 }
421                 if (packet.op != BOOTREQUEST) {
422                         bb_error_msg("not a REQUEST, ignoring packet");
423                         continue;
424                 }
425                 state = get_option(&packet, DHCP_MESSAGE_TYPE);
426                 if (state == NULL || state[0] < DHCP_MINTYPE || state[0] > DHCP_MAXTYPE) {
427                         bb_error_msg("no or bad message type option, ignoring packet");
428                         continue;
429                 }
430
431                 /* Look for a static/dynamic lease */
432                 static_lease_nip = get_static_nip_by_mac(server_config.static_leases, &packet.chaddr);
433                 if (static_lease_nip) {
434                         bb_info_msg("Found static lease: %x", static_lease_nip);
435                         memcpy(&fake_lease.lease_mac, &packet.chaddr, 6);
436                         fake_lease.lease_nip = static_lease_nip;
437                         fake_lease.expires = 0;
438                         lease = &fake_lease;
439                 } else {
440                         lease = find_lease_by_mac(packet.chaddr);
441                 }
442
443                 /* Get REQUESTED_IP and SERVER_ID if present */
444                 server_id_opt = get_option(&packet, DHCP_SERVER_ID);
445                 if (server_id_opt) {
446                         uint32_t server_id_net;
447                         move_from_unaligned32(server_id_net, server_id_opt);
448                         if (server_id_net != server_config.server_nip) {
449                                 /* client talks to somebody else */
450                                 log1("server ID doesn't match, ignoring");
451                                 continue;
452                         }
453                 }
454                 requested_opt = get_option(&packet, DHCP_REQUESTED_IP);
455                 if (requested_opt) {
456                         move_from_unaligned32(requested_nip, requested_opt);
457                 }
458
459                 switch (state[0]) {
460
461                 case DHCPDISCOVER:
462                         log1("Received DISCOVER");
463
464                         send_offer(&packet, static_lease_nip, lease);
465                         break;
466
467                 case DHCPREQUEST:
468                         log1("Received REQUEST");
469
470                         /* RFC 2131: "The REQUESTED_IP option MUST be set
471                          * to the value of 'yiaddr' in the DHCPOFFER message
472                          * from the server." */
473                         if (!requested_opt) {
474                                 log1("no requested IP, ignoring");
475                                 break;
476                         }
477                         if (lease && requested_nip == lease->lease_nip) {
478                                 /* client requests IP which matches the lease.
479                                  * ACK it, and bump lease expiration time. */
480                                 send_ACK(&packet, lease->lease_nip);
481                                 break;
482                         }
483                         if (server_id_opt) {
484                                 /* client was talking specifically to us.
485                                  * "No, we don't have this IP for you". */
486                                 send_NAK(&packet);
487                         }
488                         break;
489
490                 case DHCPDECLINE:
491                         /* RFC 2131:
492                          * "If the server receives a DHCPDECLINE message,
493                          * the client has discovered through some other means
494                          * that the suggested network address is already
495                          * in use. The server MUST mark the network address
496                          * as not available and SHOULD notify the local
497                          * sysadmin of a possible configuration problem."
498                          *
499                          * SERVER_ID must be present,
500                          * REQUESTED_IP must be present,
501                          * chaddr must be filled in,
502                          * ciaddr must be 0 (we do not check this)
503                          */
504                         log1("Received DECLINE");
505                         if (server_id_opt
506                          && requested_opt
507                          && lease  /* chaddr matches this lease */
508                          && requested_nip == lease->lease_nip
509                         ) {
510                                 memset(lease->lease_mac, 0, sizeof(lease->lease_mac));
511                                 lease->expires = time(NULL) + server_config.decline_time;
512                         }
513                         break;
514
515                 case DHCPRELEASE:
516                         /* "Upon receipt of a DHCPRELEASE message, the server
517                          * marks the network address as not allocated."
518                          *
519                          * SERVER_ID must be present,
520                          * REQUESTED_IP must not be present (we do not check this),
521                          * chaddr must be filled in,
522                          * ciaddr must be filled in
523                          */
524                         log1("Received RELEASE");
525                         if (server_id_opt
526                          && lease  /* chaddr matches this lease */
527                          && packet.ciaddr == lease->lease_nip
528                         ) {
529                                 lease->expires = time(NULL);
530                         }
531                         break;
532
533                 case DHCPINFORM:
534                         log1("Received INFORM");
535                         send_inform(&packet);
536                         break;
537                 }
538         }
539  ret0:
540         retval = 0;
541  ret:
542         /*if (server_config.pidfile) - server_config.pidfile is never NULL */
543                 remove_pidfile(server_config.pidfile);
544         return retval;
545 }