Not sure if I this list allows attachments... so here we go.

First a question: how portable do we want things? The current sdhcp
works only on Linux.

This patch contains one big change and some smaller changes. The big
change is support for raw sockets on Linux. I split the raw socket code
into a file called raw.c and included it in sdhcp. I did that only to,
hopefully, make the diff more readable.

So why raw sockets? Linux has a bug, er design decision, that when you
use UDP/TCP sockets it tries really hard to assign an address to the
outgoing packet. If you have multiple interfaces, it will actually
borrow the address from another interface.

This is usually not a problem... except when the dhcp server cannot
route local addresses. For example trying to dhcp with Rogers Cable.

So a raw socket allows you to force the outgoing address to 0.0.0.0.
This code is very Linux specific. I believe it could be forced to work
with BSD, but why? BSD doesn't have the problem.

You may notice that I close the socket when we are bound. The raw
socket sees all traffic on the interface and this is done for
performance reasons.

Other fixes that are in this diff because I need them:

  * allow binary cid input. If you put 0x<cid> then it will be
    interpreted as raw bytes. You can still specify a string.

  * talking about cid, the default is now a correct mac address cid.

  * calctimeout() was dividing the timeout by 2. Not sure why so I
    removed it. Also, the check for less than 60 seconds would only
    work if you went back in time to Jan 1, 1970 for the first minute
    after midnight.

  * sdhcp now only prints Congrats on the first connection, not every
    rebind.

  * we need the interface up to work. The code now brings the interface
    up.

  * a couple of portability cleanups may have crept in too... but I
    tried to keep them to a minimum.

Cheers,
    Sean
diff --git a/raw.c b/raw.c
new file mode 100644
index 0000000..27b7024
--- /dev/null
+++ b/raw.c
@@ -0,0 +1,158 @@
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <linux/if_packet.h>
+
+static struct pkt {
+	struct ether_header ethhdr;
+	struct ip     iphdr;
+	struct udphdr udphdr;
+	struct bootp  bootp;
+} __attribute__((packed)) pkt;
+
+/* pseudo header for udp calc */
+static struct pseudohdr
+{
+	unsigned long  source_ip;
+	unsigned long  dest_ip;
+	unsigned char  reserved;
+	unsigned char  protocol;
+	unsigned short udp_length;
+	struct udphdr  udphdr;
+	struct bootp   bootp;
+} __attribute__((packed)) pseudohdr;
+
+static unsigned char server_mac[ETHER_ADDR_LEN];
+static unsigned int ifindex;
+
+/* RFC 1071. */
+static uint16_t
+chksum16(const void *buf, int count)
+{
+	int32_t sum = 0, shift;
+	const uint16_t *p = buf;
+
+	while (count > 1) {
+		sum += *p++;
+		count -= 2;
+	}
+
+	if (count > 0)
+		sum += *p;
+
+	/*  Fold 32-bit sum to 16 bits */
+	if ((shift = sum >> 16))
+		sum = (sum & 0xffff) + shift;
+
+	return ~sum;
+}
+
+static int
+open_socket(void)
+{
+	struct ifreq ifreq;
+	int sock, bcast = 1;
+
+	if ((sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1)
+		eprintf("socket:");
+
+	if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) == -1)
+		eprintf("setsockopt broadcast:");
+
+	memset(&ifreq, 0, sizeof(ifreq));
+	strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
+
+	if (ioctl(sock, SIOCGIFINDEX, &ifreq))
+		eprintf("SIOCGIFINDEX");
+	ifindex = ifreq.ifr_ifindex;
+
+	if (ioctl(sock, SIOCGIFHWADDR, &ifreq))
+		eprintf("SIOCGIFHWADDR");
+	memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(ifreq.ifr_hwaddr.sa_data));
+
+	return sock;
+}
+
+static ssize_t
+udpsend(int sock, void *data, size_t n, int how)
+{
+	if (sock == -1)
+		sock = open_socket();
+
+	memset(&pkt, 0, sizeof(pkt));
+
+	if (how == Broadcast) {
+		memset(pkt.ethhdr.ether_dhost, 0xff, ETHER_ADDR_LEN);
+		pkt.iphdr.ip_dst.s_addr = INADDR_BROADCAST;
+	} else {
+		memcpy(&pkt.ethhdr.ether_dhost, server_mac, ETHER_ADDR_LEN);
+		memcpy(&pkt.iphdr.ip_dst, server, 4);
+	}
+
+	memcpy(pkt.ethhdr.ether_shost, hwaddr, ETHER_ADDR_LEN);
+	pkt.ethhdr.ether_type = ntohs(ETHERTYPE_IP);
+
+	pkt.iphdr.ip_v = 4;
+	pkt.iphdr.ip_hl = 5;
+	pkt.iphdr.ip_tos = IPTOS_LOWDELAY;
+	pkt.iphdr.ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + n);
+	pkt.iphdr.ip_id = 0;
+	pkt.iphdr.ip_off = htons(0x4000); /* DF set */
+	pkt.iphdr.ip_ttl = 16;
+	pkt.iphdr.ip_p = IPPROTO_UDP;
+	memcpy(&pkt.iphdr.ip_src, client, 4);
+	pkt.iphdr.ip_sum = chksum16(&pkt.iphdr, 20);
+
+	pkt.udphdr.uh_sport = htons(68);
+	pkt.udphdr.uh_dport = htons(67);
+	pkt.udphdr.uh_ulen = htons(sizeof(struct udphdr) + n);
+
+	memcpy(&pkt.bootp, data, n);
+
+	memset(&pseudohdr, 0, sizeof(pseudohdr));
+	pseudohdr.source_ip  = pkt.iphdr.ip_src.s_addr;
+	pseudohdr.dest_ip    = pkt.iphdr.ip_dst.s_addr;
+	pseudohdr.protocol   = pkt.iphdr.ip_p;
+	pseudohdr.udp_length = htons(sizeof(struct udphdr) + n);
+
+	memcpy(&pseudohdr.udphdr, &pkt.udphdr, sizeof(struct udphdr));
+	memcpy(&pseudohdr.bootp, data, n);
+	int header_len = sizeof(pseudohdr) - sizeof(struct bootp) + n;
+	pkt.udphdr.uh_sum = chksum16(&pseudohdr, header_len);
+
+	struct sockaddr_ll sa;
+	memset(&sa, 0, sizeof (sa));
+	sa.sll_family = AF_PACKET;
+	sa.sll_protocol = htons(ETH_P_IP);
+	sa.sll_halen = ETHER_ADDR_LEN;
+	memcpy(sa.sll_addr, hwaddr, ETHER_ADDR_LEN);
+	sa.sll_ifindex = ifindex;
+
+	size_t len = sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr) + n;
+	ssize_t sent;
+	if ((sent = sendto(sock, &pkt, len, 0, (struct sockaddr *)&sa, sizeof(sa))) == -1)
+		eprintf("sendto:");
+
+	return sent;
+}
+
+static ssize_t
+udprecv(int fd, void *data, size_t n)
+{
+	struct pkt recv;
+	int r;
+
+	memset(&recv, 0, sizeof(recv));
+	if ((r = read(fd, &recv, sizeof(recv))) == -1)
+		eprintf("read");
+
+	r -= sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr);
+
+	// Make sure it is a dhcp packet
+	if (recv.udphdr.uh_sport == htons(67) && recv.udphdr.uh_dport == htons(68) && r > 0) {
+		memcpy(server_mac, &recv.ethhdr.ether_shost, ETHER_ADDR_LEN);
+		memcpy(data, &recv.bootp, r);
+	} else
+		memset(data, 0, n);
+	return r;
+}
diff --git a/sdhcp.c b/sdhcp.c
index 1fcf5e6..b6d7c4e 100644
--- a/sdhcp.c
+++ b/sdhcp.c
@@ -1,8 +1,10 @@
+#include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/timerfd.h>
 
 #include <netinet/in.h>
+#include <netinet/if_ether.h>
 #include <net/if.h>
 #include <net/route.h>
 
@@ -89,13 +91,15 @@ static unsigned char magic[] = { 99, 130, 83, 99 };
 
 /* conf */
 static unsigned char xid[sizeof(bp.xid)];
-static unsigned char hwaddr[16];
-static char hostname[HOST_NAME_MAX + 1];
+static unsigned char hwaddr[ETHER_ADDR_LEN];
+static char hostname[_POSIX_HOST_NAME_MAX + 1];
 static time_t starttime;
 static char *ifname = "eth0";
-static unsigned char cid[16];
+static unsigned char cid[24];
+static int cid_len;
 static char *program = "";
-static int sock, timers[3];
+static int sock = -1;
+static int timers[3];
 /* sav */
 static unsigned char server[4];
 static unsigned char client[4];
@@ -130,13 +134,65 @@ iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port)
 	return ifaddr;
 }
 
+#ifdef __linux__
+#define USE_RAW_SOCKET
+#endif
+
+#ifdef USE_RAW_SOCKET
+#include "raw.c"
+#else
+/* open a socket */
+static int
+open_socket(void)
+{
+	struct ifreq ifreq;
+	struct sockaddr addr;
+	int sock_fd;
+	int bcast = 1;
+
+	if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+		eprintf("socket:");
+
+	if (setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) == -1)
+		eprintf("setsockopt:");
+
+	memset(&ifreq, 0, sizeof(ifreq));
+	strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
+	ioctl(sock_fd, SIOCGIFINDEX, &ifreq);
+	if (setsockopt(sock_fd, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)) == -1)
+		eprintf("setsockopt:");
+	iptoaddr(&addr, IP(255, 255, 255, 255), 68);
+	if (bind(sock_fd, (void*)&addr, sizeof(addr)) != 0)
+		eprintf("bind:");
+
+	ioctl(sock_fd, SIOCGIFHWADDR, &ifreq);
+	memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(ifreq.ifr_hwaddr.sa_data));
+
+	/* If the interface is down... bring it up */
+	if (ioctl(sock_fd, SIOCGIFFLAGS, &ifreq))
+		eprintf("get flags");
+	if ((ifreq.ifr_flags & IFF_UP) == 0) {
+		ifreq.ifr_flags |= IFF_UP;
+		if (ioctl(sock_fd, SIOCSIFFLAGS, &ifreq))
+			eprintf("interface not up");
+	}
+
+	return sock_fd;
+}
+
 /* sendto UDP wrapper */
 static ssize_t
-udpsend(unsigned char ip[4], int fd, void *data, size_t n)
+udpsend(int fd, void *data, size_t n, int how)
 {
 	struct sockaddr addr;
 	socklen_t addrlen = sizeof(addr);
 	ssize_t sent;
+	unsigned char ip[4];
+
+	if (how == Broadcast)
+		memset(ip, 0xff, 4);
+	else
+		memcpy(ip, server, 4);
 
 	iptoaddr(&addr, ip, 67); /* bootp server */
 	if ((sent = sendto(fd, data, n, 0, &addr, addrlen)) == -1)
@@ -147,18 +203,16 @@ udpsend(unsigned char ip[4], int fd, void *data, size_t n)
 
 /* recvfrom UDP wrapper */
 static ssize_t
-udprecv(unsigned char ip[4], int fd, void *data, size_t n)
+udprecv(int fd, void *data, size_t n)
 {
-	struct sockaddr addr;
-	socklen_t addrlen = sizeof(addr);
 	ssize_t r;
 
-	iptoaddr(&addr, ip, 68); /* bootp client */
-	if ((r = recvfrom(fd, data, n, 0, &addr, &addrlen)) == -1)
+	if ((r = recv(fd, data, n, 0)) == -1)
 		eprintf("recvfrom:");
 
 	return r;
 }
+#endif
 
 static void
 setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4])
@@ -267,7 +321,7 @@ hnoptput(unsigned char *p, int opt, uint32_t data, size_t len)
 static void
 dhcpsend(int type, int how)
 {
-	unsigned char *ip, *p;
+	unsigned char *p;
 
 	memset(&bp, 0, sizeof(bp));
 	hnput(bp.op, Bootrequest, 1);
@@ -277,10 +331,10 @@ dhcpsend(int type, int how)
 	hnput(bp.flags, Fbroadcast, sizeof(bp.flags));
 	hnput(bp.secs, time(NULL) - starttime, sizeof(bp.secs));
 	memcpy(bp.magic, magic, sizeof(bp.magic));
-	memcpy(bp.chaddr, hwaddr, sizeof(bp.chaddr));
+	memcpy(bp.chaddr, hwaddr, sizeof(hwaddr));
 	p = bp.optdata;
 	p = hnoptput(p, ODtype, type, 1);
-	p = optput(p, ODclientid, cid, sizeof(cid));
+	p = optput(p, ODclientid, cid, cid_len);
 	p = optput(p, OBhostname, (unsigned char *)hostname, strlen(hostname));
 
 	switch (type) {
@@ -299,8 +353,7 @@ dhcpsend(int type, int how)
 	}
 	*p++ = OBend;
 
-	ip = (how == Broadcast) ? IP(255, 255, 255, 255) : server;
-	udpsend(ip, sock, &bp, p - (unsigned char *)&bp);
+	udpsend(sock, &bp, p - (unsigned char *)&bp, how);
 }
 
 static int
@@ -319,7 +372,7 @@ dhcprecv(void)
 		eprintf("poll:");
 	if (pfd[0].revents) {
 		memset(&bp, 0, sizeof(bp));
-		udprecv(IP(255, 255, 255, 255), sock, &bp, sizeof(bp));
+		udprecv(sock, &bp, sizeof(bp));
 		optget(&bp, &type, ODtype, sizeof(type));
 		return type;
 	}
@@ -375,14 +428,6 @@ calctimeout(int n, struct itimerspec *ts)
 {
 	if (timerfd_gettime(timers[n], ts) < 0)
 		eprintf("timerfd_gettime:");
-	ts->it_value.tv_nsec /= 2;
-	if (ts->it_value.tv_sec % 2)
-		ts->it_value.tv_nsec += 500000000;
-	ts->it_value.tv_sec /= 2;
-	if (ts->it_value.tv_sec < 60) {
-		ts->it_value.tv_sec = 60;
-		ts->it_value.tv_nsec = 0;
-	}
 }
 
 static void
@@ -393,6 +438,7 @@ run(void)
 	uint32_t renewaltime, rebindingtime, lease;
 
 Init:
+	memset(client, 0, sizeof(client));
 	dhcpsend(DHCPdiscover, Broadcast);
 	timeout.it_value.tv_sec = 1;
 	timeout.it_value.tv_nsec = 0;
@@ -431,6 +477,11 @@ Requesting:
 	/* no response from DHCPREQUEST after several attempts, go to INIT */
 	goto Init;
 Bound:
+#ifdef USE_RAW_SOCKET
+	close(sock);
+	sock = -1;
+#endif
+
 	optget(&bp, mask, OBmask, sizeof(mask));
 	optget(&bp, router, OBrouter, sizeof(router));
 	optget(&bp, dns, OBdnsserver, sizeof(dns));
@@ -441,12 +492,12 @@ Bound:
 	rebindingtime = ntohl(rebindingtime);
 	lease = ntohl(lease);
 	acceptlease();
-	fputs("Congrats! You should be on the 'net.\n", stdout);
-	if (!fflag && !forked) {
+	if (!forked)
+		fputs("Congrats! You should be on the 'net.\n", stdout);
+	if (!fflag && !forked)
 		if (fork())
 			exit(0);
-		forked = 1;
-	}
+	forked = 1; /* doesn't hurt to always set this */
 	timeout.it_value.tv_sec = renewaltime;
 	settimeout(0, &timeout);
 	timeout.it_value.tv_sec = rebindingtime;
@@ -511,12 +562,36 @@ usage(void)
 	eprintf("usage: %s [-d] [-e program] [-f] [-i] [ifname] [clientid]\n", argv0);
 }
 
+static uint8_t fromhex(char nibble)
+{
+	if (nibble >= '0' && nibble <= '9')
+		return nibble - '0';
+	else if (nibble >= 'a' && nibble <= 'f')
+		return nibble - 'a' + 10;
+	else if (nibble >= 'A' && nibble <= 'F')
+		return nibble - 'A' + 10;
+	else
+		eprintf("Bad nibble %c\n", nibble);
+	return 0; // unreachable
+}
+
+static int str2bytes(const char *str, uint8_t *bytes, int len)
+{
+	int slen = strlen(str);
+	if ((slen & 1) || slen > (len * 2))
+		printf("invalid CID");
+
+	while (*str) {
+		*bytes = (fromhex(*str++) << 4);
+		*bytes++ |= fromhex(*str++);
+	}
+
+	return slen / 2;
+}
+
 int
 main(int argc, char *argv[])
 {
-	int bcast = 1;
-	struct ifreq ifreq;
-	struct sockaddr addr;
 	int rnd;
 	size_t i;
 
@@ -540,31 +615,27 @@ main(int argc, char *argv[])
 
 	if (argc)
 		ifname = argv[0]; /* interface name */
-	if (argc >= 2)
-		strlcpy((char *)cid, argv[1], sizeof(cid)); /* client-id */
+	if (argc >= 2) {  /* client-id */
+		if (strncmp(argv[1], "0x", 2) == 0)
+			cid_len = str2bytes(argv[1] + 2, cid, sizeof(cid));
+		else {
+			strlcpy((char *)cid, argv[1], sizeof(cid));
+			cid_len = strlen((char *)cid);
+		}
+	}
 
-	memset(&ifreq, 0, sizeof(ifreq));
 	signal(SIGTERM, cleanexit);
 
 	if (gethostname(hostname, sizeof(hostname)) == -1)
 		eprintf("gethostname:");
 
-	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
-		eprintf("socket:");
-	if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) == -1)
-		eprintf("setsockopt:");
+	sock = open_socket();
 
-	strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
-	ioctl(sock, SIOCGIFINDEX, &ifreq);
-	if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)) == -1)
-		eprintf("setsockopt:");
-	iptoaddr(&addr, IP(255, 255, 255, 255), 68);
-	if (bind(sock, (void*)&addr, sizeof(addr)) != 0)
-		eprintf("bind:");
-	ioctl(sock, SIOCGIFHWADDR, &ifreq);
-	memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(ifreq.ifr_hwaddr.sa_data));
-	if (!cid[0])
-		memcpy(cid, hwaddr, sizeof(cid));
+	if (cid_len == 0) {
+		cid[0] = 1;
+		memcpy(cid + 1, hwaddr, ETHER_ADDR_LEN);
+		cid_len = ETHER_ADDR_LEN + 1;
+	}
 
 	if ((rnd = open("/dev/urandom", O_RDONLY)) == -1)
 		eprintf("can't open /dev/urandom to generate unique transaction identifier:");

Reply via email to