int ni_dhcp4_fsm_recover_lease(ni_dhcp4_device_t *dev, const ni_dhcp4_request_t *req) { ni_addrconf_lease_t *lease; time_t now = time(NULL), then; /* Don't recover anything if we already have a lease attached. */ if (dev->lease != NULL) return -1; lease = ni_addrconf_lease_file_read(dev->ifname, NI_ADDRCONF_DHCP, AF_INET); if (!lease) return -1; if (lease->state != NI_ADDRCONF_STATE_GRANTED) goto discard; ni_debug_dhcp("trying to recover dhcp4 lease, now inspecting"); then = lease->time_acquired; if (now < then) { ni_debug_dhcp("%s: found time-warped lease (hi, grand-grand-pa)", __FUNCTION__); goto discard; } if (now >= then + lease->dhcp4.lease_time) { ni_debug_dhcp("%s: found expired lease", __FUNCTION__); goto discard; } if ((req->hostname && !ni_string_eq(req->hostname, dev->lease->hostname)) || (req->clientid && !ni_string_eq(req->clientid, dev->lease->dhcp4.client_id))) { ni_debug_dhcp("%s: lease doesn't match request", __FUNCTION__); goto discard; } ni_dhcp4_device_set_lease(dev, lease); if (now >= then + lease->dhcp4.rebind_time) { ni_dhcp4_fsm_rebind(dev); } else if (now >= then + lease->dhcp4.renewal_time) { ni_dhcp4_fsm_renewal(dev); } else { ni_dhcp4_fsm_set_deadline(dev, then + lease->dhcp4.renewal_time); dev->fsm.state = NI_DHCP4_STATE_BOUND; } ni_debug_dhcp("%s: recovered old lease; now in state=%s", dev->ifname, ni_dhcp4_fsm_state_name(dev->fsm.state)); dev->notify = 1; return 0; discard: ni_addrconf_lease_free(lease); return -1; }
int ni_dhcp4_fsm_release(ni_dhcp4_device_t *dev) { if (!dev->config) { ni_debug_dhcp("%s: not configured, dropping lease", dev->ifname); ni_dhcp4_fsm_commit_lease(dev, NULL); return 0; } ni_debug_dhcp("%s: releasing lease", dev->ifname); ni_dhcp4_device_send_message(dev, DHCP4_RELEASE, dev->lease); ni_dhcp4_fsm_commit_lease(dev, NULL); return 0; }
/* * NAKs in different states need to be treated differently. */ static int ni_dhcp4_process_nak(ni_dhcp4_device_t *dev) { switch (dev->fsm.state) { case NI_DHCP4_STATE_BOUND: /* RFC says discard NAKs received in state BOUND */ return 0; default: /* FIXME: how do we handle a NAK response to an INFORM? */ ni_dhcp4_device_drop_lease(dev); break; } /* Move back to state INIT */ ni_dhcp4_fsm_restart(dev); if (dev->dhcp4.nak_backoff == 0) dev->dhcp4.nak_backoff = 1; /* If we constantly get NAKs then we should slowly back off */ ni_debug_dhcp("Received NAK, backing off for %u seconds", dev->dhcp4.nak_backoff); ni_dhcp4_fsm_set_timeout(dev, dev->dhcp4.nak_backoff); dev->dhcp4.nak_backoff *= 2; if (dev->dhcp4.nak_backoff > NAK_BACKOFF_MAX) dev->dhcp4.nak_backoff = NAK_BACKOFF_MAX; return 0; }
void ni_dhcp4_device_free(ni_dhcp4_device_t *dev) { ni_dhcp4_device_t **pos; ni_assert(dev->users == 0); ni_debug_dhcp("%s: Deleting dhcp4 device with index %u", dev->ifname, dev->link.ifindex); ni_dhcp4_device_drop_buffer(dev); ni_dhcp4_device_drop_lease(dev); ni_dhcp4_device_drop_best_offer(dev); ni_dhcp4_device_close(dev); ni_string_free(&dev->system.ifname); ni_string_free(&dev->ifname); /* Drop existing config and request */ ni_dhcp4_device_set_config(dev, NULL); ni_dhcp4_device_set_request(dev, NULL); for (pos = &ni_dhcp4_active; *pos; pos = &(*pos)->next) { if (*pos == dev) { *pos = dev->next; break; } } free(dev); }
void ni_dhcp4_fsm_set_timeout_msec(ni_dhcp4_device_t *dev, unsigned int msec) { if (msec != 0) { ni_debug_dhcp("%s: setting timeout to %u msec", dev->ifname, msec); if (dev->fsm.timer) ni_timer_rearm(dev->fsm.timer, msec); else dev->fsm.timer = ni_timer_register(msec, __ni_dhcp4_fsm_timeout, dev); } }
void ni_dhcp4_fsm_process_arp_packet(ni_arp_socket_t *arph, const ni_arp_packet_t *pkt, void *user_data) { ni_dhcp4_device_t *dev = user_data; ni_netconfig_t *nc = ni_global_state_handle(0); const ni_netdev_t *ifp; ni_bool_t false_alarm = FALSE; ni_bool_t found_addr = FALSE; if (!pkt || pkt->op != ARPOP_REPLY || !dev || !dev->lease) return; /* Is it about the address we're validating at all? */ if (pkt->sip.s_addr != dev->lease->dhcp4.address.s_addr) return; /* Ignore any ARP replies that seem to come from our own * MAC address. Some helpful switches seem to generate * these. */ if (ni_link_address_equal(&dev->system.hwaddr, &pkt->sha)) return; /* As well as ARP replies that seem to come from our own * host: dup if same address, not a dup if there are two * interfaces connected to the same broadcast domain. */ for (ifp = ni_netconfig_devlist(nc); ifp; ifp = ifp->next) { if (ifp->link.ifindex == dev->link.ifindex) continue; if (!ni_netdev_link_is_up(ifp)) continue; if (!ni_link_address_equal(&ifp->link.hwaddr, &pkt->sha)) continue; /* OK, we have an interface matching the hwaddr, * which will answer arp requests when it is on * the same broadcast domain and causes a false * alarm, except it really has the IP assigned. */ false_alarm = TRUE; if (__ni_dhcp4_address_on_device(ifp, pkt->sip)) found_addr = TRUE; } if (false_alarm && !found_addr) return; ni_debug_dhcp("%s: address %s already in use by %s", dev->ifname, inet_ntoa(pkt->sip), ni_link_address_print(&pkt->sha)); ni_dhcp4_device_arp_close(dev); ni_dhcp4_fsm_decline(dev); }
int ni_dhcp4_fsm_arp_validate(ni_dhcp4_device_t *dev) { struct in_addr null = { 0 }; struct in_addr claim; if (!dev || !dev->lease) return -1; claim = dev->lease->dhcp4.address; if (dev->arp.handle == NULL) { dev->arp.handle = ni_arp_socket_open(&dev->system, ni_dhcp4_fsm_process_arp_packet, dev); if (!dev->arp.handle->user_data) { ni_error("%s: unable to create ARP handle", dev->ifname); return -1; } } if (dev->arp.nprobes) { ni_debug_dhcp("%s: arp validate: probing for %s", dev->ifname, inet_ntoa(claim)); ni_arp_send_request(dev->arp.handle, null, claim); dev->arp.nprobes--; } else if (dev->arp.nclaims) { ni_debug_dhcp("%s: arp validate: claiming %s", dev->ifname, inet_ntoa(claim)); ni_arp_send_grat_request(dev->arp.handle, claim); dev->arp.nclaims--; } else { /* Wow, we're done! */ ni_info("%s: Successfully validated DHCPv4 address %s", dev->ifname, inet_ntoa(claim)); ni_dhcp4_fsm_commit_lease(dev, dev->lease); ni_dhcp4_device_arp_close(dev); return 0; } ni_dhcp4_fsm_set_timeout_msec(dev, NI_DHCP4_ARP_TIMEOUT); return 0; }
void ni_dhcp4_fsm_fail_lease(ni_dhcp4_device_t *dev) { ni_debug_dhcp("%s: failing lease", dev->ifname); ni_dhcp4_fsm_restart(dev); ni_capture_free(dev->capture); dev->capture = NULL; ni_dhcp4_device_set_lease(dev, NULL); dev->notify = 1; dev->failed = 1; }
/* * Remove a lease file */ static void __ni_addrconf_lease_file_remove(const char *dir, const char *ifname, int type, int family) { char *filename = NULL; if (!__ni_addrconf_lease_file_path(&filename, dir, ifname, type, family)) return; if (ni_file_exists(filename) && unlink(filename) == 0) ni_debug_dhcp("removed %s", filename); ni_string_free(&filename); }
int ni_dhcp4_fsm_validate_lease(ni_dhcp4_device_t *dev, ni_addrconf_lease_t *lease) { /* * When the address is already set on the link, we * don't need to validate it and just commit it. */ if (__ni_dhcp4_address_on_link(dev, lease->dhcp4.address)) { ni_debug_dhcp("%s: address %s is on link, omit validation", dev->ifname, inet_ntoa(lease->dhcp4.address)); ni_dhcp4_fsm_commit_lease(dev, lease); return 0; } ni_info("%s: Validating DHCPv4 address %s", dev->ifname, inet_ntoa(lease->dhcp4.address)); /* For ARP validations, we will send 3 ARP queries with a timeout * of 200ms each. * The "claims" part is really for IPv4LL */ dev->arp.nprobes = 3; dev->arp.nclaims = 0; /* dhcp4cd source code says: * IEEE1394 cannot set ARP target address according to RFC2734 */ if (dev->system.hwaddr.type == ARPHRD_IEEE1394) dev->arp.nclaims = 0; if (ni_dhcp4_fsm_arp_validate(dev) < 0) { ni_debug_dhcp("%s: unable to validate lease", dev->ifname); return -1; } dev->fsm.state = NI_DHCP4_STATE_VALIDATING; return 0; }
static void ni_dhcp4_tester_protocol_event(enum ni_dhcp4_event ev, const ni_dhcp4_device_t *dev, ni_addrconf_lease_t *lease) { ni_debug_dhcp("%s(ev=%u, dev=%s[%u], config-uuid=%s)", __func__, ev, dev->ifname, dev->link.ifindex, dev->config ? ni_uuid_print(&dev->config->uuid) : "<none>"); switch (ev) { case NI_DHCP4_EVENT_ACQUIRED: if (lease && lease->state == NI_ADDRCONF_STATE_GRANTED) { FILE *fp = stdout; if (dhcp4_tester_opts.output != NULL) { fp = fopen(dhcp4_tester_opts.output, "w"); if (!fp) { ni_error("Cannot open %s for output", dhcp4_tester_opts.output); dhcp4_tester_status = NI_WICKED_RC_ERROR; return; } } if (dhcp4_tester_opts.outfmt == NI_DHCP4_TESTER_OUT_LEASE_XML) { xml_node_t *xml = NULL; if (ni_addrconf_lease_to_xml(lease, &xml, dev->ifname) != 0) { if (dhcp4_tester_opts.output) fclose(fp); dhcp4_tester_status = NI_WICKED_RC_ERROR; return; } xml_node_print(xml, fp); xml_node_free(xml); } else { ni_leaseinfo_dump(fp, lease, dev->ifname, NULL); } fflush(fp); if (dhcp4_tester_opts.output) fclose(fp); dhcp4_tester_status = NI_WICKED_RC_SUCCESS; } break; default: break; } }
int ni_dhcp4_fsm_reboot(ni_dhcp4_device_t *dev) { time_t deadline; int rv; /* RFC 2131, 3.2 (see also 3.1) */ ni_debug_dhcp("trying to confirm lease for %s", dev->ifname); dev->fsm.state = NI_DHCP4_STATE_REBOOT; rv = ni_dhcp4_device_send_message(dev, DHCP4_REQUEST, dev->lease); deadline = time(NULL) + 10; if (deadline > dev->lease->time_acquired + dev->lease->dhcp4.rebind_time) deadline = dev->lease->time_acquired + dev->lease->dhcp4.rebind_time; ni_dhcp4_fsm_set_deadline(dev, deadline); return rv; }
static int ni_dhcp_option_get_printable(ni_buffer_t *bp, char **var, const char *what) { unsigned int len; char *tmp = NULL; if (ni_dhcp_option_get_string(bp, &tmp, &len) < 0) return -1; if (!ni_check_printable(tmp, len)) { ni_debug_dhcp("Discarded non-printable %s: %s", what, ni_print_suspect(tmp, len)); free(tmp); return -1; } if (*var) free(*var); *var = tmp; return 0; }
int __ni_dhcp4_fsm_discover(ni_dhcp4_device_t *dev, int scan_offers) { ni_addrconf_lease_t *lease; int rv; ni_info("%s: Initiating DHCPv4 discovery (ifindex %d)", dev->ifname, dev->link.ifindex); /* If we already have a lease, try asking for the same. * If not, create a dummy lease with NULL fields. * Note: if DISCOVER for the old lease times out, * we should fall back to asking for anything. */ if ((lease = dev->lease) == NULL) lease = ni_addrconf_lease_new(NI_ADDRCONF_DHCP, AF_INET); lease->uuid = dev->config->uuid; dev->fsm.state = NI_DHCP4_STATE_SELECTING; rv = ni_dhcp4_device_send_message(dev, DHCP4_DISCOVER, lease); dev->dhcp4.accept_any_offer = 1; ni_debug_dhcp("valid lease: %d; have prefs: %d", ni_addrconf_lease_is_valid(dev->lease), ni_dhcp4_config_have_server_preference()); if (ni_addrconf_lease_is_valid(dev->lease) || (scan_offers && ni_dhcp4_config_have_server_preference())) { ni_dhcp4_fsm_set_timeout(dev, dev->config->initial_discovery_timeout); dev->dhcp4.accept_any_offer = 0; } else { ni_dhcp4_fsm_set_timeout(dev, dev->config->request_timeout); } ni_dhcp4_device_drop_best_offer(dev); if (lease != dev->lease) ni_addrconf_lease_free(lease); return rv; }
static int ni_dhcp4_process_ack(ni_dhcp4_device_t *dev, ni_addrconf_lease_t *lease) { if (lease->dhcp4.lease_time == 0) { lease->dhcp4.lease_time = DHCP4_DEFAULT_LEASETIME; ni_debug_dhcp("server supplied no lease time, assuming %u seconds", lease->dhcp4.lease_time); } if (lease->dhcp4.rebind_time >= lease->dhcp4.lease_time) { ni_debug_dhcp("%s: dhcp4.rebind_time greater than dhcp4.lease_time, using default", dev->ifname); lease->dhcp4.rebind_time = lease->dhcp4.lease_time * 7 / 8; } else if (lease->dhcp4.rebind_time == 0) { ni_debug_dhcp("%s: no dhcp4.rebind_time supplied, using default", dev->ifname); lease->dhcp4.rebind_time = lease->dhcp4.lease_time * 7 / 8; } if (lease->dhcp4.renewal_time >= lease->dhcp4.rebind_time) { ni_debug_dhcp("%s: dhcp4.renewal_time greater than dhcp4.rebind_time, using default", dev->ifname); lease->dhcp4.renewal_time = lease->dhcp4.lease_time / 2; } else if (lease->dhcp4.renewal_time == 0) { ni_debug_dhcp("%s: no dhcp4.renewal_time supplied, using default", dev->ifname); lease->dhcp4.renewal_time = lease->dhcp4.lease_time / 2; } if (lease->dhcp4.renewal_time > dev->config->max_lease_time) { ni_debug_dhcp("clamping lease time to %u sec", dev->config->max_lease_time); lease->dhcp4.renewal_time = dev->config->max_lease_time; } /* set lease to validate and commit or decline */ ni_dhcp4_device_set_lease(dev, lease); if (dev->config->doflags & DHCP4_DO_ARP) { /* * When we cannot init validate [arp], commit it. */ if (ni_dhcp4_fsm_validate_lease(dev, lease) < 0) ni_dhcp4_fsm_commit_lease(dev, lease); } else { ni_dhcp4_fsm_commit_lease(dev, lease); } return 0; }
static int ni_dhcp_decode_sipservers(ni_buffer_t *bp, ni_string_array_t *list) { int encoding; encoding = ni_buffer_getc(bp); switch (encoding) { case -1: ni_debug_dhcp("%s: missing data", __FUNCTION__); return -1; case 0: return ni_dhcp_decode_dnssearch(bp, list, "sip-server name"); case 1: return ni_dhcp_decode_address_list(bp, list); default: ni_error("unknown sip encoding %d", encoding); return -1; } return 0; }
/* * We never received any response. Deal with the traumatic rejection. */ static void ni_dhcp4_fsm_timeout(ni_dhcp4_device_t *dev) { ni_debug_dhcp("%s: timeout in state %s", dev->ifname, ni_dhcp4_fsm_state_name(dev->fsm.state)); dev->fsm.timer = NULL; switch (dev->fsm.state) { case NI_DHCP4_STATE_INIT: /* We get here if we previously received a NAK, and have * started to back off, or if we declined a lease because * the address was already in use. */ ni_dhcp4_device_drop_lease(dev); ni_dhcp4_fsm_discover(dev); break; case NI_DHCP4_STATE_SELECTING: if (!dev->dhcp4.accept_any_offer) { ni_dhcp4_config_t *conf = dev->config; /* We were scanning all offers to check for a best offer. * There was no perfect match, but we may have a "good enough" * match. Check for it. */ if (dev->best_offer.lease) { ni_addrconf_lease_t *lease = dev->best_offer.lease; ni_debug_dhcp("accepting lease offer from %s; server weight=%d", inet_ntoa(lease->dhcp4.server_id), dev->best_offer.weight); ni_dhcp4_process_offer(dev, lease); return; } ni_dhcp4_fsm_fail_lease(dev); if (conf->initial_discovery_timeout < conf->request_timeout) { __ni_dhcp4_fsm_discover(dev, 0); return; } } /* fallthrough */ case NI_DHCP4_STATE_REQUESTING: ni_error("%s: DHCP4 discovery failed", dev->ifname); ni_dhcp4_fsm_fail_lease(dev); ni_dhcp4_fsm_restart(dev); ni_dhcp4_send_event(NI_DHCP4_EVENT_LOST, dev, NULL); /* Now decide whether we should keep trying */ if (dev->config->request_timeout == ~0U) ni_dhcp4_fsm_discover(dev); break; case NI_DHCP4_STATE_VALIDATING: /* Send the next ARP probe */ ni_dhcp4_fsm_arp_validate(dev); break; case NI_DHCP4_STATE_BOUND: ni_dhcp4_fsm_renewal(dev); break; case NI_DHCP4_STATE_RENEWING: ni_error("unable to renew lease within renewal period; trying to rebind"); ni_dhcp4_fsm_rebind(dev); break; case NI_DHCP4_STATE_REBINDING: ni_error("unable to rebind lease"); ni_dhcp4_fsm_restart(dev); /* FIXME: now decide whether we should try to re-discover */ break; case NI_DHCP4_STATE_REBOOT: ni_error("unable to confirm lease"); ni_dhcp4_fsm_commit_lease(dev, NULL); break; default: ; } }
/* * Read a lease from a file */ ni_addrconf_lease_t * ni_addrconf_lease_file_read(const char *ifname, int type, int family) { ni_addrconf_lease_t *lease = NULL; xml_node_t *xml = NULL, *lnode; char *filename = NULL; FILE *fp; if (!__ni_addrconf_lease_file_path(&filename, ni_config_statedir(), ifname, type, family)) { ni_error("Unable to construct lease file name: %m"); return NULL; } if ((fp = fopen(filename, "re")) == NULL) { if (errno == ENOENT) { if (__ni_addrconf_lease_file_path(&filename, ni_config_storedir(), ifname, type, family)) fp = fopen(filename, "re"); } if (fp == NULL) { if (errno != ENOENT) { ni_error("Unable to open %s for reading: %m", filename); } ni_string_free(&filename); return NULL; } } ni_debug_dhcp("Reading lease from %s", filename); xml = xml_node_scan(fp, filename); fclose(fp); if (xml == NULL) { ni_error("Unable to parse %s", filename); ni_string_free(&filename); return NULL; } /* find the lease node already here, so we can report it */ if (!ni_string_eq(xml->name, NI_ADDRCONF_LEASE_XML_NODE)) lnode = xml_node_get_child(xml, NI_ADDRCONF_LEASE_XML_NODE); else lnode = xml; if (!lnode) { ni_error("File '%s' does not contain a valid lease", filename); ni_string_free(&filename); xml_node_free(xml); return NULL; } if (ni_addrconf_lease_from_xml(&lease, xml) < 0) { ni_error("Unable to parse xml lease file '%s'", filename); ni_string_free(&filename); xml_node_free(xml); return NULL; } ni_string_free(&filename); xml_node_free(xml); return lease; }
/* * Write a lease to a file */ int ni_addrconf_lease_file_write(const char *ifname, ni_addrconf_lease_t *lease) { char tempname[PATH_MAX] = {'\0'}; ni_bool_t fallback = FALSE; char *filename = NULL; xml_node_t *xml = NULL; FILE *fp = NULL; int ret = -1; int fd; if (lease->state == NI_ADDRCONF_STATE_RELEASED) { ni_addrconf_lease_file_remove(ifname, lease->type, lease->family); return 0; } if (!__ni_addrconf_lease_file_path(&filename, ni_config_storedir(), ifname, lease->type, lease->family)) { ni_error("Cannot construct lease file name: %m"); return -1; } ni_debug_dhcp("Preparing xml lease data for '%s'", filename); if ((ret = ni_addrconf_lease_to_xml(lease, &xml)) != 0) { if (ret > 0) { ni_debug_dhcp("Skipped, %s:%s leases are disabled", ni_addrfamily_type_to_name(lease->family), ni_addrconf_type_to_name(lease->type)); } else { ni_error("Unable to represent %s:%s lease as XML", ni_addrfamily_type_to_name(lease->family), ni_addrconf_type_to_name(lease->type)); } goto failed; } snprintf(tempname, sizeof(tempname), "%s.XXXXXX", filename); if ((fd = mkstemp(tempname)) < 0) { if (errno == EROFS && __ni_addrconf_lease_file_path(&filename, ni_config_statedir(), ifname, lease->type, lease->family)) { ni_debug_dhcp("Read-only filesystem, try fallback to %s", filename); snprintf(tempname, sizeof(tempname), "%s.XXXXXX", filename); fd = mkstemp(tempname); fallback = TRUE; } if (fd < 0) { ni_error("Cannot create temporary lease file '%s': %m", tempname); tempname[0] = '\0'; ret = -1; goto failed; } } if ((fp = fdopen(fd, "we")) == NULL) { ret = -1; close(fd); ni_error("Cannot reopen temporary lease file '%s': %m", tempname); goto failed; } ni_debug_dhcp("Writing lease to temporary file for '%s'", filename); xml_node_print(xml, fp); fclose(fp); xml_node_free(xml); if ((ret = rename(tempname, filename)) != 0) { ni_error("Unable to rename temporary lease file '%s' to '%s': %m", tempname, filename); goto failed; } else if (!fallback) { __ni_addrconf_lease_file_remove(ni_config_statedir(), ifname, lease->type, lease->family); } ni_debug_dhcp("Lease written to file '%s'", filename); ni_string_free(&filename); return 0; failed: if (fp) fclose(fp); if (xml) xml_node_free(xml); if (tempname[0]) unlink(tempname); ni_string_free(&filename); return -1; }
/* * Parse a DHCP response. * FIXME: RFC2131 states that the server is allowed to split a DHCP option into * several (partial) options if the total length exceeds 255 octets. We don't * handle this yet. */ int ni_dhcp_parse_response(const ni_dhcp_message_t *message, ni_buffer_t *options, ni_addrconf_lease_t **leasep) { ni_buffer_t overload_buf; ni_addrconf_lease_t *lease; ni_route_array_t default_routes = NI_ROUTE_ARRAY_INIT; ni_route_array_t static_routes = NI_ROUTE_ARRAY_INIT; ni_route_array_t classless_routes = NI_ROUTE_ARRAY_INIT; ni_string_array_t dns_servers = NI_STRING_ARRAY_INIT; ni_string_array_t dns_search = NI_STRING_ARRAY_INIT; ni_string_array_t nis_servers = NI_STRING_ARRAY_INIT; char *nisdomain = NULL; char *dnsdomain = NULL; int opt_overload = 0; int msg_type = -1; int use_bootserver = 1; int use_bootfile = 1; lease = ni_addrconf_lease_new(NI_ADDRCONF_DHCP, AF_INET); lease->state = NI_ADDRCONF_STATE_GRANTED; lease->type = NI_ADDRCONF_DHCP; lease->family = AF_INET; lease->time_acquired = time(NULL); lease->dhcp.address.s_addr = message->yiaddr; lease->dhcp.serveraddress.s_addr = message->siaddr; lease->dhcp.address.s_addr = message->yiaddr; parse_more: /* Loop as long as we still have data in the buffer. */ while (ni_buffer_count(options) && !options->underflow) { ni_buffer_t buf; int option; option = ni_dhcp_option_next(options, &buf); //ni_debug_dhcp("handle option %s (%d)", ni_dhcp_option_name(option), option); if (option == DHCP_PAD) continue; if (option == DHCP_END) break; if (option < 0) goto error; if (ni_buffer_count(&buf) == 0) { ni_error("option %d has zero length", option); goto error; } switch (option) { case DHCP_MESSAGETYPE: msg_type = ni_buffer_getc(&buf); if (msg_type < 0) goto error; continue; case DHCP_ADDRESS: ni_dhcp_option_get_ipv4(&buf, &lease->dhcp.address); break; case DHCP_NETMASK: ni_dhcp_option_get_ipv4(&buf, &lease->dhcp.netmask); break; case DHCP_BROADCAST: ni_dhcp_option_get_ipv4(&buf, &lease->dhcp.broadcast); break; case DHCP_SERVERIDENTIFIER: ni_dhcp_option_get_ipv4(&buf, &lease->dhcp.serveraddress); break; case DHCP_LEASETIME: ni_dhcp_option_get32(&buf, &lease->dhcp.lease_time); break; case DHCP_RENEWALTIME: ni_dhcp_option_get32(&buf, &lease->dhcp.renewal_time); break; case DHCP_REBINDTIME: ni_dhcp_option_get32(&buf, &lease->dhcp.rebind_time); break; case DHCP_MTU: ni_dhcp_option_get16(&buf, &lease->dhcp.mtu); /* Minimum legal mtu is 68 accoridng to * RFC 2132. In practise it's 576 which is the * minimum maximum message size. */ if (lease->dhcp.mtu < MTU_MIN) { ni_debug_dhcp("MTU %u is too low, minimum is %d; ignoring", lease->dhcp.mtu, MTU_MIN); lease->dhcp.mtu = 0; } break; case DHCP_HOSTNAME: ni_dhcp_option_get_domain(&buf, &lease->hostname, "hostname"); break; case DHCP_DNSDOMAIN: ni_dhcp_option_get_domain(&buf, &dnsdomain, "dns-domain"); break; case DHCP_MESSAGE: ni_dhcp_option_get_printable(&buf, &lease->dhcp.message, "dhcp-message"); break; case DHCP_ROOTPATH: ni_dhcp_option_get_pathname(&buf, &lease->dhcp.rootpath, "root-path"); break; case DHCP_NISDOMAIN: ni_dhcp_option_get_domain(&buf, &nisdomain, "nis-domain"); break; case DHCP_NETBIOSNODETYPE: ni_dhcp_option_get_netbios_type(&buf, &lease->netbios_type); break; case DHCP_NETBIOSSCOPE: ni_dhcp_option_get_domain(&buf, &lease->netbios_scope, "netbios-scope"); break; case DHCP_DNSSERVER: ni_dhcp_decode_address_list(&buf, &dns_servers); break; case DHCP_NTPSERVER: ni_dhcp_decode_address_list(&buf, &lease->ntp_servers); break; case DHCP_NISSERVER: ni_dhcp_decode_address_list(&buf, &nis_servers); break; case DHCP_LPRSERVER: ni_dhcp_decode_address_list(&buf, &lease->lpr_servers); break; case DHCP_LOGSERVER: ni_dhcp_decode_address_list(&buf, &lease->log_servers); break; case DHCP_NETBIOSNAMESERVER: ni_dhcp_decode_address_list(&buf, &lease->netbios_name_servers); break; case DHCP_NETBIOSDDSERVER: ni_dhcp_decode_address_list(&buf, &lease->netbios_dd_servers); break; case DHCP_DNSSEARCH: ni_dhcp_decode_dnssearch(&buf, &dns_search, "dns-search domain"); break; case DHCP_CSR: case DHCP_MSCSR: ni_route_array_destroy(&classless_routes); if (ni_dhcp_decode_csr(&buf, &classless_routes) < 0) goto error; break; case DHCP_SIPSERVER: ni_dhcp_decode_sipservers(&buf, &lease->sip_servers); break; case DHCP_STATICROUTE: ni_route_array_destroy(&static_routes); if (ni_dhcp_decode_static_routes(&buf, &static_routes) < 0) goto error; break; case DHCP_ROUTERS: ni_route_array_destroy(&default_routes); if (ni_dhcp_decode_routers(&buf, &default_routes) < 0) goto error; break; case DHCP_OPTIONSOVERLOADED: if (options != &overload_buf) { opt_overload = ni_buffer_getc(&buf); } else { ni_debug_dhcp("DHCP: ignoring OVERLOAD option in overloaded data"); (void) ni_buffer_getc(&buf); } break; case DHCP_FQDN: /* We ignore replies about FQDN */ break; default: ni_debug_dhcp("ignoring unsupported DHCP code %u", option); break; } if (buf.underflow) { ni_debug_dhcp("unable to parse DHCP option %s: too short", ni_dhcp_option_name(option)); goto error; } else if (ni_buffer_count(&buf)) { ni_debug_dhcp("excess data in DHCP option %s - %u bytes left", ni_dhcp_option_name(option), ni_buffer_count(&buf)); } } if (options->underflow) { ni_debug_dhcp("unable to parse DHCP response: truncated packet"); goto error; } if (opt_overload) { const void *more_data = NULL; size_t size = 0; if (opt_overload & DHCP_OVERLOAD_BOOTFILE) { use_bootfile = 0; more_data = message->bootfile; size = sizeof(message->bootfile); opt_overload &= ~DHCP_OVERLOAD_BOOTFILE; } else if (opt_overload & DHCP_OVERLOAD_SERVERNAME) { use_bootserver = 0; more_data = message->servername; size = sizeof(message->servername); opt_overload &= ~DHCP_OVERLOAD_SERVERNAME; } else { opt_overload = 0; } if (more_data) { ni_buffer_init_reader(&overload_buf, (void *) more_data, size); options = &overload_buf; goto parse_more; } } if (use_bootserver && message->servername[0]) { char tmp[sizeof(message->servername)]; size_t len; assert(sizeof(lease->dhcp.servername) == sizeof(message->servername)); memcpy(tmp, message->servername, sizeof(tmp)); tmp[sizeof(tmp)-1] = '\0'; len = ni_string_len(tmp); if (ni_check_domain_name(tmp, len, 0)) { memcpy(lease->dhcp.servername, tmp, sizeof(lease->dhcp.servername)); } else { ni_debug_dhcp("Discarded suspect boot-server name: %s", ni_print_suspect(tmp, len)); } } if (use_bootfile && message->bootfile[0]) { char tmp[sizeof(message->bootfile)]; size_t len; memcpy(tmp, message->bootfile, sizeof(tmp)); tmp[sizeof(tmp)-1] = '\0'; len = ni_string_len(tmp); if (ni_check_pathname(tmp, len)) { ni_string_dup(&lease->dhcp.bootfile, tmp); } else { ni_debug_dhcp("Discarded suspect boot-file name: %s", ni_print_suspect(tmp, len)); } } /* Fill in any missing fields */ if (!lease->dhcp.netmask.s_addr) { unsigned int pfxlen = guess_prefix_len(lease->dhcp.address); lease->dhcp.netmask.s_addr = htonl(~(0xFFFFFFFF >> pfxlen)); }
/* * Process a request to reconfigure the device (ie rebind a lease, or discover * a new lease). */ int ni_dhcp4_acquire(ni_dhcp4_device_t *dev, const ni_dhcp4_request_t *info) { ni_dhcp4_config_t *config; const char *classid; size_t len; int rv; if ((rv = ni_dhcp4_device_refresh(dev)) < 0) return rv; config = xcalloc(1, sizeof(*config)); config->dry_run = info->dry_run; config->resend_timeout = NI_DHCP4_RESEND_TIMEOUT_INIT; config->request_timeout = info->acquire_timeout?: NI_DHCP4_REQUEST_TIMEOUT; config->initial_discovery_timeout = NI_DHCP4_DISCOVERY_TIMEOUT; config->uuid = info->uuid; config->flags = info->flags; config->update = info->update; config->route_priority = info->route_priority; config->start_delay = info->start_delay; config->recover_lease = info->recover_lease; config->release_lease = info->release_lease; config->max_lease_time = ni_dhcp4_config_max_lease_time(); if (config->max_lease_time == 0) config->max_lease_time = ~0U; if (info->lease_time && info->lease_time < config->max_lease_time) config->max_lease_time = info->lease_time; if ((len = ni_string_len(info->hostname)) > 0) { if (ni_check_domain_name(info->hostname, len, 0)) { strncpy(config->hostname, info->hostname, sizeof(config->hostname) - 1); } else { ni_debug_dhcp("Discarded request to use suspect hostname: '%s'", ni_print_suspect(info->hostname, len)); } } if (info->clientid) { ni_dhcp4_parse_client_id(&config->client_id, dev->system.hwaddr.type, info->clientid); } else { /* Set client ID from interface hwaddr */ ni_dhcp4_set_client_id(&config->client_id, &dev->system.hwaddr); } if ((classid = info->vendor_class) == NULL) classid = ni_dhcp4_config_vendor_class(); if (classid) strncpy(config->classid, classid, sizeof(config->classid) - 1); config->doflags = DHCP4_DO_DEFAULT; config->doflags |= ni_dhcp4_do_bits(info->update); if (ni_debug & NI_TRACE_DHCP) { ni_trace("Received request:"); ni_trace(" acquire-timeout %u", config->request_timeout); ni_trace(" lease-time %u", config->max_lease_time); ni_trace(" start-delay %u", config->start_delay); ni_trace(" hostname %s", config->hostname[0]? config->hostname : "<none>"); ni_trace(" vendor-class %s", config->classid[0]? config->classid : "<none>"); ni_trace(" client-id %s", ni_print_hex(config->client_id.data, config->client_id.len)); ni_trace(" uuid %s", ni_uuid_print(&config->uuid)); ni_trace(" update-flags %s", __ni_dhcp4_print_doflags(config->doflags)); ni_trace(" recover_lease %s", config->recover_lease ? "true" : "false"); ni_trace(" release_lease %s", config->release_lease ? "true" : "false"); } ni_dhcp4_device_set_config(dev, config); if (!dev->lease) ni_dhcp4_recover_lease(dev); if (dev->lease) { if (!ni_addrconf_lease_is_valid(dev->lease) || (config->client_id.len && !ni_opaque_eq(&config->client_id, &dev->lease->dhcp4.client_id))) { ni_debug_dhcp("%s: lease doesn't match request", dev->ifname); ni_dhcp4_device_drop_lease(dev); dev->notify = 1; } } ni_note("%s: Request to acquire DHCPv4 lease with UUID %s", dev->ifname, ni_uuid_print(&config->uuid)); if (ni_dhcp4_device_start(dev) < 0) return -1; return 1; }
int ni_dhcp4_fsm_commit_lease(ni_dhcp4_device_t *dev, ni_addrconf_lease_t *lease) { ni_capture_free(dev->capture); dev->capture = NULL; if (lease) { ni_debug_dhcp("%s: committing lease", dev->ifname); if (dev->config->dry_run == NI_DHCP4_RUN_NORMAL) { ni_debug_dhcp("%s: schedule renewal of lease in %u seconds", dev->ifname, lease->dhcp4.renewal_time); ni_dhcp4_fsm_set_timeout(dev, lease->dhcp4.renewal_time); } /* If the user requested a specific route metric, apply it now */ if (dev->config) { ni_route_table_t *tab; ni_route_t *rp; unsigned int i; for (tab = lease->routes; tab; tab = tab->next) { for (i = 0; i < tab->routes.count; ++i) { if ((rp = tab->routes.data[i]) == NULL) continue; rp->protocol = RTPROT_DHCP; rp->priority = dev->config->route_priority; } } } ni_dhcp4_device_set_lease(dev, lease); dev->fsm.state = NI_DHCP4_STATE_BOUND; ni_note("%s: Committed DHCPv4 lease with address %s " "(lease time %u sec, renew in %u sec, rebind in %u sec)", dev->ifname, inet_ntoa(lease->dhcp4.address), lease->dhcp4.lease_time, lease->dhcp4.renewal_time, lease->dhcp4.rebind_time); /* Write the lease to lease cache */ if (dev->config->dry_run != NI_DHCP4_RUN_OFFER) { ni_addrconf_lease_file_write(dev->ifname, lease); } /* Notify anyone who cares that we've (re-)acquired the lease */ ni_dhcp4_send_event(NI_DHCP4_EVENT_ACQUIRED, dev, lease); if (dev->config->dry_run != NI_DHCP4_RUN_NORMAL) { ni_dhcp4_fsm_restart(dev); ni_dhcp4_device_stop(dev); } } else { /* Delete old lease file */ if ((lease = dev->lease) != NULL) { ni_note("%s: Dropped DHCPv4 lease with UUID %s", dev->ifname, ni_uuid_print(&lease->uuid)); lease->state = NI_ADDRCONF_STATE_RELEASED; ni_dhcp4_send_event(NI_DHCP4_EVENT_RELEASED, dev, lease); if (!dev->config || dev->config->dry_run != NI_DHCP4_RUN_OFFER) { ni_addrconf_lease_file_remove(dev->ifname, lease->type, lease->family); } ni_dhcp4_device_drop_lease(dev); } ni_dhcp4_fsm_restart(dev); } return 0; }
int ni_dhcp4_tester_run(ni_dhcp4_tester_t *opts) { ni_netconfig_t *nc; ni_netdev_t *ifp = NULL; ni_dhcp4_device_t *dev = NULL; ni_dhcp4_request_t *req = NULL; unsigned int link_timeout = 20; int rv; if (opts->timeout && opts->timeout != -1U) { link_timeout = (opts->timeout * 2) / 3; opts->timeout -= link_timeout; } if (!opts || ni_string_empty(opts->ifname)) ni_fatal("Invalid start parameters!"); dhcp4_tester_opts = *opts; dhcp4_tester_status = NI_WICKED_RC_ERROR; if (!(nc = ni_global_state_handle(1))) ni_fatal("Cannot refresh interface list!"); if (!(ifp = ni_netdev_by_name(nc, opts->ifname))) ni_fatal("Cannot find interface with name '%s'", opts->ifname); if (!ni_dhcp4_supported(ifp)) ni_fatal("DHCPv4 not supported on '%s'", opts->ifname); if (!(dev = ni_dhcp4_device_new(ifp->name, &ifp->link))) ni_fatal("Cannot allocate dhcp4 client for '%s'", opts->ifname); ni_dhcp4_set_event_handler(ni_dhcp4_tester_protocol_event); if (!(req = ni_dhcp4_request_new())) { ni_error("Cannot allocate dhcp4 request"); goto failure; } if (!ni_dhcp4_tester_req_init(req, opts->request)) goto failure; if (!ni_netdev_link_is_up(ifp)) { ni_netdev_req_t *ifreq; ni_debug_dhcp("%s: Link is not up, trying to bring it up", ifp->name); ifreq = ni_netdev_req_new(); ifreq->ifflags = NI_IFF_LINK_UP | NI_IFF_NETWORK_UP; if ((rv = ni_system_interface_link_change(ifp, ifreq)) < 0) { ni_error("%s: Unable to set up link", ifp->name); ni_netdev_req_free(ifreq); goto failure; } ni_netdev_req_free(ifreq); do { sleep(1); if (!(nc = ni_global_state_handle(1))) goto failure; if (!(ifp = ni_netdev_by_index(nc, dev->link.ifindex))) break; if (ni_netdev_link_is_up(ifp)) break; ni_debug_dhcp("%s: Link is not (yet) up", ifp->name); } while (link_timeout-- > 1); if (!ifp || !ni_netdev_link_is_up(ifp) || !link_timeout) { ni_error("%s: Unable to bring link up", ifp && ifp->name ? ifp->name : dev->ifname); goto failure; } /* Do not try to send too early, even link is reported up now */ sleep(1); } if (opts->timeout && opts->timeout != -1U) req->acquire_timeout = opts->timeout; req->broadcast = opts->broadcast; if ((rv = ni_dhcp4_acquire(dev, req)) < 0) { ni_error("%s: DHCP4v6 acquire request %s failed: %s", dev->ifname, ni_uuid_print(&req->uuid), ni_strerror(rv)); goto failure; } dhcp4_tester_status = NI_WICKED_RC_IN_PROGRESS; while (!ni_caught_terminal_signal()) { long timeout; timeout = ni_timer_next_timeout(); if (ni_socket_wait(timeout) != 0) break; } ni_server_deactivate_interface_events(); ni_socket_deactivate_all(); failure: if (dev) ni_dhcp4_device_put(dev); if (req) ni_dhcp4_request_free(req); return dhcp4_tester_status; }
int ni_dhcp4_fsm_process_dhcp4_packet(ni_dhcp4_device_t *dev, ni_buffer_t *msgbuf) { ni_dhcp4_message_t *message; ni_addrconf_lease_t *lease = NULL; int msg_code; if (dev->fsm.state == NI_DHCP4_STATE_VALIDATING) { /* We arrive here, when some dhcp4 packet arrives after * we've got and processed an ACK already. Just ignore. */ ni_debug_dhcp("%s: ignoring dhcp4 packet arrived in state VALIDATING", dev->ifname); return -1; } if (!(message = ni_buffer_pull_head(msgbuf, sizeof(*message)))) { ni_debug_dhcp("short DHCP4 packet (%u bytes)", ni_buffer_count(msgbuf)); return -1; } if (dev->dhcp4.xid == 0) { ni_debug_dhcp("unexpected packet on %s", dev->ifname); return -1; } if (dev->dhcp4.xid != message->xid) { ni_debug_dhcp("ignoring packet with wrong xid 0x%x (expected 0x%x)", message->xid, dev->dhcp4.xid); return -1; } msg_code = ni_dhcp4_parse_response(message, msgbuf, &lease); if (msg_code < 0) { /* Ignore this message, time out later */ ni_error("unable to parse DHCP4 response"); return -1; } /* set reqest client-id in the response early to have it in test mode */ ni_opaque_set(&lease->dhcp4.client_id, dev->config->client_id.data, dev->config->client_id.len); ni_debug_dhcp("%s: received %s message in state %s", dev->ifname, ni_dhcp4_message_name(msg_code), ni_dhcp4_fsm_state_name(dev->fsm.state)); /* When receiving a DHCP4 OFFER, verify sender address against list of * servers to ignore, and preferred servers. */ if (msg_code == DHCP4_OFFER && dev->fsm.state == NI_DHCP4_STATE_SELECTING) { struct in_addr srv_addr = lease->dhcp4.server_id; int weight = 0; if (ni_dhcp4_config_ignore_server(srv_addr)) { ni_debug_dhcp("%s: ignoring DHCP4 offer from %s", dev->ifname, inet_ntoa(srv_addr)); goto out; } /* If we're scanning all offers, we need to decide whether * this offer is accepted, or whether we want to wait for * more. */ if (!dev->dhcp4.accept_any_offer) { /* Check if we have any preferred servers. */ weight = ni_dhcp4_config_server_preference(srv_addr); /* If we're refreshing an existing lease (eg after link disconnect * and reconnect), we accept the offer if it comes from the same * server as the original one. */ if (dev->lease && dev->lease->dhcp4.server_id.s_addr == srv_addr.s_addr) weight = 100; ni_debug_dhcp("received lease offer from %s; server weight=%d (best offer=%d)", inet_ntoa(lease->dhcp4.server_id), weight, dev->best_offer.weight); /* negative weight means never. */ if (weight < 0) goto out; /* weight between 0 and 100 means maybe. */ if (weight < 100) { if (dev->best_offer.weight < weight) { ni_dhcp4_device_set_best_offer(dev, lease, weight); return 0; } goto out; } /* If the weight has maximum value, just accept this offer. */ } ni_dhcp4_device_set_best_offer(dev, lease, weight); lease = NULL; } /* We've received a valid response; if something goes wrong now * it's nothing that could be fixed by retransmitting the message. * * The only exception would be if we ever do some filtering or * matching of OFFERs - then we would certainly want to keep * waiting for additional packets. */ ni_dhcp4_device_disarm_retransmit(dev); dev->dhcp4.xid = 0; /* move to next stage of protocol */ switch (msg_code) { case DHCP4_OFFER: if (dev->fsm.state != NI_DHCP4_STATE_SELECTING) goto ignore; /* process best offer set above */ ni_dhcp4_process_offer(dev, dev->best_offer.lease); break; case DHCP4_ACK: if (dev->fsm.state == NI_DHCP4_STATE_INIT) { /* * Received a decline ACK -- wait until * timeout before we restart from begin */ ni_dhcp4_device_drop_lease(dev); break; } if (dev->fsm.state != NI_DHCP4_STATE_REQUESTING && dev->fsm.state != NI_DHCP4_STATE_RENEWING && dev->fsm.state != NI_DHCP4_STATE_REBOOT && dev->fsm.state != NI_DHCP4_STATE_REBINDING) goto ignore; ni_dhcp4_process_ack(dev, lease); lease = NULL; break; case DHCP4_NAK: /* The RFC 2131 state diagram says, ignore NAKs in state BOUND. * I guess we also have no use for NAK replies to a DHCP4_DISCOVER */ if (dev->fsm.state == NI_DHCP4_STATE_SELECTING || dev->fsm.state == NI_DHCP4_STATE_BOUND) goto ignore; ni_dhcp4_process_nak(dev); break; default: ignore: ni_debug_dhcp("ignoring %s in state %s", ni_dhcp4_message_name(msg_code), ni_dhcp4_fsm_state_name(dev->fsm.state)); break; } out: if (lease && dev->lease != lease) ni_addrconf_lease_free(lease); /* If we received a message other than NAK, reset the NAK * backoff timer. */ if (msg_code != DHCP4_NAK) dev->dhcp4.nak_backoff = 1; return 0; }
/* * Decode an RFC3397 DNS search order option. */ static int ni_dhcp_decode_dnssearch(ni_buffer_t *optbuf, ni_string_array_t *list, const char *what) { ni_stringbuf_t namebuf = NI_STRINGBUF_INIT_DYNAMIC; unsigned char *base = ni_buffer_head(optbuf); unsigned int base_offset = optbuf->head; size_t len; ni_string_array_destroy(list); while (ni_buffer_count(optbuf) && !optbuf->underflow) { ni_buffer_t *bp = optbuf; ni_buffer_t jumpbuf; while (1) { unsigned int pos = bp->head - base_offset; unsigned int pointer; char label[64]; int length; if ((length = ni_buffer_getc(bp)) < 0) goto failure; /* unexpected EOF */ if (length == 0) break; /* end of this name */ switch (length & 0xC0) { case 0: /* Plain name component */ if (ni_buffer_get(bp, label, length) < 0) goto failure; label[length] = '\0'; if (!ni_stringbuf_empty(&namebuf)) ni_stringbuf_putc(&namebuf, '.'); ni_stringbuf_puts(&namebuf, label); break; case 0xC0: /* Pointer */ pointer = (length & 0x3F) << 8; if ((length = ni_buffer_getc(bp)) < 0) goto failure; pointer |= length; if (pointer >= pos) goto failure; ni_buffer_init_reader(&jumpbuf, base, pos); jumpbuf.head = pointer; bp = &jumpbuf; break; default: goto failure; } } if (!ni_stringbuf_empty(&namebuf)) { len = ni_string_len(namebuf.string); if (ni_check_domain_name(namebuf.string, len, 0)) { ni_string_array_append(list, namebuf.string); } else { ni_debug_dhcp("Discarded suspect %s: %s", what, ni_print_suspect(namebuf.string, len)); } } ni_stringbuf_destroy(&namebuf); } return 0; failure: ni_stringbuf_destroy(&namebuf); ni_string_array_destroy(list); return -1; }