解读以下代码“int udhcpc_main(int argc UNUSED_PARAM, char **argv)
{
uint8_t *message;
const char *str_V, *str_F, *str_r;
IF_FEATURE_UDHCPC_ARPING(const char *str_a = "2000";)
IF_FEATURE_UDHCP_PORT(char *str_P;)
uint8_t *clientid_mac_ptr;
llist_t *list_O = NULL;
llist_t *list_x = NULL;
int tryagain_timeout = 20;
int discover_timeout = 3;
int discover_retries = 3;
uint32_t server_id = server_id; /* for compiler */
uint32_t requested_ip = 0;
int packet_num;
int timeout; /* must be signed */
int lease_remaining; /* must be signed */
unsigned opt;
IF_FEATURE_UDHCPC_ARPING(unsigned arpping_ms;)
int retval;
setup_common_bufsiz();
/* Default options */
IF_FEATURE_UDHCP_PORT(SERVER_PORT = 67;)
IF_FEATURE_UDHCP_PORT(CLIENT_PORT = 68;)
client_data.interface = CONFIG_UDHCPC_DEFAULT_INTERFACE;
client_data.script = CONFIG_UDHCPC_DEFAULT_SCRIPT;
client_data.sockfd = -1;
str_V = "udhcp "BB_VER;
/* Make sure fd 0,1,2 are open */
/* Set up the signal pipe on fds 3,4 - must be before openlog() */
udhcp_sp_setup();
/* Parse command line */
opt = getopt32long(argv, "^"
/* O,x: list; -T,-t,-A take numeric param */
"CV:F:i:np:qRr:s:T:+t:+SA:+O:*ox:*fB"
USE_FOR_MMU("b")
IF_FEATURE_UDHCPC_ARPING("a::")
IF_FEATURE_UDHCP_PORT("P:")
"v"
"\0" IF_UDHCP_VERBOSE("vv") /* -v is a counter */
, udhcpc_longopts
, &str_V, &str_F
, &client_data.interface, &client_data.pidfile /* i,p */
, &str_r /* r */
, &client_data.script /* s */
, &discover_timeout, &discover_retries, &tryagain_timeout /* T,t,A */
, &list_O
, &list_x
IF_FEATURE_UDHCPC_ARPING(, &str_a)
IF_FEATURE_UDHCP_PORT(, &str_P)
IF_UDHCP_VERBOSE(, &dhcp_verbose)
);
if (opt & OPT_F) {
char *p;
unsigned len;
/* FQDN option format: [0x51][len][flags][0][0]<fqdn> */
len = strlen(str_F);
p = udhcp_insert_new_option(
&client_data.options, DHCP_FQDN,
len + 3, /*dhcp6:*/ 0);
/* Flag bits: 0000NEOS
* S: 1 = Client requests server to update A RR in DNS as well as PTR
* O: 1 = Server indicates to client that DNS has been updated regardless
* E: 1 = Name is in DNS format, i.e. <4>host<6>domain<3>com<0>,
* not "host.domain.com". Format 0 is obsolete.
* N: 1 = Client requests server to not update DNS (S must be 0 then)
* Two [0] bytes which follow are deprecated and must be 0.
*/
p[OPT_DATA + 0] = 0x1;
/*p[OPT_DATA + 1] = 0; - xzalloc did it */
/*p[OPT_DATA + 2] = 0; */
memcpy(p + OPT_DATA + 3, str_F, len); /* do not store NUL byte */
}
if (opt & OPT_r)
if (!inet_aton(str_r, (void*)&requested_ip))
bb_show_usage();
#if ENABLE_FEATURE_UDHCP_PORT
if (opt & OPT_P) {
CLIENT_PORT = xatou16(str_P);
SERVER_PORT = CLIENT_PORT - 1;
}
#endif
IF_FEATURE_UDHCPC_ARPING(arpping_ms = xatou(str_a);)
while (list_O) {
char *optstr = llist_pop(&list_O);
unsigned n = bb_strtou(optstr, NULL, 0);
if (errno || n > 254) {
n = udhcp_option_idx(optstr, dhcp_option_strings);
n = dhcp_optflags[n].code;
}
client_data.opt_mask[n >> 3] |= 1 << (n & 7);
}
if (!(opt & OPT_o)) {
unsigned i, n;
for (i = 0; (n = dhcp_optflags[i].code) != 0; i++) {
if (dhcp_optflags[i].flags & OPTION_REQ) {
client_data.opt_mask[n >> 3] |= 1 << (n & 7);
}
}
}
while (list_x) {
char *optstr = xstrdup(llist_pop(&list_x));
udhcp_str2optset(optstr, &client_data.options,
dhcp_optflags, dhcp_option_strings,
/*dhcpv6:*/ 0
);
free(optstr);
}
if (str_V[0] != '\0') {
char *p;
unsigned len = strnlen(str_V, 254);
p = udhcp_insert_new_option(
&client_data.options, DHCP_VENDOR,
len, /*dhcp6:*/ 0);
memcpy(p + OPT_DATA, str_V, len); /* do not store NUL byte */
}
clientid_mac_ptr = NULL;
if (!(opt & OPT_C) && !udhcp_find_option(client_data.options, DHCP_CLIENT_ID, /*dhcpv6:*/ 0)) {
/* not suppressed and not set, create default client ID */
clientid_mac_ptr = udhcp_insert_new_option(
&client_data.options, DHCP_CLIENT_ID,
1 + 6, /*dhcp6:*/ 0);
clientid_mac_ptr[OPT_DATA] = 1; /* type: ethernet */
clientid_mac_ptr += OPT_DATA + 1; /* skip option code, len, ethernet */
}
/* Not really necessary (we redo it on every iteration)
* but allows early (before daemonization) detection
* of bad interface name.
*/
if (udhcp_read_interface(client_data.interface,
&client_data.ifindex,
NULL,
client_data.client_mac)
) {
return 1;
}
#if !BB_MMU
/* on NOMMU reexec (i.e., background) early */
if (!(opt & OPT_f)) {
bb_daemonize_or_rexec(0 /* flags */, argv);
logmode = LOGMODE_NONE;
}
#endif
if (opt & OPT_S) {
openlog(applet_name, LOG_PID, LOG_DAEMON);
logmode |= LOGMODE_SYSLOG;
}
/* Create pidfile */
write_pidfile(client_data.pidfile);
/* Goes to stdout (unless NOMMU) and possibly syslog */
bb_simple_info_msg("started, v"BB_VER);
/* We want random_xid to be random... */
srand(monotonic_us());
client_data.state = INIT_SELECTING;
d4_run_script_deconfig();
packet_num = 0;
timeout = 0;
lease_remaining = 0;
/* Main event loop. select() waits on signal pipe and possibly
* on sockfd.
* "continue" statements in code below jump to the top of the loop.
*/
for (;;) {
struct pollfd pfds[2];
struct dhcp_packet packet;
//bb_error_msg("sockfd:%d, listen_mode:%d", client_data.sockfd, client_data.listen_mode);
/* Was opening raw or udp socket here
* if (client_data.listen_mode != LISTEN_NONE && client_data.sockfd < 0),
* but on fast network renew responses return faster
* than we open sockets. Thus this code is moved
* to change_listen_mode(). Thus we open listen socket
* BEFORE we send renew request (see "case BOUND:"). */
udhcp_sp_fd_set(pfds, client_data.sockfd);
retval = 0;
/* If we already timed out, fall through with retval = 0, else... */
if (timeout > 0) {
unsigned diff;
if (timeout > INT_MAX/1000)
timeout = INT_MAX/1000;
log1("waiting %u seconds", timeout);
diff = (unsigned)monotonic_sec();
retval = poll(pfds, 2, timeout * 1000);
diff = (unsigned)monotonic_sec() - diff;
lease_remaining -= diff;
if (lease_remaining < 0)
lease_remaining = 0;
timeout -= diff;
if (timeout < 0)
timeout = 0;
if (retval < 0) {
/* EINTR? A signal was caught, don't panic */
if (errno == EINTR) {
continue;
}
/* Else: an error occurred, panic! */
bb_simple_perror_msg_and_die("poll");
}
}
/* If timeout dropped to zero, time to become active:
* resend discover/renew/whatever
*/
if (retval == 0) {
/* When running on a bridge, the ifindex may have changed
* (e.g. if member interfaces were added/removed
* or if the status of the bridge changed).
* Refresh ifindex and client_mac:
*/
if (udhcp_read_interface(client_data.interface,
&client_data.ifindex,
NULL,
client_data.client_mac)
) {
goto ret0; /* iface is gone? */
}
if (clientid_mac_ptr)
memcpy(clientid_mac_ptr, client_data.client_mac, 6);
switch (client_data.state) {
case INIT_SELECTING:
if (!discover_retries || packet_num < discover_retries) {
if (packet_num == 0) {
change_listen_mode(LISTEN_RAW);
client_data.xid = random_xid();
}
/* broadcast */
send_discover(requested_ip);
timeout = discover_timeout;
packet_num++;
continue;
}
leasefail:
change_listen_mode(LISTEN_NONE);
d4_run_script(NULL, "leasefail");
#if BB_MMU /* -b is not supported on NOMMU */
if (opt & OPT_b) { /* background if no lease */
bb_simple_info_msg("no lease, forking to background");
client_background();
/* do not background again! */
opt = ((opt & ~(OPT_b|OPT_n)) | OPT_f);
/* ^^^ also disables -n (-b takes priority over -n):
* ifup's default udhcpc options are -R -n,
* and users want to be able to add -b
* (in a config file) to make it background
* _and not exit_.
*/
} else
#endif
if (opt & OPT_n) { /* abort if no lease */
bb_simple_info_msg("no lease, failing");
retval = 1;
goto ret;
}
/* Wait before trying again */
timeout = tryagain_timeout;
packet_num = 0;
continue;
case REQUESTING:
if (packet_num < 3) {
/* send broadcast select packet */
send_select(server_id, requested_ip);
timeout = discover_timeout;
packet_num++;
continue;
}
/* Timed out, go back to init state.
* "discover...select...discover..." loops
* were seen in the wild. Treat them similarly
* to "no response to discover" case */
client_data.state = INIT_SELECTING;
goto leasefail;
case BOUND:
/* 1/2 lease passed, enter renewing state */
client_data.state = RENEWING;
client_data.first_secs = 0; /* make secs field count from 0 */
got_SIGUSR1:
log1s("entering renew state");
change_listen_mode(LISTEN_KERNEL);
/* fall right through */
case RENEW_REQUESTED: /* in manual (SIGUSR1) renew */
case RENEWING:
if (packet_num == 0) {
/* Send an unicast renew request */
/* Sometimes observed to fail (EADDRNOTAVAIL) to bind
* a new UDP socket for sending inside send_renew.
* I hazard to guess existing listening socket
* is somehow conflicting with it, but why is it
* not deterministic then?! Strange.
* Anyway, it does recover by eventually failing through
* into INIT_SELECTING state.
*/
if (send_renew(server_id, requested_ip) >= 0) {
timeout = discover_timeout;
packet_num++;
continue;
}
/* else: error sending.
* example: ENETUNREACH seen with server
* which gave us bogus server ID 1.1.1.1
* which wasn't reachable (and probably did not exist).
*/
} /* else: we had sent one packet, but got no reply */
log1s("no response to renew");
if (lease_remaining > 30) {
/* Some lease time remains, try to renew later */
change_listen_mode(LISTEN_NONE);
goto BOUND_for_half_lease;
}
/* Enter rebinding state */
client_data.state = REBINDING;
log1s("entering rebinding state");
/* Switch to bcast receive */
change_listen_mode(LISTEN_RAW);
packet_num = 0;
/* fall right through */
case REBINDING:
/* Lease is *really* about to run out,
* try to find DHCP server using broadcast */
if (lease_remaining > 0 && packet_num < 3) {
/* send a broadcast renew request */
send_renew(0 /*INADDR_ANY*/, requested_ip);
timeout = discover_timeout;
packet_num++;
continue;
}
/* Timed out, enter init state */
change_listen_mode(LISTEN_NONE);
bb_simple_info_msg("lease lost, entering init state");
d4_run_script_deconfig();
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
timeout = 0;
packet_num = 0;
continue;
/* case RELEASED: */
}
/* RELEASED state (when we got SIGUSR2) ends up here.
* (wait for SIGUSR1 to re-init, or for TERM, etc)
*/
timeout = INT_MAX;
continue; /* back to main loop */
} /* if poll timed out */
/* poll() didn't timeout, something happened */
/* Is it a signal? */
switch (udhcp_sp_read()) {
case SIGUSR1:
if (client_data.state <= REQUESTING)
/* Initial negotiations in progress, do not disturb */
break;
if (client_data.state == REBINDING)
/* Do not go back from rebind to renew state */
break;
if (lease_remaining > 30) /* if renew fails, do not go back to BOUND */
lease_remaining = 30;
client_data.first_secs = 0; /* make secs field count from 0 */
packet_num = 0;
switch (client_data.state) {
case BOUND:
case RENEWING:
/* Try to renew/rebind */
client_data.state = RENEW_REQUESTED;
goto got_SIGUSR1;
case RENEW_REQUESTED:
/* Two SIGUSR1 received, start things over */
change_listen_mode(LISTEN_NONE);
d4_run_script_deconfig();
default:
/* case RELEASED: */
/* Wake from SIGUSR2-induced deconfigured state */
change_listen_mode(LISTEN_NONE);
}
client_data.state = INIT_SELECTING;
/* Kill any timeouts, user wants this to hurry along */
timeout = 0;
continue;
case SIGUSR2:
perform_release(server_id, requested_ip);
/* ^^^ switches to LISTEN_NONE */
timeout = INT_MAX;
continue;
case SIGTERM:
bb_info_msg("received %s", "SIGTERM");
goto ret0;
}
/* Is it a packet? */
if (!pfds[1].revents)
continue; /* no */
{
int len;
/* A packet is ready, read it */
if (client_data.listen_mode == LISTEN_KERNEL)
len = udhcp_recv_kernel_packet(&packet, client_data.sockfd);
else
len = d4_recv_raw_packet(&packet, client_data.sockfd);
if (len == -1) {
/* Error is severe, reopen socket */
bb_error_msg("read error: "STRERROR_FMT", reopening socket" STRERROR_ERRNO);
sleep(discover_timeout); /* 3 seconds by default */
change_listen_mode(client_data.listen_mode); /* just close and reopen */
}
if (len < 0)
continue;
}
if (packet.xid != client_data.xid) {
log1("xid %x (our is %x)%s",
(unsigned)packet.xid, (unsigned)client_data.xid,
", ignoring packet"
);
continue;
}
/* Ignore packets that aren't for us */
if (packet.hlen != 6
|| memcmp(packet.chaddr, client_data.client_mac, 6) != 0
) {
//FIXME: need to also check that last 10 bytes are zero
log1("chaddr does not match%s", ", ignoring packet");
continue;
}
message = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE);
if (message == NULL) {
log1("no message type option%s", ", ignoring packet");
continue;
}
switch (client_data.state) {
case INIT_SELECTING:
/* Must be a DHCPOFFER */
if (*message == DHCPOFFER) {
struct in_addr temp_addr;
uint8_t *temp;
/* What exactly is server's IP? There are several values.
* Example DHCP offer captured with tchdump:
*
* 10.34.25.254:67 > 10.34.25.202:68 // IP header's src
* BOOTP fields:
* Your-IP 10.34.25.202
* Server-IP 10.34.32.125 // "next server" IP
* Gateway-IP 10.34.25.254 // relay's address (if DHCP relays are in use)
* DHCP options:
* DHCP-Message Option 53, length 1: Offer
* Server-ID Option 54, length 4: 10.34.255.7 // "server ID"
* Default-Gateway Option 3, length 4: 10.34.25.254 // router
*
* We think that real server IP (one to use in renew/release)
* is one in Server-ID option. But I am not 100% sure.
* IP header's src and Gateway-IP (same in this example)
* might work too.
* "Next server" and router are definitely wrong ones to use, though...
*/
/* We used to ignore packets without DHCP_SERVER_ID.
* I've got user reports from people who run "address-less" servers.
* They either supply DHCP_SERVER_ID of 0.0.0.0 or don't supply it at all.
* They say ISC DHCP client supports this case.
*/
server_id = 0;
temp = udhcp_get_option32(&packet, DHCP_SERVER_ID);
if (!temp) {
bb_simple_info_msg("no server ID, using 0.0.0.0");
} else {
/* it IS unaligned sometimes, don't "optimize" */
move_from_unaligned32(server_id, temp);
}
/*xid = packet.xid; - already is */
temp_addr.s_addr = requested_ip = packet.yiaddr;
log1("received offer of %s", inet_ntoa(temp_addr));
/* enter requesting state */
client_data.state = REQUESTING;
timeout = 0;
packet_num = 0;
}
continue;
case REQUESTING:
case RENEWING:
case RENEW_REQUESTED:
case REBINDING:
if (*message == DHCPACK) {
unsigned start;
struct in_addr temp_addr;
char server_str[sizeof("255.255.255.255")];
uint8_t *temp;
change_listen_mode(LISTEN_NONE);
temp_addr.s_addr = server_id;
strcpy(server_str, inet_ntoa(temp_addr));
temp_addr.s_addr = packet.yiaddr;
lease_remaining = 60 * 60;
temp = udhcp_get_option32(&packet, DHCP_LEASE_TIME);
if (temp) {
uint32_t lease;
/* it IS unaligned sometimes, don't "optimize" */
move_from_unaligned32(lease, temp);
lease_remaining = ntohl(lease);
}
/* Log message _before_ we sanitize lease */
bb_info_msg("lease of %s obtained from %s, lease time %u%s",
inet_ntoa(temp_addr), server_str, (unsigned)lease_remaining,
temp ? "" : " (default)"
);
/* paranoia: must not be too small and not prone to overflows */
/* NB: 60s leases _are_ used in real world
* (temporary IPs while ISP modem initializes)
* do not break this case by bumping it up.
*/
if (lease_remaining < 0) /* signed overflow? */
lease_remaining = INT_MAX;
if (lease_remaining < 30)
lease_remaining = 30;
requested_ip = packet.yiaddr;
#if ENABLE_FEATURE_UDHCPC_ARPING
if (opt & OPT_a) {
/* RFC 2131 3.1 paragraph 5:
* "The client receives the DHCPACK message with configuration
* parameters. The client SHOULD perform a final check on the
* parameters (e.g., ARP for allocated network address), and notes
* the duration of the lease specified in the DHCPACK message. At this
* point, the client is configured. If the client detects that the
* address is already in use (e.g., through the use of ARP),
* the client MUST send a DHCPDECLINE message to the server and restarts
* the configuration process..." */
if (!arpping(requested_ip,
NULL,
(uint32_t) 0,
client_data.client_mac,
client_data.interface,
arpping_ms)
) {
bb_simple_info_msg("offered address is in use "
"(got ARP reply), declining");
client_data.xid = random_xid(); //TODO: can omit?
send_decline(server_id, packet.yiaddr);
if (client_data.state != REQUESTING)
d4_run_script_deconfig();
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
requested_ip = 0;
timeout = tryagain_timeout;
packet_num = 0;
continue; /* back to main loop */
}
}
#endif
/* enter bound state */
start = monotonic_sec();
d4_run_script(&packet, client_data.state == REQUESTING ? "bound" : "renew");
lease_remaining -= (unsigned)monotonic_sec() - start;
if (lease_remaining < 0)
lease_remaining = 0;
if (opt & OPT_q) { /* quit after lease */
goto ret0;
}
/* future renew failures should not exit (JM) */
opt &= ~OPT_n;
#if BB_MMU /* NOMMU case backgrounded earlier */
if (!(opt & OPT_f)) {
client_background();
/* do not background again! */
opt = ((opt & ~OPT_b) | OPT_f);
}
#endif
BOUND_for_half_lease:
timeout = (unsigned)lease_remaining / 2;
client_data.state = BOUND;
/* make future renew packets use different xid */
/* client_data.xid = random_xid(); ...but why bother? */
packet_num = 0;
continue; /* back to main loop */
}
if (*message == DHCPNAK) {
/* If network has more than one DHCP server,
* "wrong" server can reply first, with a NAK.
* Do not interpret it as a NAK from "our" server.
*/
uint32_t svid = 0; /* we treat no server id as 0.0.0.0 */
uint8_t *temp = udhcp_get_option32(&packet, DHCP_SERVER_ID);
if (temp)
move_from_unaligned32(svid, temp);
if (svid != server_id) {
log1("received DHCP NAK with wrong"
" server ID%s", ", ignoring packet");
continue;
}
/* return to init state */
change_listen_mode(LISTEN_NONE);
bb_info_msg("received %s", "DHCP NAK");
d4_run_script(&packet, "nak");
if (client_data.state != REQUESTING)
d4_run_script_deconfig();
sleep(3); /* avoid excessive network traffic */
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
requested_ip = 0;
timeout = 0;
packet_num = 0;
}
continue;
/* case BOUND: - ignore all packets */
/* case RELEASED: - ignore all packets */
}
/* back to main loop */
} /* for (;;) - main loop ends */
ret0:
if (opt & OPT_R) /* release on quit */
perform_release(server_id, requested_ip);
retval = 0;
ret:
/*if (client_data.pidfile) - remove_pidfile has its own check */
remove_pidfile(client_data.pidfile);
return retval;
}”
最新发布