1 /* vi: set sw=4 ts=4: */
6 * Russ Dill <Russ.Dill@asu.edu> July 2001
8 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
13 /* Override ENABLE_FEATURE_PIDFILE - ifupdown needs our pidfile to always exist */
14 #define WANT_PIDFILE 1
21 static int sockfd
= -1;
24 #define LISTEN_KERNEL 1
26 static smallint listen_mode
;
28 /* initial state: (re)start DHCP negotiation */
29 #define INIT_SELECTING 0
30 /* discover was sent, DHCPOFFER reply received */
32 /* select/renew was sent, DHCPACK reply received */
34 /* half of lease passed, want renew it by sending unicast renew requests */
36 /* renew requests were not answered, lease is almost over, send broadcast renew */
38 /* manually requested renew (SIGUSR1) */
39 #define RENEW_REQUESTED 5
40 /* release, possibly manually requested (SIGUSR2) */
42 static smallint state
;
44 /* struct client_config_t client_config is in bb_common_bufsiz1 */
47 /* just a little helper */
48 static void change_listen_mode(int new_mode
)
50 DEBUG("Entering listen mode: %s",
51 new_mode
!= LISTEN_NONE
52 ? (new_mode
== LISTEN_KERNEL
? "kernel" : "raw")
56 listen_mode
= new_mode
;
61 if (new_mode
== LISTEN_KERNEL
)
62 sockfd
= udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT
, client_config
.interface
);
63 else if (new_mode
!= LISTEN_NONE
)
64 sockfd
= udhcp_raw_socket(client_config
.ifindex
);
65 /* else LISTEN_NONE: sockfd stay closed */
70 static void perform_renew(void)
72 bb_info_msg("Performing a DHCP renew");
75 change_listen_mode(LISTEN_RAW
); // zzz
78 // state = RENEW_REQUESTED; // zzz
80 case RENEW_REQUESTED
: /* impatient are we? fine, square 1 */
81 udhcp_run_script(NULL
, "deconfig");
84 change_listen_mode(LISTEN_RAW
);
85 state
= INIT_SELECTING
;
93 /* perform a release */
94 static void perform_release(uint32_t requested_ip
, uint32_t server_addr
)
96 char buffer
[sizeof("255.255.255.255")];
97 struct in_addr temp_addr
;
99 /* send release packet */
100 if (state
== BOUND
|| state
== RENEWING
|| state
== REBINDING
) {
101 temp_addr
.s_addr
= server_addr
;
102 strcpy(buffer
, inet_ntoa(temp_addr
));
103 temp_addr
.s_addr
= requested_ip
;
104 bb_info_msg("Unicasting a release of %s to %s",
105 inet_ntoa(temp_addr
), buffer
);
106 send_release(server_addr
, requested_ip
); /* unicast */
107 udhcp_run_script(NULL
, "deconfig");
109 bb_info_msg("Entering released state");
111 change_listen_mode(LISTEN_NONE
);
117 static void client_background(void)
120 logmode
&= ~LOGMODE_STDIO
;
121 /* rewrite pidfile, as our pid is different now */
122 write_pidfile(client_config
.pidfile
);
127 static uint8_t* alloc_dhcp_option(int code
, const char *str
, int extra
)
130 int len
= strlen(str
);
131 if (len
> 255) len
= 255;
132 storage
= xzalloc(len
+ extra
+ OPT_DATA
);
133 storage
[OPT_CODE
] = code
;
134 storage
[OPT_LEN
] = len
+ extra
;
135 memcpy(storage
+ extra
+ OPT_DATA
, str
, len
);
140 int udhcpc_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
141 int udhcpc_main(int argc UNUSED_PARAM
, char **argv
)
143 uint8_t *temp
, *message
;
144 char *str_c
, *str_V
, *str_h
, *str_F
, *str_r
;
145 USE_FEATURE_UDHCP_PORT(char *str_P
;)
146 llist_t
*list_O
= NULL
;
147 int tryagain_timeout
= 20;
148 int discover_timeout
= 3;
149 int discover_retries
= 5;
150 uint32_t server_addr
= server_addr
; /* for compiler */
151 uint32_t requested_ip
= 0;
153 uint32_t lease_seconds
= 0; /* can be given as 32-bit quantity */
155 int timeout
; /* must be signed */
156 unsigned already_waited_sec
;
161 struct dhcpMessage packet
;
164 #if ENABLE_GETOPT_LONG
165 static const char udhcpc_longopts
[] ALIGN1
=
166 "clientid\0" Required_argument
"c"
167 "clientid-none\0" No_argument
"C"
168 "vendorclass\0" Required_argument
"V"
169 "hostname\0" Required_argument
"H"
170 "fqdn\0" Required_argument
"F"
171 "interface\0" Required_argument
"i"
172 "now\0" No_argument
"n"
173 "pidfile\0" Required_argument
"p"
174 "quit\0" No_argument
"q"
175 "release\0" No_argument
"R"
176 "request\0" Required_argument
"r"
177 "script\0" Required_argument
"s"
178 "timeout\0" Required_argument
"T"
179 "version\0" No_argument
"v"
180 "retries\0" Required_argument
"t"
181 "tryagain\0" Required_argument
"A"
182 "syslog\0" No_argument
"S"
183 "request-option\0" Required_argument
"O"
184 "no-default-options\0" No_argument
"o"
185 "foreground\0" No_argument
"f"
186 "background\0" No_argument
"b"
187 USE_FEATURE_UDHCPC_ARPING("arping\0" No_argument
"a")
188 USE_FEATURE_UDHCP_PORT("client-port\0" Required_argument
"P")
213 OPT_m
= 1 << 21, // zzz
214 /* The rest has variable bit positions, need to be clever */
216 USE_FOR_MMU( OPTBIT_b
,)
217 USE_FEATURE_UDHCPC_ARPING(OPTBIT_a
,)
218 USE_FEATURE_UDHCP_PORT( OPTBIT_P
,)
219 USE_FOR_MMU( OPT_b
= 1 << OPTBIT_b
,)
220 USE_FEATURE_UDHCPC_ARPING(OPT_a
= 1 << OPTBIT_a
,)
221 USE_FEATURE_UDHCP_PORT( OPT_P
= 1 << OPTBIT_P
,)
224 /* Default options. */
225 USE_FEATURE_UDHCP_PORT(SERVER_PORT
= 67;)
226 USE_FEATURE_UDHCP_PORT(CLIENT_PORT
= 68;)
227 client_config
.interface
= "eth0";
228 client_config
.script
= DEFAULT_SCRIPT
;
230 /* Parse command line */
231 /* Cc: mutually exclusive; O: list; -T,-t,-A take numeric param */
232 opt_complementary
= "c--C:C--c:O::T+:t+:A+";
233 USE_GETOPT_LONG(applet_long_options
= udhcpc_longopts
;)
234 opt
= getopt32(argv
, "c:CV:H:h:F:i:np:qRr:s:T:t:vSA:O:of"
237 USE_FEATURE_UDHCPC_ARPING("a")
238 USE_FEATURE_UDHCP_PORT("P:")
239 , &str_c
, &str_V
, &str_h
, &str_h
, &str_F
240 , &client_config
.interface
, &client_config
.pidfile
, &str_r
/* i,p */
241 , &client_config
.script
/* s */
242 , &discover_timeout
, &discover_retries
, &tryagain_timeout
/* T,t,A */
244 USE_FEATURE_UDHCP_PORT(, &str_P
)
247 client_config
.clientid
= alloc_dhcp_option(DHCP_CLIENT_ID
, str_c
, 0);
249 client_config
.vendorclass
= alloc_dhcp_option(DHCP_VENDOR
, str_V
, 0);
250 if (opt
& (OPT_h
|OPT_H
))
251 client_config
.hostname
= alloc_dhcp_option(DHCP_HOST_NAME
, str_h
, 0);
253 client_config
.fqdn
= alloc_dhcp_option(DHCP_FQDN
, str_F
, 3);
255 S: 1 => Client requests Server to update A RR in DNS as well as PTR
256 O: 1 => Server indicates to client that DNS has been updated regardless
257 E: 1 => Name data is DNS format, i.e. <4>host<6>domain<3>com<0> not "host.domain.com"
258 N: 1 => Client requests Server to not update DNS
260 client_config
.fqdn
[OPT_DATA
+ 0] = 0x1;
261 /* client_config.fqdn[OPT_DATA + 1] = 0; - redundant */
262 /* client_config.fqdn[OPT_DATA + 2] = 0; - redundant */
265 requested_ip
= inet_addr(str_r
);
267 puts("version "BB_VER
);
270 #if ENABLE_FEATURE_UDHCP_PORT
272 CLIENT_PORT
= xatou16(str_P
);
273 SERVER_PORT
= CLIENT_PORT
- 1;
277 client_config
.no_default_options
= 1;
279 char *optstr
= llist_pop(&list_O
);
280 int n
= index_in_strings(dhcp_option_strings
, optstr
);
282 bb_error_msg_and_die("unknown option '%s'", optstr
);
283 n
= dhcp_options
[n
].code
;
284 client_config
.opt_mask
[n
>> 3] |= 1 << (n
& 7);
286 if (opt
& OPT_m
) minpkt
= 1; // zzz
288 if (udhcp_read_interface(client_config
.interface
, &client_config
.ifindex
,
289 NULL
, client_config
.arp
))
292 /* on NOMMU reexec (i.e., background) early */
293 if (!(opt
& OPT_f
)) {
294 bb_daemonize_or_rexec(0 /* flags */, argv
);
295 logmode
= LOGMODE_NONE
;
299 openlog(applet_name
, LOG_PID
, LOG_DAEMON
);
300 logmode
|= LOGMODE_SYSLOG
;
303 /* Make sure fd 0,1,2 are open */
305 /* Equivalent of doing a fflush after every \n */
309 write_pidfile(client_config
.pidfile
);
311 /* Goes to stdout (unless NOMMU) and possibly syslog */
312 bb_info_msg("%s (v"BB_VER
") started", applet_name
);
314 /* if not set, and not suppressed, setup the default client ID */
315 if (!client_config
.clientid
&& !(opt
& OPT_C
)) {
316 client_config
.clientid
= alloc_dhcp_option(DHCP_CLIENT_ID
, "", 7);
317 client_config
.clientid
[OPT_DATA
] = 1;
318 memcpy(client_config
.clientid
+ OPT_DATA
+1, client_config
.arp
, 6);
321 if (!client_config
.vendorclass
)
322 client_config
.vendorclass
= alloc_dhcp_option(DHCP_VENDOR
, "udhcp "BB_VER
, 0);
324 /* setup the signal pipe */
327 state
= INIT_SELECTING
;
328 udhcp_run_script(NULL
, "deconfig");
329 change_listen_mode(LISTEN_RAW
);
332 already_waited_sec
= 0;
334 /* Main event loop. select() waits on signal pipe and possibly
336 * "continue" statements in code below jump to the top of the loop.
339 /* silence "uninitialized!" warning */
340 unsigned timestamp_before_wait
= timestamp_before_wait
;
342 //bb_error_msg("sockfd:%d, listen_mode:%d", sockfd, listen_mode);
344 /* Was opening raw or udp socket here
345 * if (listen_mode != LISTEN_NONE && sockfd < 0),
346 * but on fast network renew responses return faster
347 * than we open sockets. Thus this code is moved
348 * to change_listen_mode(). Thus we open listen socket
349 * BEFORE we send renew request (see "case BOUND:"). */
351 max_fd
= udhcp_sp_fd_set(&rfds
, sockfd
);
353 tv
.tv_sec
= timeout
- already_waited_sec
;
355 retval
= 0; /* If we already timed out, fall through, else... */
356 if ((int)tv
.tv_sec
> 0) {
357 timestamp_before_wait
= (unsigned)monotonic_sec();
358 DEBUG("Waiting on select...");
359 retval
= select(max_fd
+ 1, &rfds
, NULL
, NULL
, &tv
);
361 /* EINTR? A signal was caught, don't panic */
362 if (errno
== EINTR
) {
363 already_waited_sec
+= (unsigned)monotonic_sec() - timestamp_before_wait
;
366 /* Else: an error occured, panic! */
367 bb_perror_msg_and_die("select");
371 /* If timeout dropped to zero, time to become active:
372 * resend discover/renew/whatever
375 /* We will restart the wait in any case */
376 already_waited_sec
= 0;
380 if (packet_num
< discover_retries
) {
384 send_discover(xid
, requested_ip
); /* broadcast */
386 timeout
= discover_timeout
;
391 udhcp_run_script(NULL
, "leasefail");
392 #if BB_MMU /* -b is not supported on NOMMU */
393 if (opt
& OPT_b
) { /* background if no lease */
394 bb_info_msg("No lease, forking to background");
396 /* do not background again! */
397 opt
= ((opt
& ~OPT_b
) | OPT_f
);
400 if (opt
& OPT_n
) { /* abort if no lease */
401 bb_info_msg("No lease, failing");
405 /* wait before trying again */
406 timeout
= tryagain_timeout
;
410 if (packet_num
< discover_retries
) {
411 /* send broadcast select packet */
412 send_select(xid
, server_addr
, requested_ip
);
413 timeout
= discover_timeout
;
417 /* Timed out, go back to init state.
418 * "discover...select...discover..." loops
419 * were seen in the wild. Treat them similarly
420 * to "no response to discover" case */
421 change_listen_mode(LISTEN_RAW
);
422 state
= INIT_SELECTING
;
425 /* Half of the lease passed, time to enter renewing state */
427 change_listen_mode(LISTEN_RAW
); // was: LISTEN_KERNEL -- zzz
428 DEBUG("Entering renew state");
429 /* fall right through */
430 case RENEW_REQUESTED
: /* manual (SIGUSR1) renew */
431 case_RENEW_REQUESTED
:
434 /* send an unicast renew request packet */
435 send_renew(xid
, server_addr
, requested_ip
);
439 /* Timed out, enter rebinding state */
440 DEBUG("Entering rebinding state");
442 /* fall right through */
444 /* Switch to bcast receive */
445 change_listen_mode(LISTEN_RAW
);
446 /* Lease is *really* about to run out,
447 * try to find DHCP server using broadcast */
449 /* send a request packet */
450 send_renew(xid
, 0 /*INADDR_ANY*/, requested_ip
); /* broadcast */
454 /* Timed out, enter init state */
455 bb_info_msg("Lease lost, entering init state");
456 udhcp_run_script(NULL
, "deconfig");
457 state
= INIT_SELECTING
;
458 /*timeout = 0; - already is */
463 /* yah, I know, *you* say it would never happen */
465 continue; /* back to main loop */
466 } /* if select timed out */
468 /* select() didn't timeout, something happened */
470 /* Is it a signal? */
471 /* note: udhcp_sp_read checks FD_ISSET before reading */
472 switch (udhcp_sp_read(&rfds
)) {
475 if (state
== RENEW_REQUESTED
)
476 goto case_RENEW_REQUESTED
;
477 /* Start things over */
479 /* Kill any timeouts because the user wants this to hurry along */
483 perform_release(requested_ip
, server_addr
);
487 bb_info_msg("Received SIGTERM");
488 if (opt
& OPT_R
) /* release on quit */
489 perform_release(requested_ip
, server_addr
);
493 /* Is it a packet? */
494 if (listen_mode
== LISTEN_NONE
|| !FD_ISSET(sockfd
, &rfds
))
500 /* A packet is ready, read it */
501 if (listen_mode
== LISTEN_KERNEL
)
502 len
= udhcp_recv_kernel_packet(&packet
, sockfd
);
504 len
= udhcp_recv_raw_packet(&packet
, sockfd
);
506 /* Error is severe, reopen socket */
507 bb_info_msg("Read error: %s, reopening socket", strerror(errno
));
508 sleep(discover_timeout
); /* 3 seconds by default */
509 change_listen_mode(listen_mode
); /* just close and reopen */
511 /* If this packet will turn out to be unrelated/bogus,
512 * we will go back and wait for next one.
513 * Be sure timeout is properly decreased. */
514 already_waited_sec
+= (unsigned)monotonic_sec() - timestamp_before_wait
;
519 if (packet
.xid
!= xid
) {
520 DEBUG("xid %x (our is %x), ignoring packet",
521 (unsigned)packet
.xid
, (unsigned)xid
);
525 /* Ignore packets that aren't for us */
526 if (memcmp(packet
.chaddr
, client_config
.arp
, 6)) {
527 DEBUG("Packet does not have our chaddr - ignoring");
531 message
= get_option(&packet
, DHCP_MESSAGE_TYPE
);
532 if (message
== NULL
) {
533 bb_error_msg("no message type option, ignoring packet");
539 /* Must be a DHCPOFFER to one of our xid's */
540 if (*message
== DHCPOFFER
) {
541 /* TODO: why we don't just fetch server's IP from IP header? */
542 temp
= get_option(&packet
, DHCP_SERVER_ID
);
544 bb_error_msg("no server ID in message");
546 /* still selecting - this server looks bad */
548 /* it IS unaligned sometimes, don't "optimize" */
549 move_from_unaligned32(server_addr
, temp
);
551 requested_ip
= packet
.yiaddr
;
553 /* enter requesting state */
557 already_waited_sec
= 0;
562 case RENEW_REQUESTED
:
564 if (*message
== DHCPACK
) {
565 temp
= get_option(&packet
, DHCP_LEASE_TIME
);
567 bb_error_msg("no lease time with ACK, using 1 hour lease");
568 lease_seconds
= 60 * 60;
570 /* it IS unaligned sometimes, don't "optimize" */
571 move_from_unaligned32(lease_seconds
, temp
);
572 lease_seconds
= ntohl(lease_seconds
);
573 lease_seconds
&= 0x0fffffff; /* paranoia: must not be prone to overflows */
574 if (lease_seconds
< 10) /* and not too small */
577 #if ENABLE_FEATURE_UDHCPC_ARPING
579 /* RFC 2131 3.1 paragraph 5:
580 * "The client receives the DHCPACK message with configuration
581 * parameters. The client SHOULD perform a final check on the
582 * parameters (e.g., ARP for allocated network address), and notes
583 * the duration of the lease specified in the DHCPACK message. At this
584 * point, the client is configured. If the client detects that the
585 * address is already in use (e.g., through the use of ARP),
586 * the client MUST send a DHCPDECLINE message to the server and restarts
587 * the configuration process..." */
588 if (!arpping(packet
.yiaddr
,
591 client_config
.interface
)
593 bb_info_msg("Offered address is in use "
594 "(got ARP reply), declining");
595 send_decline(xid
, server_addr
, packet
.yiaddr
);
597 if (state
!= REQUESTING
)
598 udhcp_run_script(NULL
, "deconfig");
599 change_listen_mode(LISTEN_RAW
);
600 state
= INIT_SELECTING
;
602 timeout
= tryagain_timeout
;
604 already_waited_sec
= 0;
605 continue; /* back to main loop */
609 /* enter bound state */
610 timeout
= lease_seconds
/ 2;
612 struct in_addr temp_addr
;
613 temp_addr
.s_addr
= packet
.yiaddr
;
614 bb_info_msg("Lease of %s obtained, lease time %u",
615 inet_ntoa(temp_addr
), (unsigned)lease_seconds
);
617 requested_ip
= packet
.yiaddr
;
618 udhcp_run_script(&packet
, state
== REQUESTING
? "bound" : "renew");
621 change_listen_mode(LISTEN_NONE
);
622 if (opt
& OPT_q
) { /* quit after lease */
623 if (opt
& OPT_R
) /* release on quit */
624 perform_release(requested_ip
, server_addr
);
627 /* future renew failures should not exit (JM) */
629 #if BB_MMU /* NOMMU case backgrounded earlier */
630 if (!(opt
& OPT_f
)) {
632 /* do not background again! */
633 opt
= ((opt
& ~OPT_b
) | OPT_f
);
636 already_waited_sec
= 0;
637 continue; /* back to main loop */
639 if (*message
== DHCPNAK
) {
640 /* return to init state */
641 bb_info_msg("Received DHCP NAK");
642 udhcp_run_script(&packet
, "nak");
643 if (state
!= REQUESTING
)
644 udhcp_run_script(NULL
, "deconfig");
645 change_listen_mode(LISTEN_RAW
);
646 sleep(3); /* avoid excessive network traffic */
647 state
= INIT_SELECTING
;
651 already_waited_sec
= 0;
654 /* case BOUND: - ignore all packets */
655 /* case RELEASED: - ignore all packets */
657 /* back to main loop */
658 } /* for (;;) - main loop ends */
663 /*if (client_config.pidfile) - remove_pidfile has its own check */
664 remove_pidfile(client_config
.pidfile
);