I have attached a more complete diff. This diff has Linux raw sockets,
portability, and some bug fixes. I have also pushed the changes to
github and they can be found here: https://github.com/smaclennan/sdhcp.

The diff is quite big, but not really that complicated. The main change
is moving all the low level socket calls to util/compat.c. And the bulk
of that is the raw udpsend() since you need to build all the headers.
So it looks large, but it is really just a lot of simple assignments.

IMHO this actually makes sdhcp.c slightly more readable (although it
was good before). sdhcp.c has no ifdefs and now really only deals with
the DHCP protocol and not socket details. Timers where also moved to
compat.c.

I have tested the code to work under three main environments:

  1) Linux laptops. Just needed raw socket.

  2) QNX. Just needed Linux specific code removed and MSG_DONTROUTE
  flag when sending. This is my main use case for sdhcp.

  3) My Linux router connecting to Rogers. For this I needed binary cid
  support. I also found I had to send the params option or Rogers would
  not send me the router (GW) address.

I have my dhcp server set for a 2 hour lease so the systems (except
Rogers) renew after an hour. I instrumented sdhcp so it logged that the
renews (and hence the timers) where working.

I kept the timerfd API for Linux... it just seems to work better. I use
timer_create and a set a wrapper functions for other OSes. I then wrote
a compat functions create_timers() that takes a recreate arg. I could
detect when it was a recreate, but I think explicitly setting the arg
makes it cleaner.

The code compiles under FreeBSD (you need -lrt), but doesn't work:
sendto fails with network unreachable. I am not a BSD guy, so I didn't
even try to fix it.

Note that I cheat for non-Linux systems. You must provide a -e script to
actually set the IP parameters. With some work this might not be needed,
but this is just so much easier.

Cheers,
   Sean
diff --git a/Makefile b/Makefile
index 7126a36..687e420 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,7 @@ include config.mk
 
 HDR = util.h arg.h
 LIB = \
+	util/compat.o \
 	util/strlcpy.o \
 	util/eprintf.o
 
@@ -29,6 +30,8 @@ bin: $(BIN)
 
 $(OBJ): $(HDR) config.mk
 
+$(SRC:.c=.o): util.a
+
 .o:
 	@echo LD $@
 	@$(LD) -o $@ $< util.a $(LDFLAGS)
diff --git a/TODO.sam b/TODO.sam
new file mode 100644
index 0000000..af36406
--- /dev/null
+++ b/TODO.sam
@@ -0,0 +1,4 @@
+* multiple DNS
+* domain search
+* NTP?
+* bring interface up
diff --git a/config.mk b/config.mk
index 8f32578..bc449eb 100644
--- a/config.mk
+++ b/config.mk
@@ -10,3 +10,6 @@ LD        = $(CC)
 CPPFLAGS  = -D_DEFAULT_SOURCE
 CFLAGS    = -Wall -Wextra -pedantic -std=c99 $(CPPFLAGS)
 LDFLAGS   = -s
+
+# This is Linux specific
+CFLAGS += -DUSE_RAW_SOCKET
diff --git a/sdhcp.c b/sdhcp.c
index 1fcf5e6..4e0a39f 100644
--- a/sdhcp.c
+++ b/sdhcp.c
@@ -1,9 +1,9 @@
+#include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
-#include <sys/timerfd.h>
 
 #include <netinet/in.h>
-#include <net/if.h>
+#include <netinet/if_ether.h>
 #include <net/route.h>
 
 #include <errno.h>
@@ -82,23 +82,29 @@ enum {
 	OBend =            255,
 };
 
-enum { Broadcast, Unicast };
-
 static Bootp bp;
-static unsigned char magic[] = { 99, 130, 83, 99 };
+static const unsigned char magic[] = { 99, 130, 83, 99 };
+
+static const unsigned char params[] = {
+	OBmask, OBrouter, OBdnsserver, ODlease, ODrenewaltime, ODrebindingtime
+};
+
+/* One socket to rule them all */
+int sock = -1;
 
 /* conf */
 static unsigned char xid[sizeof(bp.xid)];
-static unsigned char hwaddr[16];
-static char hostname[HOST_NAME_MAX + 1];
+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];
+char *ifname = "eth0";
+static unsigned char cid[24];
+static int cid_len;
 static char *program = "";
-static int sock, timers[3];
+int timers[N_TIMERS];
 /* sav */
-static unsigned char server[4];
-static unsigned char client[4];
+unsigned char server[4];
+unsigned char client[4];
 static unsigned char mask[4];
 static unsigned char router[4];
 static unsigned char dns[4];
@@ -107,8 +113,6 @@ static int dflag = 1; /* change DNS in /etc/resolv.conf ? */
 static int iflag = 1; /* set IP ? */
 static int fflag = 0; /* run in foreground */
 
-#define IP(a, b, c, d) (unsigned char[4]){ a, b, c, d }
-
 static void
 hnput(unsigned char *dst, uint32_t src, size_t n)
 {
@@ -118,7 +122,7 @@ hnput(unsigned char *dst, uint32_t src, size_t n)
 		dst[i] = (src >> (n * 8)) & 0xff;
 }
 
-static struct sockaddr *
+struct sockaddr *
 iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port)
 {
 	struct sockaddr_in *in = (struct sockaddr_in *)ifaddr;
@@ -130,39 +134,16 @@ iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port)
 	return ifaddr;
 }
 
-/* sendto UDP wrapper */
-static ssize_t
-udpsend(unsigned char ip[4], int fd, void *data, size_t n)
-{
-	struct sockaddr addr;
-	socklen_t addrlen = sizeof(addr);
-	ssize_t sent;
-
-	iptoaddr(&addr, ip, 67); /* bootp server */
-	if ((sent = sendto(fd, data, n, 0, &addr, addrlen)) == -1)
-		eprintf("sendto:");
-
-	return sent;
-}
-
-/* recvfrom UDP wrapper */
-static ssize_t
-udprecv(unsigned char ip[4], 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)
-		eprintf("recvfrom:");
-
-	return r;
-}
-
 static void
 setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4])
 {
+#ifndef __linux__
+	/* TODO I believe this could work under other OSes. But since the
+	 * -e callout makes it so easy to work around this, I am just
+	 * going to leave it out for now.
+	 */
+	(void)ip; (void)mask; (void)gateway;
+#else
 	struct ifreq ifreq;
 	struct rtentry rtreq;
 	int fd;
@@ -187,6 +168,7 @@ setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4])
 	ioctl(fd, SIOCADDRT, &rtreq);
 
 	close(fd);
+#endif
 }
 
 static void
@@ -245,7 +227,7 @@ optget(Bootp *bp, void *data, int opt, int n)
 }
 
 static unsigned char *
-optput(unsigned char *p, int opt, unsigned char *data, size_t len)
+optput(unsigned char *p, int opt, const unsigned char *data, size_t len)
 {
 	*p++ = opt;
 	*p++ = (unsigned char)len;
@@ -267,7 +249,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 +259,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) {
@@ -290,6 +272,7 @@ dhcpsend(int type, int how)
 		/* memcpy(bp.ciaddr, client, sizeof bp.ciaddr); */
 		p = optput(p, ODipaddr, client, sizeof(client));
 		p = optput(p, ODserverid, server, sizeof(server));
+		p = optput(p, ODparams, params, sizeof(params));
 		break;
 	case DHCPrelease:
 		memcpy(bp.ciaddr, client, sizeof(client));
@@ -299,8 +282,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(&bp, p - (unsigned char *)&bp, how);
 }
 
 static int
@@ -315,11 +297,12 @@ dhcprecv(void)
 	};
 	uint64_t n;
 
-	if (poll(pfd, LEN(pfd), -1) == -1)
-		eprintf("poll:");
+	while (poll(pfd, LEN(pfd), -1) == -1)
+		if (errno != EINTR)
+			eprintf("poll:");
 	if (pfd[0].revents) {
 		memset(&bp, 0, sizeof(bp));
-		udprecv(IP(255, 255, 255, 255), sock, &bp, sizeof(bp));
+		udprecv(&bp, sizeof(bp));
 		optget(&bp, &type, ODtype, sizeof(type));
 		return type;
 	}
@@ -389,10 +372,11 @@ static void
 run(void)
 {
 	int forked = 0, t;
-	struct itimerspec timeout = { 0 };
+	struct itimerspec timeout = { { 0, 0 }, { 0, 0 } };
 	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 +415,8 @@ Requesting:
 	/* no response from DHCPREQUEST after several attempts, go to INIT */
 	goto Init;
 Bound:
+	close_socket(); /* currently raw sockets only */
+
 	optget(&bp, mask, OBmask, sizeof(mask));
 	optget(&bp, router, OBrouter, sizeof(router));
 	optget(&bp, dns, OBdnsserver, sizeof(dns));
@@ -441,12 +427,14 @@ Bound:
 	rebindingtime = ntohl(rebindingtime);
 	lease = ntohl(lease);
 	acceptlease();
-	fputs("Congrats! You should be on the 'net.\n", stdout);
+	if (!forked)
+		fputs("Congrats! You should be on the 'net.\n", stdout);
 	if (!fflag && !forked) {
 		if (fork())
 			exit(0);
-		forked = 1;
+		create_timers(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,14 +499,38 @@ 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;
 
 	ARGBEGIN {
 	case 'd': /* don't update DNS in /etc/resolv.conf */
@@ -540,42 +552,45 @@ 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:");
+	open_socket(ifname);
+	get_hw_addr(ifname, hwaddr);
 
+	/* Check if the interface is down... */
+	memset(&ifreq, 0, sizeof(ifreq));
 	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));
+
+	const int want_flags = IFF_UP | IFF_RUNNING;
+	if (ioctl(sock, SIOCGIFFLAGS, &ifreq))
+		eprintf("get flags");
+	if ((ifreq.ifr_flags & want_flags) != want_flags)
+		eprintf("interface down");
+
+	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:");
 	read(rnd, xid, sizeof(xid));
 	close(rnd);
 
-	for (i = 0; i < LEN(timers); ++i) {
-		timers[i] = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC);
-		if (timers[i] == -1)
-			eprintf("timerfd_create:");
-	}
+	create_timers(0);
 
 	starttime = time(NULL);
 	run();
diff --git a/util.h b/util.h
index 9071712..5fb616d 100644
--- a/util.h
+++ b/util.h
@@ -1,3 +1,6 @@
+#include <time.h>
+#include <net/if.h>
+
 #define MIN(a,b) (((a)<(b))?(a):(b))
 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
 #define bpdump(p,n) 1
@@ -8,3 +11,41 @@ size_t strlcpy(char *, const char *, size_t);
 void weprintf(const char *, ...);
 void eprintf(const char *, ...);
 void enprintf(int, const char *, ...);
+
+/* compat.c */
+
+enum { Broadcast, Unicast };
+
+extern int sock;
+
+#define N_TIMERS 3
+extern int timers[];
+extern unsigned char hwaddr[];
+extern unsigned char server[];
+extern unsigned char client[];
+
+extern char *ifname;
+
+void open_socket(const char *ifname);
+void close_socket(void);
+ssize_t udpsend(void *data, size_t n, int how);
+ssize_t udprecv(void *data, size_t n);
+void get_hw_addr(const char *ifname, unsigned char *hwaddr);
+void create_timers(int recreate);
+
+struct sockaddr *iptoaddr(struct sockaddr *ifaddr,
+						  unsigned char ip[4], int port);
+#define IP(a, b, c, d) (unsigned char[4]){ a, b, c, d }
+
+#ifdef __linux__
+
+#include <sys/timerfd.h>
+
+#else
+
+int timerfd_gettime(int fd, struct itimerspec *curr_value);
+int timerfd_settime(int fd, int flags,
+					const struct itimerspec *new_value,
+					struct itimerspec *old_value);
+
+#endif
diff --git a/util/Makefile b/util/Makefile
new file mode 100644
index 0000000..1844eaf
--- /dev/null
+++ b/util/Makefile
@@ -0,0 +1,2 @@
+all:
+	make -C ..
diff --git a/util/compat.c b/util/compat.c
new file mode 100644
index 0000000..7b349e2
--- /dev/null
+++ b/util/compat.c
@@ -0,0 +1,387 @@
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+#ifdef __linux__
+
+void
+get_hw_addr(const char *ifname, unsigned char *hwaddr)
+{
+	struct ifreq ifreq;
+
+	memset(&ifreq, 0, sizeof(ifreq));
+	strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
+	if (ioctl(sock, SIOCGIFHWADDR, &ifreq))
+		eprintf("SIOCGIFHWADDR");
+
+	memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(ifreq.ifr_hwaddr.sa_data));
+}
+
+void
+create_timers(int recreate)
+{	/* timerfd survives a fork, don't need to recreate */
+	if (recreate == 0)
+		for (int i = 0; i < N_TIMERS; ++i) {
+			timers[i] = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC);
+			if (timers[i] == -1)
+				eprintf("timerfd_create:");
+		}
+}
+
+#else
+
+#include <ifaddrs.h>
+#include <net/if_dl.h>
+
+void
+get_hw_addr(const char *ifname, unsigned char *hwaddr)
+{
+	struct ifaddrs *ifa = NULL;
+	struct sockaddr_dl *sa = NULL;
+
+	if (getifaddrs(&ifa))
+		eprintf("getifaddrs");
+
+	for (struct ifaddrs *p = ifa; p; p = p->ifa_next) {
+		if (p->ifa_addr->sa_family == AF_LINK &&
+			strcmp(p->ifa_name, ifname) == 0) {
+			sa = (struct sockaddr_dl *)p->ifa_addr;
+			if (sa->sdl_type == 1 || sa->sdl_type == 6) { // ethernet
+				memcpy(hwaddr, LLADDR(sa), sa->sdl_alen);
+				freeifaddrs(ifa);
+				return;
+			} else
+				eprintf("INVALID %d", sa->sdl_type);
+		}
+	}
+
+	eprintf("No interface called '%s'", ifname);
+}
+
+#include <signal.h>
+#include <errno.h>
+
+static timer_t t_id[N_TIMERS];
+static int t_wr_pipe[N_TIMERS];
+
+static void
+sigalrm(int sig, siginfo_t *si, void *ctx)
+{
+	(void)sig; (void)ctx;
+
+	unsigned char n = si->si_value.sival_int;
+
+	if (n < N_TIMERS)
+		write(t_wr_pipe[n], &n, sizeof(n));
+}
+
+void
+create_timers(int recreate)
+{
+	struct sigaction act = {
+		.sa_flags = SA_SIGINFO,
+		.sa_sigaction = sigalrm,
+	};
+	struct sigevent ev = {
+		.sigev_notify = SIGEV_SIGNAL,
+		.sigev_signo = SIGALRM,
+	};
+
+	for (int id = 0; id < N_TIMERS; ++id) {
+		ev.sigev_value.sival_int = id;
+
+		if (timer_create(CLOCK_MONOTONIC, &ev, &t_id[id]))
+			eprintf("timer_create");
+	}
+
+	if (recreate)
+		/* the pipes survive the fork() */
+		return;
+
+	if (sigaction(SIGALRM, &act, NULL) < 0)
+		eprintf("sigaction SIGALRM:");
+
+	for (int id = 0; id < N_TIMERS; ++id) {
+		int pipes[2];
+		if (pipe(pipes))
+			eprintf("pipe");
+
+		timers[id] = pipes[0];		/* read end */
+		t_wr_pipe[id] = pipes[1];	/* write end */
+	}
+}
+
+int
+timerfd_gettime(int fd, struct itimerspec *curr_value)
+{
+	for (int i = 0; i < N_TIMERS; ++i)
+		if (timers[i] == fd)
+			return timer_gettime(t_id[i], curr_value);
+
+	errno = EBADF;
+	return -1;
+}
+
+int
+timerfd_settime(int fd, int flags,
+				const struct itimerspec *new_value,
+				struct itimerspec *old_value)
+{
+	for (int i = 0; i < N_TIMERS; ++i)
+		if (timers[i] == fd)
+			return timer_settime(t_id[i], flags, new_value, old_value);
+
+	errno = EBADF;
+	return -1;
+}
+
+#endif
+
+#ifdef USE_RAW_SOCKET
+
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <linux/if_packet.h>
+
+/* Fixed bootp header + 312 for optional */
+#define BOOTP_SIZE (236 + 312)
+
+static struct pkt {
+	struct ether_header ethhdr;
+	struct ip     iphdr;
+	struct udphdr udphdr;
+	unsigned char bootp[BOOTP_SIZE];
+} __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;
+	unsigned char bootp[BOOTP_SIZE];
+} __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;
+}
+
+/* open a socket - ifreq will have ifname filled in */
+void
+open_socket(const char *ifname)
+{
+	int 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:");
+
+	struct ifreq ifreq;
+	memset(&ifreq, 0, sizeof(ifreq));
+	strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
+
+	if (ioctl(sock, SIOCGIFINDEX, &ifreq))
+		eprintf("SIOCGIFINDEX");
+	ifindex = ifreq.ifr_ifindex;
+}
+
+void
+close_socket(void)
+{	/* We close the socket for performance reasons */
+	close(sock);
+	sock = -1;
+}
+
+ssize_t
+udpsend(void *data, size_t n, int how)
+{
+	if (sock == -1)
+		open_socket(ifname);
+
+	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) - BOOTP_SIZE + 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;
+}
+
+ssize_t
+udprecv(void *data, size_t n)
+{
+	struct pkt recv;
+	int r;
+
+	memset(&recv, 0, sizeof(recv));
+	if ((r = read(sock, &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;
+}
+
+#else
+
+/* open a socket - ifreq will have ifname filled in */
+void
+open_socket(const char *ifname)
+{
+	struct ifreq ifreq;
+	int set = 1;
+
+	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+		eprintf("socket:");
+
+	if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &set, sizeof(set)) == -1)
+		eprintf("SO_BROADCAST:");
+
+	memset(&ifreq, 0, sizeof(ifreq));
+	strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
+
+#ifdef SIOCGIFINDEX
+	// SAM I am pretty sure this is not needed
+	if (ioctl(sock, SIOCGIFINDEX, &ifreq))
+		eprintf("SIOCGIFINDEX:");
+#endif
+
+#ifdef SO_BINDTODEVICE
+	if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)) == -1)
+		eprintf("SO_BINDTODEVICE:");
+#endif
+
+	/* needed */
+	struct sockaddr addr;
+	iptoaddr(&addr, IP(0, 0, 0, 0), 68);
+	if (bind(sock, (void*)&addr, sizeof(addr)) != 0)
+		eprintf("bind:");
+}
+
+void close_socket(void) {}
+
+/* sendto UDP wrapper */
+ssize_t
+udpsend(void *data, size_t n, int how)
+{
+	struct sockaddr addr;
+	socklen_t addrlen = sizeof(addr);
+	ssize_t sent;
+	unsigned char ip[4];
+	int flags = 0;
+
+	if (how == Broadcast) {
+		*(uint32_t *)ip = INADDR_BROADCAST;
+		flags |= MSG_DONTROUTE;
+	} else
+		memcpy(ip, server, 4);
+
+	iptoaddr(&addr, ip, 67); /* bootp server */
+	if ((sent = sendto(sock, data, n, flags, &addr, addrlen)) == -1)
+		eprintf("sendto:");
+
+	return sent;
+}
+
+/* recvfrom UDP wrapper */
+ssize_t
+udprecv(void *data, size_t n)
+{
+	ssize_t r;
+
+	if ((r = recv(sock, data, n, 0)) == -1)
+		eprintf("recvfrom:");
+
+	return r;
+}
+
+#endif

Reply via email to