/* $NetBSD: discover.c,v 1.5 2022/04/03 01:10:58 christos Exp $ */ /* discover.c Find and identify the network interfaces. */ /* * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-2003 by Internet Software Consortium * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Internet Systems Consortium, Inc. * PO Box 360 * Newmarket, NH 03857 USA * * https://www.isc.org/ * */ #include __RCSID("$NetBSD: discover.c,v 1.5 2022/04/03 01:10:58 christos Exp $"); #include "dhcpd.h" /* length of line we can read from the IF file, 256 is too small in some cases */ #define IF_LINE_LENGTH 1024 #define BSD_COMP /* needed on Solaris for SIOCGLIFNUM */ #include #include #ifdef HAVE_NET_IF6_H # include #endif struct interface_info *interfaces = 0; struct interface_info *dummy_interfaces = 0; struct interface_info *fallback_interface = 0; int interfaces_invalidated; int quiet_interface_discovery; u_int16_t local_port = 0; u_int16_t remote_port = 0; u_int16_t relay_port = 0; int dhcpv4_over_dhcpv6 = 0; int (*dhcp_interface_setup_hook) (struct interface_info *, struct iaddr *); int (*dhcp_interface_discovery_hook) (struct interface_info *); isc_result_t (*dhcp_interface_startup_hook) (struct interface_info *); int (*dhcp_interface_shutdown_hook) (struct interface_info *); struct in_addr limited_broadcast; int local_family = AF_INET; struct in_addr local_address; #ifdef DHCPv6 /* * Another clear abuse of the fact that undefined IP addresses are all zeroes. */ struct in6_addr local_address6; int bind_local_address6 = 0; #endif /* DHCPv6 */ void (*bootp_packet_handler) (struct interface_info *, struct dhcp_packet *, unsigned, unsigned int, struct iaddr, struct hardware *); #ifdef DHCPv6 void (*dhcpv6_packet_handler)(struct interface_info *, const char *, int, int, const struct iaddr *, isc_boolean_t); #endif /* DHCPv6 */ omapi_object_type_t *dhcp_type_interface; #if defined (TRACING) trace_type_t *interface_trace; trace_type_t *inpacket_trace; trace_type_t *outpacket_trace; #endif struct interface_info **interface_vector; int interface_count; int interface_max; OMAPI_OBJECT_ALLOC (interface, struct interface_info, dhcp_type_interface) isc_result_t interface_setup () { isc_result_t status; status = omapi_object_type_register (&dhcp_type_interface, "interface", dhcp_interface_set_value, dhcp_interface_get_value, dhcp_interface_destroy, dhcp_interface_signal_handler, dhcp_interface_stuff_values, dhcp_interface_lookup, dhcp_interface_create, dhcp_interface_remove, 0, 0, 0, sizeof (struct interface_info), interface_initialize, RC_MISC); if (status != ISC_R_SUCCESS) log_fatal ("Can't register interface object type: %s", isc_result_totext (status)); return status; } #if defined (TRACING) void interface_trace_setup () { interface_trace = trace_type_register ("interface", (void *)0, trace_interface_input, trace_interface_stop, MDL); inpacket_trace = trace_type_register ("inpacket", (void *)0, trace_inpacket_input, trace_inpacket_stop, MDL); outpacket_trace = trace_type_register ("outpacket", (void *)0, trace_outpacket_input, trace_outpacket_stop, MDL); } #endif isc_result_t interface_initialize (omapi_object_t *ipo, const char *file, int line) { struct interface_info *ip = (struct interface_info *)ipo; ip -> rfdesc = ip -> wfdesc = -1; return ISC_R_SUCCESS; } /* * Scanning for Interfaces * ----------------------- * * To find interfaces, we create an iterator that abstracts out most * of the platform specifics. Use is fairly straightforward: * * - begin_iface_scan() starts the process. * - Use next_iface() until it returns 0. * - end_iface_scan() performs any necessary cleanup. * * We check for errors on each call to next_iface(), which returns a * description of the error as a string if any occurs. * * We currently have code for Solaris and Linux. Other systems need * to have code written. * * NOTE: the long-term goal is to use the interface code from BIND 9. */ #if defined(SIOCGLIFCONF) && defined(SIOCGLIFNUM) && defined(SIOCGLIFFLAGS) /* HP/UX doesn't define struct lifconf, instead they define struct * if_laddrconf. Similarly, 'struct lifreq' and 'struct lifaddrreq'. */ #ifdef ISC_PLATFORM_HAVEIF_LADDRCONF # define lifc_len iflc_len # define lifc_buf iflc_buf # define lifc_req iflc_req # define LIFCONF if_laddrconf #else # define ISC_HAVE_LIFC_FAMILY 1 # define ISC_HAVE_LIFC_FLAGS 1 # define LIFCONF lifconf #endif #ifdef ISC_PLATFORM_HAVEIF_LADDRREQ # define lifr_addr iflr_addr # define lifr_name iflr_name # define lifr_dstaddr iflr_dstaddr # define lifr_flags iflr_flags # define sockaddr_storage sockaddr_ext # define ss_family sa_family # define LIFREQ if_laddrreq #else # define LIFREQ lifreq #endif #ifndef IF_NAMESIZE # if defined(LIFNAMSIZ) # define IF_NAMESIZE LIFNAMSIZ # elif defined(IFNAMSIZ) # define IF_NAMESIZE IFNAMSIZ # else # define IF_NAMESIZE 16 # endif #endif #elif !defined(__linux) && !defined(HAVE_IFADDRS_H) # define SIOCGLIFCONF SIOCGIFCONF # define SIOCGLIFFLAGS SIOCGIFFLAGS # define LIFREQ ifreq # define LIFCONF ifconf # define lifr_name ifr_name # define lifr_addr ifr_addr # define lifr_flags ifr_flags # define lifc_len ifc_len # define lifc_buf ifc_buf # define lifc_req ifc_req #ifdef _AIX # define ss_family __ss_family #endif #endif #if defined(SIOCGLIFCONF) && defined(SIOCGLIFFLAGS) /* * Solaris support * --------------- * * The SIOCGLIFCONF ioctl() are the extension that you need to use * on Solaris to get information about IPv6 addresses. * * Solaris' extended interface is documented in the if_tcp man page. */ /* * Structure holding state about the scan. */ struct iface_conf_list { int sock; /* file descriptor used to get information */ int num; /* total number of interfaces */ struct LIFCONF conf; /* structure used to get information */ int next; /* next interface to retrieve when iterating */ }; /* * Structure used to return information about a specific interface. */ struct iface_info { char name[IF_NAMESIZE+1]; /* name of the interface, e.g. "bge0" */ struct sockaddr_storage addr; /* address information */ isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */ }; /* * Start a scan of interfaces. * * The iface_conf_list structure maintains state for this process. */ static int begin_iface_scan(struct iface_conf_list *ifaces) { #ifdef ISC_PLATFORM_HAVELIFNUM struct lifnum lifnum; #else int lifnum; #endif ifaces->sock = socket(local_family, SOCK_DGRAM, IPPROTO_UDP); if (ifaces->sock < 0) { log_error("Error creating socket to list interfaces; %m"); return 0; } memset(&lifnum, 0, sizeof(lifnum)); #ifdef ISC_PLATFORM_HAVELIFNUM lifnum.lifn_family = AF_UNSPEC; #endif #ifdef SIOCGLIFNUM if (ioctl(ifaces->sock, SIOCGLIFNUM, &lifnum) < 0) { log_error("Error finding total number of interfaces; %m"); close(ifaces->sock); ifaces->sock = -1; return 0; } #ifdef ISC_PLATFORM_HAVELIFNUM ifaces->num = lifnum.lifn_count; #else ifaces->num = lifnum; #endif #else ifaces->num = 64; #endif /* SIOCGLIFNUM */ memset(&ifaces->conf, 0, sizeof(ifaces->conf)); #ifdef ISC_HAVE_LIFC_FAMILY ifaces->conf.lifc_family = AF_UNSPEC; #endif ifaces->conf.lifc_len = ifaces->num * sizeof(struct LIFREQ); ifaces->conf.lifc_buf = dmalloc(ifaces->conf.lifc_len, MDL); if (ifaces->conf.lifc_buf == NULL) { log_fatal("Out of memory getting interface list."); } if (ioctl(ifaces->sock, SIOCGLIFCONF, &ifaces->conf) < 0) { log_error("Error getting interfaces configuration list; %m"); dfree(ifaces->conf.lifc_buf, MDL); close(ifaces->sock); ifaces->sock = -1; return 0; } ifaces->next = 0; return 1; } /* * Retrieve the next interface. * * Returns information in the info structure. * Sets err to 1 if there is an error, otherwise 0. */ static int next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { struct LIFREQ *p; struct LIFREQ tmp; isc_boolean_t foundif; #if defined(sun) || defined(__linux) /* Pointer used to remove interface aliases. */ char *s; #endif do { foundif = ISC_FALSE; if (ifaces->next >= ifaces->num) { *err = 0; return 0; } p = ifaces->conf.lifc_req; p += ifaces->next; if (strlen(p->lifr_name) >= sizeof(info->name)) { *err = 1; log_error("Interface name '%s' too long", p->lifr_name); return 0; } /* Reject if interface address family does not match */ if (p->lifr_addr.ss_family != local_family) { ifaces->next++; continue; } memset(info, 0, sizeof(struct iface_info)); strncpy(info->name, p->lifr_name, sizeof(info->name) - 1); memcpy(&info->addr, &p->lifr_addr, sizeof(p->lifr_addr)); #if defined(sun) || defined(__linux) /* interface aliases look like "eth0:1" or "wlan1:3" */ s = strchr(info->name, ':'); if (s != NULL) { *s = '\0'; } #endif /* defined(sun) || defined(__linux) */ foundif = ISC_TRUE; } while ((foundif == ISC_FALSE) || (strncmp(info->name, "dummy", 5) == 0)); memset(&tmp, 0, sizeof(tmp)); strncpy(tmp.lifr_name, info->name, sizeof(tmp.lifr_name) - 1); if (ioctl(ifaces->sock, SIOCGLIFFLAGS, &tmp) < 0) { log_error("Error getting interface flags for '%s'; %m", p->lifr_name); *err = 1; return 0; } info->flags = tmp.lifr_flags; ifaces->next++; *err = 0; return 1; } /* * End scan of interfaces. */ static void end_iface_scan(struct iface_conf_list *ifaces) { dfree(ifaces->conf.lifc_buf, MDL); close(ifaces->sock); ifaces->sock = -1; } #else /* * BSD/Linux support * ----------- * * FreeBSD, NetBSD, OpenBSD, OS X/macOS and Linux all have the getifaddrs() * function. * * The getifaddrs() man page describes the use. */ #include /* * Structure holding state about the scan. */ struct iface_conf_list { struct ifaddrs *head; /* beginning of the list */ struct ifaddrs *next; /* current position in the list */ }; /* * Structure used to return information about a specific interface. */ struct iface_info { char name[IFNAMSIZ]; /* name of the interface, e.g. "bge0" */ struct sockaddr_storage addr; /* address information */ isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */ }; /* * Start a scan of interfaces. * * The iface_conf_list structure maintains state for this process. */ static int begin_iface_scan(struct iface_conf_list *ifaces) { if (getifaddrs(&ifaces->head) != 0) { log_error("Error getting interfaces; %m"); return 0; } ifaces->next = ifaces->head; return 1; } /* * Retrieve the next interface. * * Returns information in the info structure. * Sets err to 1 if there is an error, otherwise 0. */ static int next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { size_t sa_len = 0; if (ifaces->next == NULL) { *err = 0; return 0; } if (strlen(ifaces->next->ifa_name) >= sizeof(info->name)) { log_error("Interface name '%s' too long", ifaces->next->ifa_name); *err = 1; return 0; } memset(info, 0, sizeof(struct iface_info)); strncpy(info->name, ifaces->next->ifa_name, sizeof(info->name) - 1); memset(&info->addr, 0 , sizeof(info->addr)); /* * getifaddrs() can on Linux with some interfaces like PPP or TEQL * result in a record with no address (ifa_addr). */ if (ifaces->next->ifa_addr != NULL) { /* Linux lacks the sa_len member in struct sockaddr. */ #if defined(__linux) if (ifaces->next->ifa_addr->sa_family == AF_INET) sa_len = sizeof(struct sockaddr_in); else if (ifaces->next->ifa_addr->sa_family == AF_INET6) sa_len = sizeof(struct sockaddr_in6); #else sa_len = ifaces->next->ifa_addr->sa_len; #endif memcpy(&info->addr, ifaces->next->ifa_addr, sa_len); } info->flags = ifaces->next->ifa_flags; ifaces->next = ifaces->next->ifa_next; *err = 0; return 1; } /* * End scan of interfaces. */ static void end_iface_scan(struct iface_conf_list *ifaces) { freeifaddrs(ifaces->head); ifaces->head = NULL; ifaces->next = NULL; } #endif /* XXX: perhaps create drealloc() rather than do it manually */ static void add_ipv4_addr_to_interface(struct interface_info *iface, const struct in_addr *addr) { /* * We don't expect a lot of addresses per IPv4 interface, so * we use 4, as our "chunk size" for collecting addresses. */ if (iface->addresses == NULL) { iface->addresses = dmalloc(4 * sizeof(struct in_addr), MDL); if (iface->addresses == NULL) { log_fatal("Out of memory saving IPv4 address " "on interface."); } iface->address_count = 0; iface->address_max = 4; } else if (iface->address_count >= iface->address_max) { struct in_addr *tmp; int new_max; new_max = iface->address_max + 4; tmp = dmalloc(new_max * sizeof(struct in_addr), MDL); if (tmp == NULL) { log_fatal("Out of memory saving IPv4 address " "on interface."); } memcpy(tmp, iface->addresses, iface->address_max * sizeof(struct in_addr)); dfree(iface->addresses, MDL); iface->addresses = tmp; iface->address_max = new_max; } iface->addresses[iface->address_count++] = *addr; } #ifdef DHCPv6 /* XXX: perhaps create drealloc() rather than do it manually */ static void add_ipv6_addr_to_interface(struct interface_info *iface, const struct in6_addr *addr) { /* * Each IPv6 interface will have at least two IPv6 addresses, * and likely quite a few more. So we use 8, as our "chunk size" for * collecting addresses. */ if (iface->v6addresses == NULL) { iface->v6addresses = dmalloc(8 * sizeof(struct in6_addr), MDL); if (iface->v6addresses == NULL) { log_fatal("Out of memory saving IPv6 address " "on interface."); } iface->v6address_count = 0; iface->v6address_max = 8; } else if (iface->v6address_count >= iface->v6address_max) { struct in6_addr *tmp; int new_max; new_max = iface->v6address_max + 8; tmp = dmalloc(new_max * sizeof(struct in6_addr), MDL); if (tmp == NULL) { log_fatal("Out of memory saving IPv6 address " "on interface."); } memcpy(tmp, iface->v6addresses, iface->v6address_max * sizeof(struct in6_addr)); dfree(iface->v6addresses, MDL); iface->v6addresses = tmp; iface->v6address_max = new_max; } iface->v6addresses[iface->v6address_count++] = *addr; } #endif /* DHCPv6 */ /* Use the SIOCGIFCONF ioctl to get a list of all the attached interfaces. For each interface that's of type INET and not the loopback interface, register that interface with the network I/O software, figure out what subnet it's on, and add it to the list of interfaces. */ void discover_interfaces(int state) { struct iface_conf_list ifaces; struct iface_info info; int err; struct interface_info *tmp; struct interface_info *last, *next; #ifdef DHCPv6 char abuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; #endif /* DHCPv6 */ struct subnet *subnet; int ir; isc_result_t status; int wifcount = 0; #ifdef RELAY_PORT int updone = 0; int downdone = 0; #endif static int setup_fallback = 0; if (!begin_iface_scan(&ifaces)) { log_fatal("Can't get list of interfaces."); } /* If we already have a list of interfaces, and we're running as a DHCP server, the interfaces were requested. */ if (interfaces && (state == DISCOVER_SERVER || state == DISCOVER_RELAY || state == DISCOVER_REQUESTED)) ir = 0; else if (state == DISCOVER_UNCONFIGURED) ir = INTERFACE_REQUESTED | INTERFACE_AUTOMATIC; else { ir = INTERFACE_REQUESTED; if (state == DISCOVER_RELAY && local_family == AF_INET) { /* We're a v4 relay without specifically requested * interfaces, so mark them all as bidirectional. */ ir |= INTERFACE_STREAMS; } } /* Cycle through the list of interfaces looking for IP addresses. */ while (next_iface(&info, &err, &ifaces)) { /* See if we've seen an interface that matches this one. */ for (tmp = interfaces; tmp; tmp = tmp->next) { if (!strcmp(tmp->name, info.name)) break; } /* Skip non broadcast interfaces (plus loopback and point-to-point in case an OS incorrectly marks them as broadcast). Also skip down interfaces unless we're trying to get a list of configurable interfaces. */ if ((((local_family == AF_INET && !(info.flags & IFF_BROADCAST)) || #ifdef DHCPv6 (local_family == AF_INET6 && !(info.flags & IFF_MULTICAST)) || #endif info.flags & IFF_LOOPBACK || info.flags & IFF_POINTOPOINT) && !tmp) || (!(info.flags & IFF_UP) && state != DISCOVER_UNCONFIGURED)) continue; /* If there isn't already an interface by this name, allocate one. */ if (tmp == NULL) { status = interface_allocate(&tmp, MDL); if (status != ISC_R_SUCCESS) { log_fatal("Error allocating interface %s: %s", info.name, isc_result_totext(status)); } memcpy(tmp->name, info.name, sizeof(tmp->name)); interface_snorf(tmp, ir); interface_dereference(&tmp, MDL); tmp = interfaces; /* XXX */ } if (dhcp_interface_discovery_hook) { (*dhcp_interface_discovery_hook)(tmp); } if ((info.addr.ss_family == AF_INET) && (local_family == AF_INET)) { struct sockaddr_in *a = (struct sockaddr_in*)&info.addr; struct iaddr addr; /* We don't want the loopback interface. */ if (a->sin_addr.s_addr == htonl(INADDR_LOOPBACK) && ((tmp->flags & INTERFACE_AUTOMATIC) && ((state == DISCOVER_SERVER) || (state == DISCOVER_SERVER46)))) continue; /* If the only address we have is 0.0.0.0, we shouldn't consider the interface configured. */ if (a->sin_addr.s_addr != htonl(INADDR_ANY)) tmp->configured = 1; add_ipv4_addr_to_interface(tmp, &a->sin_addr); /* invoke the setup hook */ addr.len = 4; memcpy(addr.iabuf, &a->sin_addr.s_addr, addr.len); if (dhcp_interface_setup_hook) { (*dhcp_interface_setup_hook)(tmp, &addr); } } #ifdef DHCPv6 else if ((info.addr.ss_family == AF_INET6) && (local_family == AF_INET6)) { struct sockaddr_in6 *a = (struct sockaddr_in6*)&info.addr; struct iaddr addr; /* We don't want the loopback interface. */ if (IN6_IS_ADDR_LOOPBACK(&a->sin6_addr) && ((tmp->flags & INTERFACE_AUTOMATIC) && ((state == DISCOVER_SERVER) || (state == DISCOVER_SERVER46)))) continue; /* If the only address we have is 0.0.0.0, we shouldn't consider the interface configured. */ if (IN6_IS_ADDR_UNSPECIFIED(&a->sin6_addr)) tmp->configured = 1; add_ipv6_addr_to_interface(tmp, &a->sin6_addr); /* invoke the setup hook */ addr.len = 16; memcpy(addr.iabuf, &a->sin6_addr, addr.len); if (dhcp_interface_setup_hook) { (*dhcp_interface_setup_hook)(tmp, &addr); } } #endif /* DHCPv6 */ } if (err) { log_fatal("Error getting interface information."); } end_iface_scan(&ifaces); /* Mock-up an 'ifp' structure which is no longer used in the * new interface-sensing code, but is used in higher layers * (for example to sense fallback interfaces). */ for (tmp = interfaces ; tmp != NULL ; tmp = tmp->next) { if (tmp->ifp == NULL) { struct ifreq *tif; tif = (struct ifreq *)dmalloc(sizeof(struct ifreq), MDL); if (tif == NULL) log_fatal("no space for ifp mockup."); strcpy(tif->ifr_name, tmp->name); tmp->ifp = tif; } } /* If we're just trying to get a list of interfaces that we might be able to configure, we can quit now. */ if (state == DISCOVER_UNCONFIGURED) { return; } /* Weed out the interfaces that did not have IP addresses. */ tmp = last = next = NULL; if (interfaces) interface_reference (&tmp, interfaces, MDL); while (tmp) { if (next) interface_dereference (&next, MDL); if (tmp -> next) interface_reference (&next, tmp -> next, MDL); /* skip interfaces that are running already */ if (tmp -> flags & INTERFACE_RUNNING) { interface_dereference(&tmp, MDL); if(next) interface_reference(&tmp, next, MDL); continue; } if ((tmp -> flags & INTERFACE_AUTOMATIC) && state == DISCOVER_REQUESTED) tmp -> flags &= ~(INTERFACE_AUTOMATIC | INTERFACE_REQUESTED); #ifdef DHCPv6 if (!(tmp->flags & INTERFACE_REQUESTED)) { #else if (!tmp -> ifp || !(tmp -> flags & INTERFACE_REQUESTED)) { #endif /* DHCPv6 */ if ((tmp -> flags & INTERFACE_REQUESTED) != ir) log_fatal ("%s: not found", tmp -> name); if (!last) { if (interfaces) interface_dereference (&interfaces, MDL); if (next) interface_reference (&interfaces, next, MDL); } else { interface_dereference (&last -> next, MDL); if (next) interface_reference (&last -> next, next, MDL); } if (tmp -> next) interface_dereference (&tmp -> next, MDL); /* Remember the interface in case we need to know about it later. */ if (dummy_interfaces) { interface_reference (&tmp -> next, dummy_interfaces, MDL); interface_dereference (&dummy_interfaces, MDL); } interface_reference (&dummy_interfaces, tmp, MDL); interface_dereference (&tmp, MDL); if (next) interface_reference (&tmp, next, MDL); continue; } last = tmp; /* We must have a subnet declaration for each interface. */ if (!tmp->shared_network && (state == DISCOVER_SERVER)) { log_error("%s", ""); if (local_family == AF_INET) { log_error("No subnet declaration for %s (%s).", tmp->name, (tmp->addresses == NULL) ? "no IPv4 addresses" : inet_ntoa(tmp->addresses[0])); #ifdef DHCPv6 } else { if (tmp->v6addresses != NULL) { inet_ntop(AF_INET6, &tmp->v6addresses[0], abuf, sizeof(abuf)); } else { strcpy(abuf, "no IPv6 addresses"); } log_error("No subnet6 declaration for %s (%s).", tmp->name, abuf); #endif /* DHCPv6 */ } if (supports_multiple_interfaces(tmp)) { log_error ("** Ignoring requests on %s. %s", tmp -> name, "If this is not what"); log_error (" you want, please write %s", #ifdef DHCPv6 (local_family != AF_INET) ? "a subnet6 declaration" : #endif "a subnet declaration"); log_error (" in your dhcpd.conf file %s", "for the network segment"); log_error (" to %s %s %s", "which interface", tmp -> name, "is attached. **"); log_error ("%s", ""); goto next; } else { log_error ("You must write a %s", #ifdef DHCPv6 (local_family != AF_INET) ? "subnet6 declaration for this" : #endif "subnet declaration for this"); log_error ("subnet. You cannot prevent %s", "the DHCP server"); log_error ("from listening on this subnet %s", "because your"); log_fatal ("operating system does not %s.", "support this capability"); } } /* Find subnets that don't have valid interface addresses... */ for (subnet = (tmp -> shared_network ? tmp -> shared_network -> subnets : (struct subnet *)0); subnet; subnet = subnet -> next_sibling) { /* Set the interface address for this subnet to the first address we found. */ if (subnet->interface_address.len == 0) { if (tmp->address_count > 0) { subnet->interface_address.len = 4; memcpy(subnet->interface_address.iabuf, &tmp->addresses[0].s_addr, 4); } else if (tmp->v6address_count > 0) { subnet->interface_address.len = 16; memcpy(subnet->interface_address.iabuf, &tmp->v6addresses[0].s6_addr, 16); } else { /* XXX: should be one */ log_error("%s missing an interface " "address", tmp->name); continue; } } } /* Flag the index as not having been set, so that the interface registerer can set it or not as it chooses. */ tmp -> index = -1; /* Register the interface... */ switch (local_family) { case AF_INET: if (!dhcpv4_over_dhcpv6) { if_register_receive(tmp); if_register_send(tmp); } else { /* get_hw_addr() was called by register. */ get_hw_addr(tmp->name, &tmp->hw_address); } break; #ifdef DHCPv6 case AF_INET6: if ((state == DISCOVER_SERVER) || (state == DISCOVER_RELAY)) { if_register6(tmp, 1); } else if (state == DISCOVER_SERVER46) { /* get_hw_addr() was called by if_register*6 so now we have to call it explicitly to not leave the hardware address unknown (some code expects it cannot be. */ get_hw_addr(tmp->name, &tmp->hw_address); } else { if_register_linklocal6(tmp); } break; #endif /* DHCPv6 */ } interface_stash (tmp); wifcount++; #if defined (F_SETFD) /* if_register*() are no longer always called so descriptors must be checked. */ if ((tmp -> rfdesc >= 0) && (fcntl (tmp -> rfdesc, F_SETFD, 1) < 0)) log_error ("Can't set close-on-exec on %s: %m", tmp -> name); if ((tmp -> wfdesc != tmp -> rfdesc) && (tmp -> wfdesc >= 0) && (fcntl (tmp -> wfdesc, F_SETFD, 1) < 0)) log_error ("Can't set close-on-exec on %s: %m", tmp -> name); #endif next: interface_dereference (&tmp, MDL); if (next) interface_reference (&tmp, next, MDL); } /* * Now register all the remaining interfaces as protocols. * We register with omapi to allow for control of the interface, * we've already registered the fd or socket with the socket * manager as part of if_register_receive(). */ for (tmp = interfaces; tmp; tmp = tmp -> next) { /* not if it's been registered before */ if (tmp -> flags & INTERFACE_RUNNING) continue; if (tmp -> rfdesc == -1) continue; switch (local_family) { #ifdef DHCPv6 case AF_INET6: #ifdef RELAY_PORT #define UPSTREAM(ifp) \ ((ifp->flags & INTERFACE_STREAMS) == INTERFACE_UPSTREAM) #define DOWNSTREAM(ifp) \ ((ifp->flags & INTERFACE_STREAMS) == INTERFACE_DOWNSTREAM) if (relay_port) { /* * The normal IPv6 relay only needs one * socket as long as we find an interface. * When user relay port is defined, and we * have two different UDP ports. One to * receive from DHCP client with port 547, * and the other is user defined for sending * to the server or upstream relay agent. * Thus we need to register sockets for one * upstream and one downstream interfaces. */ if (updone && UPSTREAM(tmp)) continue; if (downdone && DOWNSTREAM(tmp)) continue; } #endif status = omapi_register_io_object((omapi_object_t *)tmp, if_readsocket, 0, got_one_v6, 0, 0); #ifdef RELAY_PORT if (UPSTREAM(tmp)) updone++; else downdone++; #endif break; #endif /* DHCPv6 */ case AF_INET: default: status = omapi_register_io_object((omapi_object_t *)tmp, if_readsocket, 0, got_one, 0, 0); break; } if (status != ISC_R_SUCCESS) log_fatal ("Can't register I/O handle for %s: %s", tmp -> name, isc_result_totext (status)); #if defined(DHCPv6) /* Only register the first interface for V6, since * servers and relays all use the same socket. * XXX: This has some messy side effects if we start * dynamically adding and removing interfaces, but * we're well beyond that point in terms of mess. */ if (((state == DISCOVER_SERVER) || (state == DISCOVER_RELAY)) && (local_family == AF_INET6) #if defined(RELAY_PORT) && ((relay_port == 0) || (updone && downdone)) #endif ) break; #endif } /* for (tmp = interfaces; ... */ if (state == DISCOVER_SERVER && wifcount == 0) { log_info ("%s", ""); log_fatal ("Not configured to listen on any interfaces!"); } if ((local_family == AF_INET) && !setup_fallback && !dhcpv4_over_dhcpv6) { setup_fallback = 1; maybe_setup_fallback(); } #if defined (F_SETFD) if (fallback_interface) { if (fcntl (fallback_interface -> rfdesc, F_SETFD, 1) < 0) log_error ("Can't set close-on-exec on fallback: %m"); if (fallback_interface -> rfdesc != fallback_interface -> wfdesc) { if (fcntl (fallback_interface -> wfdesc, F_SETFD, 1) < 0) log_error ("Can't set close-on-exec on fallback: %m"); } } #endif /* F_SETFD */ } int if_readsocket (h) omapi_object_t *h; { struct interface_info *ip; if (h -> type != dhcp_type_interface) return -1; ip = (struct interface_info *)h; return ip -> rfdesc; } int setup_fallback (struct interface_info **fp, const char *file, int line) { isc_result_t status; status = interface_allocate (&fallback_interface, file, line); if (status != ISC_R_SUCCESS) log_fatal ("Error allocating fallback interface: %s", isc_result_totext (status)); strcpy (fallback_interface -> name, "fallback"); if (dhcp_interface_setup_hook) (*dhcp_interface_setup_hook) (fallback_interface, (struct iaddr *)0); status = interface_reference (fp, fallback_interface, file, line); fallback_interface -> index = -1; interface_stash (fallback_interface); return status == ISC_R_SUCCESS; } void reinitialize_interfaces () { struct interface_info *ip; for (ip = interfaces; ip; ip = ip -> next) { if_reinitialize_receive (ip); if_reinitialize_send (ip); } if (fallback_interface) if_reinitialize_send (fallback_interface); interfaces_invalidated = 1; } isc_result_t got_one (h) omapi_object_t *h; { struct sockaddr_in from; struct hardware hfrom; struct iaddr ifrom; int result; union { unsigned char packbuf [4095]; /* Packet input buffer. Must be as large as largest possible MTU. */ struct dhcp_packet packet; } u; struct interface_info *ip; if (h -> type != dhcp_type_interface) return DHCP_R_INVALIDARG; ip = (struct interface_info *)h; again: if ((result = receive_packet (ip, u.packbuf, sizeof u, &from, &hfrom)) < 0) { log_error ("receive_packet failed on %s: %m", ip -> name); return ISC_R_UNEXPECTED; } if (result == 0) return ISC_R_UNEXPECTED; /* * If we didn't at least get the fixed portion of the BOOTP * packet, drop the packet. * Previously we allowed packets with no sname or filename * as we were aware of at least one client that did. But * a bug caused short packets to not work and nobody has * complained, it seems rational to tighten up that * restriction. */ if (result < DHCP_FIXED_NON_UDP) return ISC_R_UNEXPECTED; #if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO) { /* We retrieve the ifindex from the unused hfrom variable */ unsigned int ifindex; memcpy(&ifindex, hfrom.hbuf, sizeof (ifindex)); /* * Seek forward from the first interface to find the matching * source interface by interface index. */ ip = interfaces; while ((ip != NULL) && (if_nametoindex(ip->name) != ifindex)) ip = ip->next; if (ip == NULL) return ISC_R_NOTFOUND; } #endif if (bootp_packet_handler) { ifrom.len = 4; memcpy (ifrom.iabuf, &from.sin_addr, ifrom.len); (*bootp_packet_handler) (ip, &u.packet, (unsigned)result, from.sin_port, ifrom, &hfrom); } /* If there is buffered data, read again. This is for, e.g., bpf, which may return two packets at once. */ if (ip -> rbuf_offset != ip -> rbuf_len) goto again; return ISC_R_SUCCESS; } #ifdef DHCPv6 isc_result_t got_one_v6(omapi_object_t *h) { struct sockaddr_in6 from; struct in6_addr to; struct iaddr ifrom; int result; char buf[65536]; /* maximum size for a UDP packet is 65536 */ struct interface_info *ip; int is_unicast; unsigned int if_idx = 0; if (h->type != dhcp_type_interface) { return DHCP_R_INVALIDARG; } ip = (struct interface_info *)h; result = receive_packet6(ip, (unsigned char *)buf, sizeof(buf), &from, &to, &if_idx); if (result < 0) { log_error("receive_packet6() failed on %s: %m", ip->name); return ISC_R_UNEXPECTED; } /* 0 is 'any' interface. */ if (if_idx == 0) return ISC_R_NOTFOUND; if (dhcpv6_packet_handler != NULL) { /* * If a packet is not multicast, we assume it is unicast. */ if (IN6_IS_ADDR_MULTICAST(&to)) { is_unicast = ISC_FALSE; } else { is_unicast = ISC_TRUE; } ifrom.len = 16; memcpy(ifrom.iabuf, &from.sin6_addr, ifrom.len); /* Seek forward to find the matching source interface. */ ip = interfaces; while ((ip != NULL) && (if_nametoindex(ip->name) != if_idx)) ip = ip->next; if (ip == NULL) return ISC_R_NOTFOUND; (*dhcpv6_packet_handler)(ip, buf, result, from.sin6_port, &ifrom, is_unicast); } return ISC_R_SUCCESS; } #endif /* DHCPv6 */ isc_result_t dhcp_interface_set_value (omapi_object_t *h, omapi_object_t *id, omapi_data_string_t *name, omapi_typed_data_t *value) { struct interface_info *interface; isc_result_t status; if (h -> type != dhcp_type_interface) return DHCP_R_INVALIDARG; interface = (struct interface_info *)h; if (!omapi_ds_strcmp (name, "name")) { if ((value -> type == omapi_datatype_data || value -> type == omapi_datatype_string) && value -> u.buffer.len < sizeof interface -> name) { memcpy (interface -> name, value -> u.buffer.value, value -> u.buffer.len); interface -> name [value -> u.buffer.len] = 0; } else return DHCP_R_INVALIDARG; return ISC_R_SUCCESS; } /* Try to find some inner object that can take the value. */ if (h -> inner && h -> inner -> type -> set_value) { status = ((*(h -> inner -> type -> set_value)) (h -> inner, id, name, value)); if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED) return status; } return ISC_R_NOTFOUND; } isc_result_t dhcp_interface_get_value (omapi_object_t *h, omapi_object_t *id, omapi_data_string_t *name, omapi_value_t **value) { return ISC_R_NOTIMPLEMENTED; } isc_result_t dhcp_interface_destroy (omapi_object_t *h, const char *file, int line) { struct interface_info *interface; if (h -> type != dhcp_type_interface) return DHCP_R_INVALIDARG; interface = (struct interface_info *)h; if (interface -> ifp) { dfree (interface -> ifp, file, line); interface -> ifp = 0; } if (interface -> next) interface_dereference (&interface -> next, file, line); if (interface -> rbuf) { dfree (interface -> rbuf, file, line); interface -> rbuf = (unsigned char *)0; } if (interface -> client) interface -> client = (struct client_state *)0; if (interface -> shared_network) omapi_object_dereference ((void *) &interface -> shared_network, MDL); return ISC_R_SUCCESS; } isc_result_t dhcp_interface_signal_handler (omapi_object_t *h, const char *name, va_list ap) { struct interface_info *ip, *interface; isc_result_t status; if (h -> type != dhcp_type_interface) return DHCP_R_INVALIDARG; interface = (struct interface_info *)h; /* If it's an update signal, see if the interface is dead right now, or isn't known at all, and if that's the case, revive it. */ if (!strcmp (name, "update")) { for (ip = dummy_interfaces; ip; ip = ip -> next) if (ip == interface) break; if (ip && dhcp_interface_startup_hook) return (*dhcp_interface_startup_hook) (ip); for (ip = interfaces; ip; ip = ip -> next) if (ip == interface) break; if (!ip && dhcp_interface_startup_hook) return (*dhcp_interface_startup_hook) (ip); } /* Try to find some inner object that can take the value. */ if (h -> inner && h -> inner -> type -> signal_handler) { status = ((*(h -> inner -> type -> signal_handler)) (h -> inner, name, ap)); if (status == ISC_R_SUCCESS) return status; } return ISC_R_NOTFOUND; } isc_result_t dhcp_interface_stuff_values (omapi_object_t *c, omapi_object_t *id, omapi_object_t *h) { struct interface_info *interface; isc_result_t status; if (h -> type != dhcp_type_interface) return DHCP_R_INVALIDARG; interface = (struct interface_info *)h; /* Write out all the values. */ status = omapi_connection_put_name (c, "state"); if (status != ISC_R_SUCCESS) return status; if ((interface->flags & INTERFACE_REQUESTED) != 0) status = omapi_connection_put_string (c, "up"); else status = omapi_connection_put_string (c, "down"); if (status != ISC_R_SUCCESS) return status; /* Write out the inner object, if any. */ if (h -> inner && h -> inner -> type -> stuff_values) { status = ((*(h -> inner -> type -> stuff_values)) (c, id, h -> inner)); if (status == ISC_R_SUCCESS) return status; } return ISC_R_SUCCESS; } isc_result_t dhcp_interface_lookup (omapi_object_t **ip, omapi_object_t *id, omapi_object_t *ref) { omapi_value_t *tv = (omapi_value_t *)0; isc_result_t status; struct interface_info *interface; if (!ref) return DHCP_R_NOKEYS; /* First see if we were sent a handle. */ status = omapi_get_value_str (ref, id, "handle", &tv); if (status == ISC_R_SUCCESS) { status = omapi_handle_td_lookup (ip, tv -> value); omapi_value_dereference (&tv, MDL); if (status != ISC_R_SUCCESS) return status; /* Don't return the object if the type is wrong. */ if ((*ip) -> type != dhcp_type_interface) { omapi_object_dereference (ip, MDL); return DHCP_R_INVALIDARG; } } /* Now look for an interface name. */ status = omapi_get_value_str (ref, id, "name", &tv); if (status == ISC_R_SUCCESS) { char *s; unsigned len; for (interface = interfaces; interface; interface = interface -> next) { s = memchr (interface -> name, 0, IFNAMSIZ); if (s) len = s - &interface -> name [0]; else len = IFNAMSIZ; if ((tv -> value -> u.buffer.len == len && !memcmp (interface -> name, (char *)tv -> value -> u.buffer.value, len))) break; } if (!interface) { for (interface = dummy_interfaces; interface; interface = interface -> next) { s = memchr (interface -> name, 0, IFNAMSIZ); if (s) len = s - &interface -> name [0]; else len = IFNAMSIZ; if ((tv -> value -> u.buffer.len == len && !memcmp (interface -> name, (char *) tv -> value -> u.buffer.value, len))) break; } } omapi_value_dereference (&tv, MDL); if (*ip && *ip != (omapi_object_t *)interface) { omapi_object_dereference (ip, MDL); return DHCP_R_KEYCONFLICT; } else if (!interface) { if (*ip) omapi_object_dereference (ip, MDL); return ISC_R_NOTFOUND; } else if (!*ip) omapi_object_reference (ip, (omapi_object_t *)interface, MDL); } /* If we get to here without finding an interface, no valid key was specified. */ if (!*ip) return DHCP_R_NOKEYS; return ISC_R_SUCCESS; } /* actually just go discover the interface */ isc_result_t dhcp_interface_create (omapi_object_t **lp, omapi_object_t *id) { struct interface_info *hp; isc_result_t status; hp = (struct interface_info *)0; status = interface_allocate (&hp, MDL); if (status != ISC_R_SUCCESS) return status; hp -> flags = INTERFACE_REQUESTED; status = interface_reference ((struct interface_info **)lp, hp, MDL); interface_dereference (&hp, MDL); return status; } isc_result_t dhcp_interface_remove (omapi_object_t *lp, omapi_object_t *id) { struct interface_info *interface, *ip, *last; interface = (struct interface_info *)lp; /* remove from interfaces */ last = 0; for (ip = interfaces; ip; ip = ip -> next) { if (ip == interface) { if (last) { interface_dereference (&last -> next, MDL); if (ip -> next) interface_reference (&last -> next, ip -> next, MDL); } else { interface_dereference (&interfaces, MDL); if (ip -> next) interface_reference (&interfaces, ip -> next, MDL); } if (ip -> next) interface_dereference (&ip -> next, MDL); break; } last = ip; } if (!ip) return ISC_R_NOTFOUND; /* add the interface to the dummy_interface list */ if (dummy_interfaces) { interface_reference (&interface -> next, dummy_interfaces, MDL); interface_dereference (&dummy_interfaces, MDL); } interface_reference (&dummy_interfaces, interface, MDL); /* do a DHCPRELEASE */ if (dhcp_interface_shutdown_hook) (*dhcp_interface_shutdown_hook) (interface); /* remove the io object */ omapi_unregister_io_object ((omapi_object_t *)interface); switch(local_family) { #ifdef DHCPv6 case AF_INET6: if_deregister6(interface); break; #endif /* DHCPv6 */ case AF_INET: default: if_deregister_send(interface); if_deregister_receive(interface); break; } return ISC_R_SUCCESS; } void interface_stash (struct interface_info *tptr) { struct interface_info **vec; int delta; /* If the registerer didn't assign an index, assign one now. */ if (tptr -> index == -1) { tptr -> index = interface_count++; while (tptr -> index < interface_max && interface_vector [tptr -> index]) tptr -> index = interface_count++; } if (interface_max <= tptr -> index) { delta = tptr -> index - interface_max + 10; vec = dmalloc ((interface_max + delta) * sizeof (struct interface_info *), MDL); if (!vec) { log_error ("interface_stash: allocation failed "); return; } memset (&vec [interface_max], 0, (sizeof (struct interface_info *)) * delta); interface_max += delta; if (interface_vector) { memcpy (vec, interface_vector, (interface_count * sizeof (struct interface_info *))); dfree (interface_vector, MDL); } interface_vector = vec; } interface_reference (&interface_vector [tptr -> index], tptr, MDL); if (tptr -> index >= interface_count) interface_count = tptr -> index + 1; #if defined (TRACING) trace_interface_register (interface_trace, tptr); #endif } void interface_snorf (struct interface_info *tmp, int ir) { tmp -> circuit_id = (u_int8_t *)tmp -> name; tmp -> circuit_id_len = strlen (tmp -> name); tmp -> remote_id = 0; tmp -> remote_id_len = 0; tmp -> flags = ir; if (interfaces) { interface_reference (&tmp -> next, interfaces, MDL); interface_dereference (&interfaces, MDL); } interface_reference (&interfaces, tmp, MDL); }