changeset 4187:73de5e0edf30 draft

dhcp: when unicasting, don't use a L2 broadcast This relies change now relies on the kernel supporting IP_PKTINFO to select the correct outbound interface which may not always be the case due to routing.
author Roy Marples <roy@marples.name>
date Thu, 07 Dec 2017 15:12:18 +0000
parents cf951e13d6fc
children 23ff91710a01
files src/dhcp.c
diffstat 1 files changed, 73 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/src/dhcp.c	Wed Dec 06 09:44:26 2017 +0000
+++ b/src/dhcp.c	Thu Dec 07 15:12:18 2017 +0000
@@ -1587,10 +1587,6 @@
 	int s;
 	struct sockaddr_in sin;
 	int n;
-	struct dhcp_state *state;
-#ifdef SO_BINDTODEVICE
-	struct ifreq ifr;
-#endif
 
 	if ((s = xsocket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_UDP)) == -1)
 		return -1;
@@ -1598,20 +1594,12 @@
 	n = 1;
 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1)
 		goto eexit;
-#ifdef SO_BINDTODEVICE
-	if (ifp) {
-		memset(&ifr, 0, sizeof(ifr));
-		strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
-		if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr,
-		    sizeof(ifr)) == -1)
-		        goto eexit;
-	}
-#endif
 	memset(&sin, 0, sizeof(sin));
 	sin.sin_family = AF_INET;
 	sin.sin_port = htons(BOOTPC);
 	if (ifp) {
-		state = D_STATE(ifp);
+		struct dhcp_state *state = D_STATE(ifp);
+
 		if (state->addr)
 			sin.sin_addr.s_addr = state->addr->addr.s_addr;
 	}
@@ -1694,6 +1682,63 @@
 	return udpp;
 }
 
+static ssize_t
+dhcp_sendudp(struct interface *ifp, struct in_addr *to, void *data, size_t len)
+{
+	int s;
+	struct msghdr msg;
+	union {
+		struct sockaddr sa;
+		struct sockaddr_in sin;
+	} sto;
+	struct iovec iov[1];
+	ssize_t r;
+#ifdef IP_PKTINFO
+	uint8_t cmsg[CMSG_SPACE(sizeof(struct in_pktinfo))];
+	struct cmsghdr *cm;
+	struct in_pktinfo ipi;
+#endif
+
+	iov[0].iov_base = data;
+	iov[0].iov_len = len;
+
+	memset(&sto, 0, sizeof(sto));
+	sto.sin.sin_family = AF_INET;
+	sto.sin.sin_addr = *to;
+	sto.sin.sin_port = htons(BOOTPS);
+
+	memset(&msg, 0, sizeof(msg));
+	msg.msg_name = (void *)&sto.sa;
+	msg.msg_namelen = sizeof(sto);
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 1;
+
+#ifdef IP_PKTINFO
+	/* Set the outbound interface */
+	msg.msg_control = cmsg;
+	msg.msg_controllen = sizeof(cmsg);
+
+	memset(&ipi, 0, sizeof(ipi));
+	ipi.ipi_ifindex = ifp->index;
+	cm = CMSG_FIRSTHDR(&msg);
+	if (cm == NULL) {
+		errno = ESRCH;
+		return -1;
+	}
+	cm->cmsg_level = IPPROTO_IP;
+	cm->cmsg_type = IP_PKTINFO;
+	cm->cmsg_len = CMSG_LEN(sizeof(ipi));
+	memcpy(CMSG_DATA(cm), &ipi, sizeof(ipi));
+#endif
+
+	s = dhcp_openudp(ifp);
+	if (s == -1)
+		return -1;
+	r = sendmsg(s, &msg, 0);
+	close(s);
+	return r;
+}
+
 static void
 send_message(struct interface *ifp, uint8_t type,
     void (*callback)(void *))
@@ -1738,9 +1783,6 @@
 		    timespec_to_double(&tv));
 	}
 
-	if (dhcp_openbpf(ifp) == -1)
-		return;
-
 	r = make_message(&bootp, ifp, type);
 	if (r == -1)
 		goto fail;
@@ -1750,6 +1792,18 @@
 		to.s_addr = state->lease.server.s_addr;
 	else
 		to.s_addr = INADDR_ANY;
+
+	/* If unicasting, try and void sending by BPF so we don't
+	 * use a L2 broadcast. */
+	if (to.s_addr != INADDR_ANY && to.s_addr != INADDR_BROADCAST) {
+		if (dhcp_sendudp(ifp, &to, bootp, len) != -1)
+			goto out;
+		logerr("%s: dhcp_sendudp", ifp->name);
+	}
+
+	if (dhcp_openbpf(ifp) == -1)
+		goto out;
+
 	udp = dhcp_makeudppacket(&ulen, (uint8_t *)bootp, len, from, to);
 	if (udp == NULL) {
 		logerr("%s: dhcp_makeudppacket", ifp->name);
@@ -1780,6 +1834,8 @@
 			callback = NULL;
 		}
 	}
+
+out:
 	free(bootp);
 
 fail: