--- dhcp-4.1-ESV-R1/common/discover.c Tue Sep 29 12:44:49 2009 +++ dhcp-4.1-ESV-R1-patched/common/discover.c Thu May 26 11:49:33 2011 @@ -309,6 +309,7 @@ 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; @@ -315,6 +316,7 @@ #endif do { + foundif = ISC_FALSE; if (ifaces->next >= ifaces->num) { *err = 0; return 0; @@ -328,6 +330,13 @@ 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; + } + strcpy(info->name, p->lifr_name); memset(&info->addr, 0, sizeof(info->addr)); memcpy(&info->addr, &p->lifr_addr, sizeof(p->lifr_addr)); @@ -340,7 +349,9 @@ } #endif /* defined(sun) || defined(__linux) */ - } while (strncmp(info->name, "dummy", 5) == 0); + foundif = ISC_TRUE; + } while ((foundif == ISC_FALSE) || + (strncmp(p->lifr_name, "dummy", 5) == 0)); memset(&tmp, 0, sizeof(tmp)); strcpy(tmp.lifr_name, info->name); @@ -958,7 +969,12 @@ 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 (((!(info.flags & IFF_BROADCAST) || + 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) && @@ -1386,6 +1402,25 @@ if (result < DHCP_FIXED_NON_UDP - DHCP_SNAME_LEN - DHCP_FILE_LEN) return ISC_R_UNEXPECTED; +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) + { + /* 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); @@ -1442,7 +1477,11 @@ ifrom.len = 16; memcpy(ifrom.iabuf, &from.sin6_addr, ifrom.len); - /* Seek forward to find the matching source interface. */ + /* + * Seek forward from the first interface to find the matching + * source interface by interface index. + */ + ip = interfaces; while ((ip != NULL) && (if_nametoindex(ip->name) != if_idx)) ip = ip->next; --- dhcp-4.1-ESV-R1/common/socket.c Tue Oct 5 17:32:52 2010 +++ dhcp-4.1-ESV-R1-patched/common/socket.c Thu May 12 16:11:13 2011 @@ -45,6 +45,16 @@ #include #include #include +#if defined(sun) +#include +#include +#include +#if defined(SIOCGLIFHWADDR) +#include +#else +#include +#endif +#endif #include #ifdef USE_SOCKET_FALLBACK @@ -67,6 +77,16 @@ #endif /* + * We can use a single socket for AF_INET (similar to AF_INET6) on all + * interfaces configured for DHCP if the system has support for IP_PKTINFO + * and IP_RECVPKTINFO (f.e. Solaris 11). + */ +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) +static unsigned int global_v4_socket_references = 0; +static int global_v4_socket = -1; +#endif + +/* * If we can't bind() to a specific interface, then we can only have * a single socket. This variable insures that we don't try to listen * on two sockets. @@ -242,6 +262,20 @@ log_fatal("Can't set IP_BROADCAST_IF on dhcp socket: %m"); #endif +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) + /* + * If we turn on IP_RECVPKTINFO we will be able to receive + * the interface index information of the received packet. + */ + if (family == AF_INET) { + int on = 1; + if (setsockopt(sock, IPPROTO_IP, IP_RECVPKTINFO, + &on, sizeof(on)) != 0) { + log_fatal("setsockopt: IPV_RECVPKTINFO: %m"); + } + } +#endif + #ifdef DHCPv6 /* * If we turn on IPV6_PKTINFO, we will be able to receive @@ -275,10 +309,6 @@ } #endif /* DHCPv6 */ - /* If this is a normal IPv4 address, get the hardware address. */ - if ((local_family == AF_INET) && (strcmp(info->name, "fallback") != 0)) - get_hw_addr(info->name, &info->hw_address); - return sock; } #endif /* USE_SOCKET_SEND || USE_SOCKET_RECEIVE || USE_SOCKET_FALLBACK */ @@ -328,9 +358,25 @@ void if_register_receive (info) struct interface_info *info; { +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) + if (global_v4_socket_references == 0) { + global_v4_socket = if_register_socket(info, AF_INET, 0); + if (global_v4_socket < 0) { + /* + * if_register_socket() fatally logs if it fails to + * create a socket, this is just a sanity check. + */ + log_fatal("Failed to create AF_INET socket %s:%d", MDL); + } + } + + info->rfdesc = global_v4_socket; + global_v4_socket_references++; +#else /* If we're using the socket API for sending and receiving, we don't need to register this interface twice. */ info -> rfdesc = if_register_socket (info, AF_INET, 0); +#endif if (!quiet_interface_discovery) log_info ("Listening on Socket/%s%s%s", info -> name, @@ -337,13 +383,34 @@ (info -> shared_network ? "/" : ""), (info -> shared_network ? info -> shared_network -> name : "")); + + /* If this is a normal IPv4 address, get the hardware address. */ + if (strcmp(info->name, "fallback") != 0) + get_hw_addr(info->name, &info->hw_address); } void if_deregister_receive (info) struct interface_info *info; { +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) + /* Dereference the global v4 socket. */ + if ((info->rfdesc == global_v4_socket) && + (info->wfdesc == global_v4_socket) && + (global_v4_socket_references > 0)) { + global_v4_socket_references--; + info->rfdesc = -1; + } else { + log_fatal("Impossible condition at %s:%d", MDL); + } + + if (global_v4_socket_references == 0) { + close(global_v4_socket); + global_v4_socket = -1; + } +#else close (info -> rfdesc); info -> rfdesc = -1; +#endif if (!quiet_interface_discovery) log_info ("Disabling input on Socket/%s%s%s", @@ -489,6 +556,17 @@ int retry = 0; do { #endif +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) + struct in_pktinfo pktinfo; + + if (interface->ifp != NULL) { + memset(&pktinfo, 0, sizeof (pktinfo)); + pktinfo.ipi_ifindex = interface->ifp->ifr_index; + if (setsockopt(interface -> wfdesc, IPPROTO_IP, + IP_PKTINFO, (char *)&pktinfo, sizeof (pktinfo)) < 0) + log_fatal("setsockopt: IP_PKTINFO: %m"); + } +#endif result = sendto (interface -> wfdesc, (char *)raw, len, 0, (struct sockaddr *)to, sizeof *to); #ifdef IGNORE_HOSTUNREACH @@ -559,11 +637,13 @@ #endif /* DHCPv6 */ -#ifdef DHCPv6 +#ifdef DHCPv6 || (defined(IP_PKTINFO) && defined(IP_RECVPKTINFO)) /* * For both send_packet6() and receive_packet6() we need to allocate * space for the cmsg header information. We do this once and reuse - * the buffer. + * the buffer. We also need the control buf for send_packet and + * receive_packet for AF_INET when we use a single socket and IP_PKTINFO + * to send the packet out the right interface. */ static void *control_buf = NULL; static size_t control_buf_len = 0; @@ -574,7 +654,9 @@ control_buf = dmalloc(control_buf_len, MDL); return; } +#endif +#ifdef DHCPv6 /* * For both send_packet6() and receive_packet6() we need to use the * sendmsg()/recvmsg() functions rather than the simpler send()/recv() @@ -687,8 +769,97 @@ int retry = 0; do { #endif +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) + struct msghdr m; + struct iovec v; + struct cmsghdr *cmsg; + struct in_pktinfo *pktinfo; + unsigned int ifindex; + int found_pktinfo; + + /* + * If necessary allocate space for the control message header. + * The space is common between send and receive. + */ + if (control_buf == NULL) { + allocate_cmsg_cbuf(); + if (control_buf == NULL) { + log_error("receive_packet: unable to allocate cmsg " + "header"); + return(ENOMEM); + } + } + memset(control_buf, 0, control_buf_len); + + /* + * Initialize our message header structure. + */ + memset(&m, 0, sizeof(m)); + + /* + * Point so we can get the from address. + */ + m.msg_name = from; + m.msg_namelen = sizeof(*from); + + /* + * Set the data buffer we're receiving. (Using this wacky + * "scatter-gather" stuff... but we that doesn't really make + * sense for us, so we use a single vector entry.) + */ + v.iov_base = buf; + v.iov_len = len; + m.msg_iov = &v; + m.msg_iovlen = 1; + + /* + * Getting the interface is a bit more involved. + * + * We set up some space for a "control message". We have + * previously asked the kernel to give us packet + * information (when we initialized the interface), so we + * should get the destination address from that. + */ + m.msg_control = control_buf; + m.msg_controllen = control_buf_len; + + result = recvmsg(interface->rfdesc, &m, 0); + + if (result >= 0) { + /* + * If we did read successfully, then we need to loop + * through the control messages we received and + * find the one with our destination address. + * + * We also keep a flag to see if we found it. If we + * didn't, then we consider this to be an error. + */ + found_pktinfo = 0; + cmsg = CMSG_FIRSTHDR(&m); + while (cmsg != NULL) { + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_PKTINFO)) { + pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); + ifindex = pktinfo->ipi_ifindex; + /* + * We pass the ifindex back to the caller using + * the unused hfrom parameter avoiding interface + * changes between sockets and the discover code. + */ + memcpy(hfrom->hbuf, &ifindex, sizeof (ifindex)); + found_pktinfo = 1; + } + cmsg = CMSG_NXTHDR(&m, cmsg); + } + if (!found_pktinfo) { + result = -1; + errno = EIO; + } + } +#else result = recvfrom (interface -> rfdesc, (char *)buf, len, 0, (struct sockaddr *)from, &flen); +#endif #ifdef IGNORE_HOSTUNREACH } while (result < 0 && (errno == EHOSTUNREACH || @@ -842,7 +1013,7 @@ int supports_multiple_interfaces (ip) struct interface_info *ip; { -#if defined (SO_BINDTODEVICE) +#if defined (SO_BINDTODEVICE) || (defined(IP_PKTINFO) && defined(IP_RECVPKTINFO)) return 1; #else return 0; @@ -876,6 +1047,80 @@ } #endif } + +#if defined(sun) +void +get_hw_addr(const char *name, struct hardware *hw) { +#if defined(SIOCGLIFHWADDR) + struct sockaddr_dl *dladdrp; +#else + dlpi_handle_t dh; + uint8_t pa_buf[DLPI_PHYSADDR_MAX]; + size_t len = sizeof (pa_buf); +#endif + int rv, sock, i; + struct lifreq lifr; + + memset(&lifr, 0, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, name, sizeof (lifr.lifr_name)); + /* + * Check if the interface is a virtual or IPMP interface - in those + * cases it has no hw address, so generate a random one. + */ + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0 || + ioctl(sock, SIOCGLIFFLAGS, &lifr) < 0) { + /* + * If the interface only has IPv6, try this with an IPv6 socket. + */ + if (sock != -1) + (void) close(sock); + + if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0 || + ioctl(sock, SIOCGLIFFLAGS, &lifr) < 0) { + log_fatal("Couldn't get interface flags for %s: %m", name); + } + } + + if (lifr.lifr_flags & (IFF_VIRTUAL|IFF_IPMP)) { + hw->hlen = sizeof (hw->hbuf); + srandom((long)gethrtime()); + + for (i = 0; i < hw->hlen; ++i) { + hw->hbuf[i] = random() % 256; + } + + if (sock != -1) + (void) close(sock); + return; + } + +#if defined(SIOCGLIFHWADDR) + if (ioctl(sock, SIOCGLIFHWADDR, &lifr) < 0) + log_fatal("Couldn't get interface hardware address for %s: %m", name); + dladdrp = (struct sockaddr_dl *)&lifr.lifr_addr; + hw->hlen = dladdrp->sdl_alen; + memcpy(hw->hbuf, LLADDR(dladdrp), hw->hlen); +#else + if ((rv = dlpi_open(name, &dh, 0)) != DLPI_SUCCESS) { + log_fatal("Couldn't open DLPI device for %s: %s", name, + dlpi_strerror(rv)); + } + + if ((rv = dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR, pa_buf, &len)) + != DLPI_SUCCESS) { + log_fatal("Couldn't get physical address for device %s: %s", + name, dlpi_strerror(rv)); + } + + hw->hlen = MIN(sizeof (hw->hbuf), len); + memcpy(hw->hbuf, pa_buf, hw->hlen); + + dlpi_close(dh); +#endif + if (sock != -1) + (void) close(sock); +} +#endif /* defined(sun) */ #endif /* USE_SOCKET_SEND */ /*