IP_HDRINCL byte ordering

Roy Marples <[email protected]> Mon, 23 Jun 2025 16:33:25 +0100
Newsgroups gmane.os.netbsd.devel.network
Message-ID <[email protected]>
Since inception on NetBSD, IP_HDRINCL for RAW sockets requires ip_len and ip_off to by sent in host byte order.
https://nxr.netbsd.org/xref/src/sys/netinet/raw_ip.c#369

This makes literally no sense because reading from the same socket you get ip_len and ip_off in network byte order.
Writing to a bpf socket, ip_len and ip_off have to be done in network byte order.
No other part of the system behaves like this (that I know of) and it's not documented in ip(4).

Every other OS does not do this. Or if they did, they don't do it now.
Our Linux compat support for this socket option does nothing special here, so it won't work for Linux binaries.
There is even a comment by a NetBSD developer in the dhclient code complaining that the raw socket code didn't
work on NetBSD and this is likely why as the ancient code there assumes network byte ordering for these fields.

Attached is a patch to fix this by adding IP_HDRINCL_RAW and mapping the linux compat socket option to this.
This allows old binaries and old software to compile and work as before.

Portable software should then be written thusly, assuming all target platforms are modern.

     #ifdef IP_HDRINCL_RAW
             setsockopt(s, IPPROTO_IP, IP_HDRINCL_RAW, &hincl, sizeof(hincl));
     #else
             #ifdef __NetBSD__
             // Either error here or handle byte swapping yourself as before
             #endif
             setsockopt(s, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl));
     #endif

Comments, as always, welcome.

Roy
IP_HDRINCL_RAW.patch (application/octet-stream, 11.9 KB)
diff -r dd9486957114 sbin/ping/ping.c
--- a/sbin/ping/ping.c	Sat Jun 21 02:45:57 2025 +0000
+++ b/sbin/ping/ping.c	Mon Jun 23 16:21:49 2025 +0100
@@ -562,14 +562,14 @@
 				 - optlen);
 	(void) memcpy(opack_ip + 1, optspace, optlen);
 
-	if (prog_setsockopt(s, IPPROTO_IP, IP_HDRINCL,
+	if (prog_setsockopt(s, IPPROTO_IP, IP_HDRINCL_RAW,
 	    (char *) &on, sizeof(on)) < 0)
 		err(EXIT_FAILURE, "Can't set special IP header");
 
 	opack_ip->ip_v = IPVERSION;
 	opack_ip->ip_hl = (sizeof(struct ip)+optlen) >> 2;
 	opack_ip->ip_tos = tos;
-	opack_ip->ip_off = (pingflags & F_DF) ? IP_DF : 0;
+	opack_ip->ip_off = ntohs((pingflags & F_DF) ? IP_DF : 0);
 	opack_ip->ip_ttl = ttl ? ttl : MAXTTL;
 	opack_ip->ip_p = IPPROTO_ICMP;
 	opack_ip->ip_src = src_addr.sin_addr;
@@ -914,7 +914,7 @@
 		opack_icmp.icmp_cksum = in_cksum((u_int16_t *)&opack_icmp,
 		    ICMP_MINLEN);
 		sw = 0;
-		if (prog_setsockopt(sloop, IPPROTO_IP, IP_HDRINCL,
+		if (prog_setsockopt(sloop, IPPROTO_IP, IP_HDRINCL_RAW,
 			       (char *)&sw, sizeof(sw)) < 0)
 			err(EXIT_FAILURE, "Can't turn off special IP header");
 		if (prog_sendto(sloop, (char *) &opack_icmp,
@@ -953,7 +953,7 @@
 	opack_icmp.icmp_cksum = in_cksum((u_int16_t *)&opack_icmp, cc);
 
 	cc += opack_ip->ip_hl<<2;
-	opack_ip->ip_len = cc;
+	opack_ip->ip_len = htons(cc);
 	i = prog_sendto(s, (char *) opack_ip, cc, 0,
 		   (struct sockaddr *)&send_addr, sizeof(struct sockaddr_in));
 	if (i != cc) {
@@ -1131,9 +1131,9 @@
 			SET(ntohs((u_int16_t)icp->icmp_seq));
 		}
 
-		if (tot_len != opack_ip->ip_len) {
+		if (tot_len != ntohs(opack_ip->ip_len)) {
 			PR_PACK_SUB();
-			switch (opack_ip->ip_len - tot_len) {
+			switch (ntohs(opack_ip->ip_len) - tot_len) {
 			case MAX_IPOPTLEN:
 				if ((pongflags & F_RECORD_ROUTE) != 0)
 					break;
@@ -1155,7 +1155,7 @@
 			default:
 			out:
 				(void)printf("\nwrong total length %d "
-				    "instead of %d", tot_len, opack_ip->ip_len);
+				    "instead of %d", tot_len, ntohs(opack_ip->ip_len));
 				break;
 			}
 		}
@@ -1774,9 +1774,9 @@
 
 	(void)printf("\n Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src	     Dst\n");
 	(void)printf("  %1x  %1x  %02x %04x %04x",
-		     ip->ip_v, ip->ip_hl, ip->ip_tos, ip->ip_len, ip->ip_id);
+		     ip->ip_v, ip->ip_hl, ip->ip_tos, ntohs(ip->ip_len), ip->ip_id);
 	(void)printf("   %1x %04x",
-		     ((ip->ip_off)&0xe000)>>13, (ip->ip_off)&0x1fff);
+		     (ntohs(ip->ip_off)&0xe000)>>13, (ntohs(ip->ip_off)&0x1fff));
 	(void)printf("  %02x  %02x %04x",
 		     ip->ip_ttl, ip->ip_p, ip->ip_sum);
 	(void)printf(" %15s ",
diff -r dd9486957114 share/man/man4/ip.4
--- a/share/man/man4/ip.4	Sat Jun 21 02:45:57 2025 +0000
+++ b/share/man/man4/ip.4	Mon Jun 23 16:21:49 2025 +0100
@@ -436,19 +436,34 @@
 header prepended to them (based on the destination address and the
 protocol number the socket is created with), unless the
 .Dv IP_HDRINCL
+or
+.Dv IP_HDRINCL_RAW
 option has been set.
 Incoming packets are received with IP header and options intact.
 .Pp
-.Dv IP_HDRINCL
-indicates the complete IP header is included with the data and may
+.Dv IP_HDRINCL_RAW
+indicates the complete IP header is included with the outgoing packet and may
 be used only with the
 .Dv SOCK_RAW
 type.
+.Pp
+.Dv IP_HDRINCL
+is the same as
+.Dv IP_HDRINCL_RAW
+except that the
+.Fa ip_off
+and
+.Fa ip_len
+fields are in host byte order, rather than network byte order.
 .Bd -literal
 #include <netinet/ip.h>
 
 int hincl = 1;                  /* 1 = on, 0 = off */
-setsockopt(s, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl));
+#ifdef IP_HDRINCL_RAW
+	setsockopt(s, IPPROTO_IP, IP_HDRINCL_RAW, &hincl, sizeof(hincl));
+#else
+	setsockopt(s, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl));
+#endif
 .Ed
 .Pp
 Unlike previous
@@ -459,7 +474,8 @@
 ip->ip_v = IPVERSION;
 ip->ip_hl = hlen >> 2;
 ip->ip_id = 0;  /* 0 means kernel set appropriate value */
-ip->ip_off = offset;
+ip->ip_off = htons(offset);
+ip->ip_len = htons(len);
 .Ed
 .Pp
 If the header source address is set to
@@ -511,6 +527,15 @@
 .Dv IP_RECVPKTINFO
 option instead.
 Source code compatibility with both environments is thus maintained.
+.Pp
+Portable code that uses
+.Dv IP_HDRINCL
+should #ifdef for
+.Dv IP_HDRINCL_RAW
+and use that first, falling back to
+.Dv IP_HDRINCL
+which should also be network byte order outside of
+.Nx .
 .Sh SEE ALSO
 .Xr getsockopt 2 ,
 .Xr recv 2 ,
diff -r dd9486957114 sys/compat/linux/common/linux_socket.c
--- a/sys/compat/linux/common/linux_socket.c	Sat Jun 21 02:45:57 2025 +0000
+++ b/sys/compat/linux/common/linux_socket.c	Mon Jun 23 16:21:49 2025 +0100
@@ -946,7 +946,7 @@
 	case LINUX_IP_TTL:
 		return IP_TTL;
 	case LINUX_IP_HDRINCL:
-		return IP_HDRINCL;
+		return IP_HDRINCL_RAW;
 	case LINUX_IP_MULTICAST_TTL:
 		return IP_MULTICAST_TTL;
 	case LINUX_IP_MULTICAST_LOOP:
diff -r dd9486957114 sys/netinet/in.h
--- a/sys/netinet/in.h	Sat Jun 21 02:45:57 2025 +0000
+++ b/sys/netinet/in.h	Mon Jun 23 16:21:49 2025 +0100
@@ -297,6 +297,7 @@
 #define	IP_PKTINFO		25   /* struct; set default src if/addr */
 #define	IP_RECVPKTINFO		26   /* int; receive dst if/addr w/dgram */
 #define	IP_BINDANY		27   /* bool: allow bind to any address */
+#define	IP_HDRINCL_RAW		28   /* int; header is included with data */
 #define IP_SENDSRCADDR IP_RECVDSTADDR /* FreeBSD compatibility */
 
 /*
diff -r dd9486957114 sys/netinet/in_pcb.h
--- a/sys/netinet/in_pcb.h	Sat Jun 21 02:45:57 2025 +0000
+++ b/sys/netinet/in_pcb.h	Mon Jun 23 16:21:49 2025 +0100
@@ -178,6 +178,7 @@
 #define	INP_RECVTTL		0x0800	/* receive incoming IP TTL */
 #define	INP_RECVPKTINFO		0x1000	/* receive IP dst if/addr */
 #define	INP_BINDANY		0x2000	/* allow bind to any address */
+#define	INP_HDRINCL_RAW		0x4000	/* user supplies entire IP header */
 #define	INP_CONTROLOPTS		(INP_RECVOPTS|INP_RECVRETOPTS|INP_RECVDSTADDR|\
 				INP_RECVIF|INP_RECVTTL|INP_RECVPKTINFO)
 
diff -r dd9486957114 sys/netinet/raw_ip.c
--- a/sys/netinet/raw_ip.c	Sat Jun 21 02:45:57 2025 +0000
+++ b/sys/netinet/raw_ip.c	Mon Jun 23 16:21:49 2025 +0100
@@ -320,7 +320,7 @@
 	 * If the user handed us a complete IP packet, use it.
 	 * Otherwise, allocate an mbuf for a header and fill it in.
 	 */
-	if ((inp->inp_flags & INP_HDRINCL) == 0) {
+	if ((inp->inp_flags & (INP_HDRINCL | INP_HDRINCL_RAW)) == 0) {
 		if ((m->m_pkthdr.len + sizeof(struct ip)) > IP_MAXPACKET) {
 			error = EMSGSIZE;
 			goto release;
@@ -340,6 +340,8 @@
 		ip->ip_ttl = in4p_ip(inp).ip_ttl ? in4p_ip(inp).ip_ttl : MAXTTL;
 		opts = inp->inp_options;
 	} else {
+		int hlen, ip_len;
+
 		if (m->m_pkthdr.len > IP_MAXPACKET) {
 			error = EMSGSIZE;
 			goto release;
@@ -348,7 +350,26 @@
 			error = EINVAL;
 			goto release;
 		}
+
 		ip = mtod(m, struct ip *);
+		hlen = ip->ip_hl << 2;
+
+		if (inp->inp_flags & INP_HDRINCL_RAW)
+			ip_len = ntohs(ip->ip_len);
+		else
+			ip_len = ip->ip_len;
+
+		/* Don't allow packet length sizes that will crash. */
+		if (hlen < sizeof(struct ip) || hlen > ip_len ||
+		    m->m_pkthdr.len != ip_len) {
+			error = EINVAL;
+			goto release;
+		}
+
+		if (ip->ip_v != IPVERSION) {
+			error = EINVAL;
+			goto release;
+		}
 
 		/*
 		 * If the mbuf is read-only, we need to allocate
@@ -356,8 +377,6 @@
 		 * modify the header.
 		 */
 		if (M_READONLY(m)) {
-			int hlen = ip->ip_hl << 2;
-
 			m = m_copyup(m, hlen, (max_linkhdr + 3) & ~3);
 			if (m == NULL) {
 				error = ENOMEM;
@@ -366,13 +385,10 @@
 			ip = mtod(m, struct ip *);
 		}
 
-		/* XXX userland passes ip_len and ip_off in host order */
-		if (m->m_pkthdr.len != ip->ip_len) {
-			error = EINVAL;
-			goto release;
+		if ((inp->inp_flags & INP_HDRINCL_RAW) == 0) {
+			HTONS(ip->ip_len);
+			HTONS(ip->ip_off);
 		}
-		HTONS(ip->ip_len);
-		HTONS(ip->ip_off);
 
 		if (ip->ip_id != 0 || m->m_pkthdr.len < IP_MINFRAGSIZE)
 			flags |= IP_NOIPNEWID;
@@ -415,7 +431,7 @@
 			if (error)
 				goto out;
 			if (optval) {
-				inp->inp_flags &= ~INP_HDRINCL;
+				inp->inp_flags &= ~(INP_HDRINCL | INP_HDRINCL_RAW);
 				inp->inp_flags |= INP_NOHEADER;
 			} else
 				inp->inp_flags &= ~INP_NOHEADER;
@@ -437,6 +453,15 @@
 			else
 				inp->inp_flags &= ~INP_HDRINCL;
 			break;
+		case IP_HDRINCL_RAW:
+			error = sockopt_getint(sopt, &optval);
+			if (error)
+				break;
+			if (optval)
+				inp->inp_flags |= INP_HDRINCL_RAW;
+			else
+				inp->inp_flags &= ~INP_HDRINCL_RAW;
+			break;
 
 #ifdef MROUTING
 		case MRT_INIT:
@@ -465,6 +490,10 @@
 			optval = inp->inp_flags & INP_HDRINCL;
 			error = sockopt_set(sopt, &optval, sizeof(optval));
 			break;
+		case IP_HDRINCL_RAW:
+			optval = inp->inp_flags & INP_HDRINCL_RAW;
+			error = sockopt_set(sopt, &optval, sizeof(optval));
+			break;
 
 #ifdef MROUTING
 		case MRT_VERSION:
diff -r dd9486957114 sys/rump/net/lib/libsockin/sockin_user.c
--- a/sys/rump/net/lib/libsockin/sockin_user.c	Sat Jun 21 02:45:57 2025 +0000
+++ b/sys/rump/net/lib/libsockin/sockin_user.c	Mon Jun 23 16:21:49 2025 +0100
@@ -107,6 +107,7 @@
 	translate(IP_MULTICAST_IF);
 	translate(IP_ADD_MEMBERSHIP);
 	translate(IP_DROP_MEMBERSHIP);
+	translate(IP_HDRINCL_RAW);
 	default: return -1;
 	}
 }
diff -r dd9486957114 usr.bin/kdump/kdump.c
--- a/usr.bin/kdump/kdump.c	Sat Jun 21 02:45:57 2025 +0000
+++ b/usr.bin/kdump/kdump.c	Mon Jun 23 16:21:49 2025 +0100
@@ -703,6 +703,7 @@
 	CASERETURN(IP_PKTINFO);
 	CASERETURN(IP_RECVPKTINFO);
 	CASERETURN(IP_BINDANY);
+	CASERETURN(IP_HDRINCL_RAW);
 	default:
 		return NULL;
 	}
diff -r dd9486957114 usr.sbin/mrouted/igmp.c
--- a/usr.sbin/mrouted/igmp.c	Sat Jun 21 02:45:57 2025 +0000
+++ b/usr.sbin/mrouted/igmp.c	Mon Jun 23 16:21:49 2025 +0100
@@ -152,7 +152,7 @@
     }
 
     iphdrlen  = ip->ip_hl << 2;
-    ipdatalen = ip->ip_len;
+    ipdatalen = ntohs(ip->ip_len);
     if (iphdrlen + ipdatalen != recvlen) {
 	logit(LOG_WARNING, 0,
 	    "received packet from %s shorter (%u bytes) than hdr+data length (%u+%u)",
@@ -311,7 +311,7 @@
     ip                      = (struct ip *)send_buf;
     ip->ip_src.s_addr       = src;
     ip->ip_dst.s_addr       = dst;
-    ip->ip_len              = MIN_IP_HEADER_LEN + IGMP_MINLEN + datalen;
+    ip->ip_len              = htons(MIN_IP_HEADER_LEN + IGMP_MINLEN + datalen);
 
     igmp                    = (struct igmp *)(send_buf + MIN_IP_HEADER_LEN);
     igmp->igmp_type         = type;
@@ -335,7 +335,7 @@
     sdst.sin_len = sizeof(sdst);
 #endif
     sdst.sin_addr.s_addr = dst;
-    if (sendto(igmp_socket, send_buf, ip->ip_len, 0,
+    if (sendto(igmp_socket, send_buf, ntohs(ip->ip_len), 0,
 			(struct sockaddr *)&sdst, sizeof(sdst)) < 0) {
 	if (errno == ENETDOWN)
 	    check_vif_state();
diff -r dd9486957114 usr.sbin/mrouted/kern.c
--- a/usr.sbin/mrouted/kern.c	Sat Jun 21 02:45:57 2025 +0000
+++ b/usr.sbin/mrouted/kern.c	Mon Jun 23 16:21:49 2025 +0100
@@ -23,11 +23,17 @@
 
 void k_hdr_include(int onoff)
 {
+#ifdef IP_HDRINCL_RAW
+    if (setsockopt(igmp_socket, IPPROTO_IP, IP_HDRINCL_RAW,
+		   (char *)&onoff, sizeof(onoff)) < 0)
+	logit(LOG_ERR, errno, "setsockopt IP_HDRINCL_RAW %u", onoff);
+#else
 #ifdef IP_HDRINCL
     if (setsockopt(igmp_socket, IPPROTO_IP, IP_HDRINCL,
 		   (char *)&onoff, sizeof(onoff)) < 0)
 	logit(LOG_ERR, errno, "setsockopt IP_HDRINCL %u", onoff);
 #endif
+    #endif
 }
 
 
diff -r dd9486957114 usr.sbin/traceroute/Makefile
--- a/usr.sbin/traceroute/Makefile	Sat Jun 21 02:45:57 2025 +0000
+++ b/usr.sbin/traceroute/Makefile	Mon Jun 23 16:21:49 2025 +0100
@@ -9,6 +9,7 @@
 CPPFLAGS+=-DHAVE_STRERROR=1 -DHAVE_SETLINEBUF=1 -DHAVE_SOCKADDR_SA_LEN=1
 CPPFLAGS+=-DHAVE_RAW_OPTIONS=1
 CPPFLAGS+=-DHAVE_ICMP_NEXTMTU=1
+CPPFLAGS+=-DBYTESWAP_IP_HDR=1 -DBYTESWAP_IP_LEN=1
 
 CPPFLAGS+=-DIPSEC
 LDADD+=	-lipsec
diff -r dd9486957114 usr.sbin/traceroute/traceroute.c
--- a/usr.sbin/traceroute/traceroute.c	Sat Jun 21 02:45:57 2025 +0000
+++ b/usr.sbin/traceroute/traceroute.c	Mon Jun 23 16:21:49 2025 +0100
@@ -805,7 +805,11 @@
 	    sizeof(packlen)) < 0)
 		err(1, "SO_SNDBUF");
 #endif
-#ifdef IP_HDRINCL
+#ifdef IP_HDRINCL_RAW
+	if (prog_setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL_RAW, (char *)&on,
+	    sizeof(on)) < 0)
+		err(1, "IP_HDRINCL");
+#elif defined(IP_HDRINCL)
 	if (prog_setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL, (char *)&on,
 	    sizeof(on)) < 0)
 		err(1, "IP_HDRINCL");