summaryrefslogtreecommitdiff
path: root/common/socket.c
diff options
context:
space:
mode:
authorShawn Routhier <sar@isc.org>2011-06-27 16:00:32 +0000
committerShawn Routhier <sar@isc.org>2011-06-27 16:00:32 +0000
commit7cfeb9160de5c62e091429395c76ec7e28ac2f72 (patch)
tree5fec6f2bda301e11aad4f40306e2b2bcce86ded3 /common/socket.c
parentade10315e1b6cd8795da0e53e03d58ce41b91dfa (diff)
downloadisc-dhcp-7cfeb9160de5c62e091429395c76ec7e28ac2f72.tar.gz
In Solaris 11 switch to using sockets instead of DLPI, thanks
to a patch form Oracle. [ISC-Bugs #24634].
Diffstat (limited to 'common/socket.c')
-rw-r--r--common/socket.c291
1 files changed, 267 insertions, 24 deletions
diff --git a/common/socket.c b/common/socket.c
index 50ca746b..a48404b0 100644
--- a/common/socket.c
+++ b/common/socket.c
@@ -3,7 +3,7 @@
BSD socket interface code... */
/*
- * Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2011 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1995-2003 by Internet Software Consortium
*
* Permission to use, copy, modify, and distribute this software for any
@@ -46,6 +46,13 @@
#include <sys/uio.h>
#include <sys/uio.h>
+#if defined(sun) && defined(USE_V4_PKTINFO)
+#include <sys/sysmacros.h>
+#include <net/if.h>
+#include <sys/sockio.h>
+#include <net/if_dl.h>
+#endif
+
#ifdef USE_SOCKET_FALLBACK
# if !defined (USE_SOCKET_SEND)
# define if_register_send if_register_fallback
@@ -66,6 +73,16 @@ static void if_register_multicast(struct interface_info *info);
#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 (for example Solaris 11).
+ */
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+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.
@@ -248,6 +265,20 @@ if_register_socket(struct interface_info *info, int family,
log_fatal("Can't set IP_BROADCAST_IF on dhcp socket: %m");
#endif
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ /*
+ * 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
@@ -281,10 +312,6 @@ if_register_socket(struct interface_info *info, int family,
}
#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 */
@@ -294,21 +321,24 @@ void if_register_send (info)
struct interface_info *info;
{
#ifndef USE_SOCKET_RECEIVE
- info -> wfdesc = if_register_socket (info, AF_INET, 0);
+ info->wfdesc = if_register_socket(info, AF_INET, 0);
+ /* 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);
#if defined (USE_SOCKET_FALLBACK)
/* Fallback only registers for send, but may need to receive as
well. */
- info -> rfdesc = info -> wfdesc;
+ info->rfdesc = info->wfdesc;
#endif
#else
- info -> wfdesc = info -> rfdesc;
+ info->wfdesc = info->rfdesc;
#endif
if (!quiet_interface_discovery)
log_info ("Sending on Socket/%s%s%s",
- info -> name,
- (info -> shared_network ? "/" : ""),
- (info -> shared_network ?
- info -> shared_network -> name : ""));
+ info->name,
+ (info->shared_network ? "/" : ""),
+ (info->shared_network ?
+ info->shared_network->name : ""));
}
#if defined (USE_SOCKET_SEND)
@@ -334,23 +364,61 @@ void if_deregister_send (info)
void if_register_receive (info)
struct interface_info *info;
{
+
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ 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);
+ info->rfdesc = if_register_socket(info, AF_INET, 0);
+#endif /* IP_PKTINFO... */
+ /* 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);
+
if (!quiet_interface_discovery)
log_info ("Listening on Socket/%s%s%s",
- info -> name,
- (info -> shared_network ? "/" : ""),
- (info -> shared_network ?
- info -> shared_network -> name : ""));
+ info->name,
+ (info->shared_network ? "/" : ""),
+ (info->shared_network ?
+ info->shared_network->name : ""));
}
void if_deregister_receive (info)
struct interface_info *info;
{
- close (info -> rfdesc);
- info -> rfdesc = -1;
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ /* 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 /* IP_PKTINFO... */
if (!quiet_interface_discovery)
log_info ("Disabling input on Socket/%s%s%s",
info -> name,
@@ -495,6 +563,18 @@ ssize_t send_packet (interface, packet, raw, len, from, to, hto)
int retry = 0;
do {
#endif
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ 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
@@ -565,11 +645,15 @@ static size_t CMSG_SPACE(size_t len) {
#endif /* DHCPv6 */
-#ifdef DHCPv6
+#if defined(DHCPv6) || \
+ (defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && \
+ defined(USE_V4_PKTINFO))
/*
* 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() when we use a single socket and IP_PKTINFO to
+ * send the packet out the correct interface.
*/
static void *control_buf = NULL;
static size_t control_buf_len = 0;
@@ -580,7 +664,9 @@ allocate_cmsg_cbuf(void) {
control_buf = dmalloc(control_buf_len, MDL);
return;
}
+#endif /* DHCPv6, IP_PKTINFO ... */
+#ifdef DHCPv6
/*
* For both send_packet6() and receive_packet6() we need to use the
* sendmsg()/recvmsg() functions rather than the simpler send()/recv()
@@ -679,7 +765,9 @@ ssize_t receive_packet (interface, buf, len, from, hfrom)
struct sockaddr_in *from;
struct hardware *hfrom;
{
+#if !defined(USE_V4_PKTINFO)
SOCKLEN_T flen = sizeof *from;
+#endif
int result;
/*
@@ -693,8 +781,99 @@ ssize_t receive_packet (interface, buf, len, from, hfrom)
int retry = 0;
do {
#endif
+
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ 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 /* IP_PKTINFO ... */
#ifdef IGNORE_HOSTUNREACH
} while (result < 0 &&
(errno == EHOSTUNREACH ||
@@ -848,10 +1027,12 @@ int can_receive_unicast_unconfigured (ip)
int supports_multiple_interfaces (ip)
struct interface_info *ip;
{
-#if defined (SO_BINDTODEVICE)
- return 1;
+#if defined(SO_BINDTODEVICE) || \
+ (defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && \
+ defined(USE_V4_PKTINFO))
+ return(1);
#else
- return 0;
+ return(0);
#endif
}
@@ -882,4 +1063,66 @@ void maybe_setup_fallback ()
}
#endif
}
+
+
+#if defined(sun) && defined(USE_V4_PKTINFO)
+/* This code assumes the existence of SIOCGLIFHWADDR */
+void
+get_hw_addr(const char *name, struct hardware *hw) {
+ struct sockaddr_dl *dladdrp;
+ 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 (sock != -1)
+ (void) close(sock);
+
+#ifdef DHCPv6
+ /*
+ * If approrpriate try this with an IPv6 socket
+ */
+ if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) >= 0 &&
+ ioctl(sock, SIOCGLIFFLAGS, &lifr) >= 0) {
+ goto flag_check;
+ }
+ if (sock != -1)
+ (void) close(sock);
+#endif
+ log_fatal("Couldn't get interface flags for %s: %m", name);
+
+ }
+
+ flag_check:
+ 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 (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);
+
+ if (sock != -1)
+ (void) close(sock);
+}
+#endif /* defined(sun) */
+
#endif /* USE_SOCKET_SEND */