X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fdhcp-client.c;h=a43c01c807eaedd6fc54335e98b5a6e6f237a9f4;hb=ab43666b205d968b4ce7428b9a730b70ff81042f;hp=f6cc67bf8e41bc81b80d2257853ba687eb7fd49b;hpb=489d9ca0be9e824f33c291504b6d6e2dab34d367;p=sliver-openvswitch.git diff --git a/lib/dhcp-client.c b/lib/dhcp-client.c index f6cc67bf8..a43c01c80 100644 --- a/lib/dhcp-client.c +++ b/lib/dhcp-client.c @@ -40,7 +40,9 @@ #include #include #include +#include #include +#include #include "buffer.h" #include "csum.h" #include "dhcp.h" @@ -49,6 +51,8 @@ #include "netdev.h" #include "ofp-print.h" #include "poll-loop.h" +#include "sat-math.h" +#include "timeval.h" #define THIS_MODULE VLM_dhcp_client #include "vlog.h" @@ -69,6 +73,8 @@ enum dhclient_state { #undef DHCLIENT_STATE }; +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60); + static const char * state_name(enum dhclient_state state) { @@ -137,9 +143,6 @@ static unsigned int calc_t2(unsigned int lease); static unsigned int calc_t1(unsigned int lease, unsigned int t2); static unsigned int clamp(unsigned int x, unsigned int min, unsigned int max); -static unsigned int sat_add(unsigned int x, unsigned int y); -static unsigned int sat_sub(unsigned int x, unsigned int y); -static unsigned int sat_mul(unsigned int x, unsigned int y); /* Creates a new DHCP client to configure the network device 'netdev_name' * (e.g. "eth0"). @@ -192,7 +195,7 @@ dhclient_create(const char *netdev_name, cli->aux = aux; cli->netdev = netdev; cli->state = S_RELEASED; - cli->state_entered = time(0); + cli->state_entered = time_now(); cli->xid = random_uint32(); cli->ipaddr = 0; cli->server_ip = 0; @@ -236,7 +239,7 @@ dhclient_release(struct dhclient *cli) static void do_force_renew(struct dhclient *cli, int deadline) { - time_t now = time(0); + time_t now = time_now(); unsigned int lease_left = sat_sub(cli->lease_expiration, now); if (lease_left <= deadline) { if (cli->state & (S_RENEWING | S_REBINDING)) { @@ -302,6 +305,29 @@ dhclient_changed(struct dhclient *cli) return changed; } +/* Returns 'cli''s current state, as a string. The caller must not modify or + * free the string. */ +const char * +dhclient_get_state(const struct dhclient *cli) +{ + return state_name(cli->state); +} + +/* Returns the number of seconds spent so far in 'cli''s current state. */ +unsigned int +dhclient_get_state_elapsed(const struct dhclient *cli) +{ + return elapsed_in_this_state(cli); +} + +/* If 'cli' is bound, returns the number of seconds remaining in its lease; + * otherwise, returns 0. */ +unsigned int +dhclient_get_lease_remaining(const struct dhclient *cli) +{ + return dhclient_is_bound(cli) ? cli->lease_expiration - time_now() : 0; +} + /* If 'cli' is bound to an IP address, returns that IP address; otherwise, * returns 0. */ uint32_t @@ -376,6 +402,86 @@ dhclient_configure_netdev(struct dhclient *cli) return error; } + +/* If 'cli' is bound and the binding includes DNS domain parameters, updates + * /etc/resolv.conf will be updated to match the received parameters. Returns + * 0 if successful, otherwise a positive errno value. */ +int +dhclient_update_resolv_conf(struct dhclient *cli) +{ + uint32_t dns_server; + char *domain_name; + bool has_domain_name; + char new_name[128]; + FILE *old, *new; + int i; + + if (!dhclient_is_bound(cli)) { + return 0; + } + if (!dhcp_msg_get_ip(cli->binding, DHCP_CODE_DNS_SERVER, 0, &dns_server)) { + VLOG_DBG("binding does not include any DNS servers"); + return 0; + } + + sprintf(new_name, "/etc/resolv.conf.tmp%ld", (long int) getpid()); + new = fopen(new_name, "w"); + if (!new) { + VLOG_WARN("%s: create: %s", new_name, strerror(errno)); + return errno; + } + + domain_name = dhcp_msg_get_string(cli->binding, DHCP_CODE_DOMAIN_NAME); + has_domain_name = domain_name != NULL; + if (domain_name) { + if (strspn(domain_name, "-_.0123456789abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == strlen(domain_name)) { + fprintf(new, "domain %s\n", domain_name); + } else { + VLOG_WARN("ignoring invalid domain name %s", domain_name); + has_domain_name = false; + } + } else { + VLOG_DBG("binding does not include domain name"); + } + free(domain_name); + + for (i = 0; dhcp_msg_get_ip(cli->binding, DHCP_CODE_DNS_SERVER, + i, &dns_server); i++) { + fprintf(new, "nameserver "IP_FMT"\n", IP_ARGS(&dns_server)); + } + + old = fopen("/etc/resolv.conf", "r"); + if (old) { + char line[128]; + + while (fgets(line, sizeof line, old)) { + char *kw = xmemdup0(line, strcspn(line, " \t\r\n")); + if (strcmp(kw, "nameserver") + && (!has_domain_name + || (strcmp(kw, "domain") && strcmp(kw, "search")))) { + fputs(line, new); + } + free(kw); + } + fclose(old); + } else { + VLOG_DBG("/etc/resolv.conf: open: %s", strerror(errno)); + } + + if (fclose(new) < 0) { + VLOG_WARN("%s: close: %s", new_name, strerror(errno)); + return errno; + } + + if (rename(new_name, "/etc/resolv.conf") < 0) { + VLOG_WARN("failed to rename %s to /etc/resolv.conf: %s", + new_name, strerror(errno)); + return errno; + } + + return 0; +} /* DHCP protocol. */ @@ -404,7 +510,7 @@ static void do_init(struct dhclient *cli, enum dhclient_state next_state) { if (!cli->init_delay) { - cli->init_delay = clamp(fuzz(2, 8), 1, 10); + cli->init_delay = fuzz(2, 1); } if (timeout(cli, cli->init_delay)) { state_transition(cli, next_state); @@ -437,13 +543,15 @@ dhcp_receive(struct dhclient *cli, unsigned int msgs, struct dhcp_msg *msg) { while (do_receive_msg(cli, msg)) { if (msg->type < 0 || msg->type > 31 || !((1u << msg->type) & msgs)) { - VLOG_DBG("received unexpected %s in %s state: %s", - dhcp_type_name(msg->type), state_name(cli->state), - dhcp_msg_to_string(msg, &cli->s)); + VLOG_DBG_RL(&rl, "received unexpected %s in %s state: %s", + dhcp_type_name(msg->type), state_name(cli->state), + dhcp_msg_to_string(msg, false, &cli->s)); } else if (msg->xid != cli->xid) { - VLOG_DBG("ignoring %s with xid != %08"PRIx32" in %s state: %s", - dhcp_type_name(msg->type), msg->xid, - state_name(cli->state), dhcp_msg_to_string(msg, &cli->s)); + VLOG_DBG_RL(&rl, + "ignoring %s with xid != %08"PRIx32" in %s state: %s", + dhcp_type_name(msg->type), msg->xid, + state_name(cli->state), + dhcp_msg_to_string(msg, false, &cli->s)); } else { return true; } @@ -457,18 +565,18 @@ validate_offered_options(struct dhclient *cli, const struct dhcp_msg *msg) { uint32_t lease, netmask; if (!dhcp_msg_get_secs(msg, DHCP_CODE_LEASE_TIME, 0, &lease)) { - VLOG_WARN("%s lacks lease time: %s", - dhcp_type_name(msg->type), dhcp_msg_to_string(msg, &cli->s)); + VLOG_WARN_RL(&rl, "%s lacks lease time: %s", dhcp_type_name(msg->type), + dhcp_msg_to_string(msg, false, &cli->s)); } else if (!dhcp_msg_get_ip(msg, DHCP_CODE_SUBNET_MASK, 0, &netmask)) { - VLOG_WARN("%s lacks netmask: %s", - dhcp_type_name(msg->type), dhcp_msg_to_string(msg, &cli->s)); + VLOG_WARN_RL(&rl, "%s lacks netmask: %s", dhcp_type_name(msg->type), + dhcp_msg_to_string(msg, false, &cli->s)); } else if (lease < MIN_ACCEPTABLE_LEASE) { - VLOG_WARN("Ignoring %s with %"PRIu32"-second lease time: %s", - dhcp_type_name(msg->type), lease, - dhcp_msg_to_string(msg, &cli->s)); + VLOG_WARN_RL(&rl, "Ignoring %s with %"PRIu32"-second lease time: %s", + dhcp_type_name(msg->type), lease, + dhcp_msg_to_string(msg, false, &cli->s)); } else if (cli->validate_offer && !cli->validate_offer(msg, cli->aux)) { - VLOG_DBG("client validation hook refused offer: %s", - dhcp_msg_to_string(msg, &cli->s)); + VLOG_DBG_RL(&rl, "client validation hook refused offer: %s", + dhcp_msg_to_string(msg, false, &cli->s)); } else { return true; } @@ -491,18 +599,65 @@ dhclient_run_SELECTING(struct dhclient *cli) } if (!dhcp_msg_get_ip(&msg, DHCP_CODE_SERVER_IDENTIFIER, 0, &cli->server_ip)) { - VLOG_WARN("DHCPOFFER lacks server identifier: %s", - dhcp_msg_to_string(&msg, &cli->s)); + VLOG_WARN_RL(&rl, "DHCPOFFER lacks server identifier: %s", + dhcp_msg_to_string(&msg, false, &cli->s)); continue; } - VLOG_DBG("accepting DHCPOFFER: %s", dhcp_msg_to_string(&msg, &cli->s)); + VLOG_DBG_RL(&rl, "accepting DHCPOFFER: %s", + dhcp_msg_to_string(&msg, false, &cli->s)); cli->ipaddr = msg.yiaddr; state_transition(cli, S_REQUESTING); break; } } +static bool +same_binding(const struct dhcp_msg *old, const struct dhcp_msg *new) +{ + static const int codes[] = { + DHCP_CODE_SUBNET_MASK, + DHCP_CODE_ROUTER, + DHCP_CODE_DNS_SERVER, + DHCP_CODE_HOST_NAME, + DHCP_CODE_DOMAIN_NAME, + DHCP_CODE_IP_TTL, + DHCP_CODE_MTU, + DHCP_CODE_BROADCAST_ADDRESS, + DHCP_CODE_STATIC_ROUTE, + DHCP_CODE_ARP_CACHE_TIMEOUT, + DHCP_CODE_ETHERNET_ENCAPSULATION, + DHCP_CODE_TCP_TTL, + DHCP_CODE_SERVER_IDENTIFIER, + DHCP_CODE_OFP_CONTROLLER_VCONN, + DHCP_CODE_OFP_PKI_URI, + }; + int i; + bool same = true; + + if (old->yiaddr != new->yiaddr) { + VLOG_WARN("DHCP binding changed IP address from "IP_FMT" to "IP_FMT, + IP_ARGS(&old->yiaddr), IP_ARGS(&new->yiaddr)); + same = false; + } + for (i = 0; i < ARRAY_SIZE(codes); i++) { + int code = codes[i]; + const struct dhcp_option *old_opt = &old->options[code]; + const struct dhcp_option *new_opt = &new->options[code]; + if (!dhcp_option_equals(old_opt, new_opt)) { + struct ds old_string = DS_EMPTY_INITIALIZER; + struct ds new_string = DS_EMPTY_INITIALIZER; + VLOG_WARN("DHCP binding changed option from %s to %s", + dhcp_option_to_string(old_opt, code, &old_string), + dhcp_option_to_string(new_opt, code, &new_string)); + ds_destroy(&old_string); + ds_destroy(&new_string); + same = false; + } + } + return same; +} + static bool receive_ack(struct dhclient *cli) { @@ -521,6 +676,9 @@ receive_ack(struct dhclient *cli) uint32_t lease = 0, t1 = 0, t2 = 0; if (cli->binding) { + if (!same_binding(cli->binding, &msg)) { + cli->changed = true; + } dhcp_msg_uninit(cli->binding); } else { cli->binding = xmalloc(sizeof *cli->binding); @@ -539,7 +697,7 @@ receive_ack(struct dhclient *cli) t1 = calc_t1(lease, t2); } - cli->lease_expiration = sat_add(time(0), lease); + cli->lease_expiration = sat_add(time_now(), lease); cli->bound_timeout = t1; cli->renewing_timeout = t2 - t1; cli->rebinding_timeout = lease - t2; @@ -550,7 +708,7 @@ receive_ack(struct dhclient *cli) cli->router = INADDR_ANY; } state_transition(cli, S_BOUND); - VLOG_DBG("Bound: %s", dhcp_msg_to_string(&msg, &cli->s)); + VLOG_DBG("Bound: %s", dhcp_msg_to_string(&msg, false, &cli->s)); return true; } } @@ -640,9 +798,11 @@ state_transition(struct dhclient *cli, enum dhclient_state state) { bool was_bound = dhclient_is_bound(cli); bool am_bound; - VLOG_DBG("entering %s", state_name(state)); - cli->state = state; - cli->state_entered = time(0); + if (cli->state != state) { + VLOG_DBG("entering %s", state_name(state)); + cli->state = state; + } + cli->state_entered = time_now(); cli->retransmit = cli->delay = 0; am_bound = dhclient_is_bound(cli); if (was_bound != am_bound) { @@ -698,20 +858,19 @@ dhclient_msg_init(struct dhclient *cli, enum dhcp_msg_type type, msg->secs = cli->secs; msg->type = type; memcpy(msg->chaddr, netdev_get_etheraddr(cli->netdev), ETH_ADDR_LEN); - } static unsigned int elapsed_in_this_state(const struct dhclient *cli) { - return time(0) - cli->state_entered; + return time_now() - cli->state_entered; } static bool timeout(struct dhclient *cli, unsigned int secs) { cli->min_timeout = MIN(cli->min_timeout, secs); - return time(0) >= sat_add(cli->state_entered, secs); + return time_now() >= sat_add(cli->state_entered, secs); } static bool @@ -729,7 +888,7 @@ do_receive_msg(struct dhclient *cli, struct dhcp_msg *msg) buffer_clear(&b); error = netdev_recv(cli->netdev, &b); if (error) { - break; + goto drained; } flow_extract(&b, 0, &flow); @@ -745,25 +904,27 @@ do_receive_msg(struct dhclient *cli, struct dhcp_msg *msg) ip = b.l3; if (IP_IS_FRAGMENT(ip->ip_frag_off)) { /* We don't do reassembly. */ - VLOG_WARN("ignoring fragmented DHCP datagram"); + VLOG_WARN_RL(&rl, "ignoring fragmented DHCP datagram"); continue; } dhcp = b.l7; if (!dhcp) { - VLOG_WARN("ignoring DHCP datagram with missing payload"); + VLOG_WARN_RL(&rl, "ignoring DHCP datagram with missing payload"); continue; } buffer_pull(&b, b.l7 - b.data); error = dhcp_parse(msg, &b); if (!error) { - VLOG_DBG("received %s", dhcp_msg_to_string(msg, &cli->s)); + VLOG_DBG_RL(&rl, "received %s", + dhcp_msg_to_string(msg, false, &cli->s)); buffer_uninit(&b); return true; } } netdev_drain(cli->netdev); +drained: buffer_uninit(&b); return false; } @@ -832,7 +993,7 @@ do_send_msg(struct dhclient *cli, const struct dhcp_msg *msg) * frame to have to be discarded or fragmented if it travels over a regular * Ethernet at some point. 1500 bytes should be enough for anyone. */ if (b.size <= ETH_TOTAL_MAX) { - VLOG_DBG("sending %s", dhcp_msg_to_string(msg, &cli->s)); + VLOG_DBG("sending %s", dhcp_msg_to_string(msg, false, &cli->s)); error = netdev_send(cli->netdev, &b); if (error) { VLOG_ERR("send failed on %s: %s", @@ -854,25 +1015,6 @@ fuzz(unsigned int x, int max_fuzz) return fuzz >= 0 ? (y >= x ? y : UINT_MAX) : (y <= x ? y : 0); } -static unsigned int -sat_add(unsigned int x, unsigned int y) -{ - return x + y >= x ? x + y : UINT_MAX; -} - -static unsigned int -sat_sub(unsigned int x, unsigned int y) -{ - return x >= y ? x - y : 0; -} - -static unsigned int -sat_mul(unsigned int x, unsigned int y) -{ - assert(y); - return x <= UINT_MAX / y ? x * y : UINT_MAX; -} - static unsigned int clamp(unsigned int x, unsigned int min, unsigned int max) {