How to receive UDP packets that have no checksum

It seems that IPv4 UDP packets that have no checksum set (i.e. checksum = 0x0000) are filtered out and cannot be received via an UDP socket that is listening even though they are valid UDP packets (according to RFC768).


We are trying to receive IPSec ESP packets that are UDP encapsulated. In this case RFC3948 prescribes that "the IPv4 UDP Checksum SHOULD be transmitted as a zero value".


We are using POSIX sockets (not raw sockets) and are using the NetworkExtension and or developing for macOS.


We have tried

sudo sysctl net.inet.udp.checksum=0

to switch-off the checksum checking, but this didn't seem to make any difference.


Is there a way to receive these packets? Preferrably without needing admin rights.


Thanks

Post not yet marked as solved Up vote post of jsistech Down vote post of jsistech
2.1k views

Answers

Don't you need to set SO_NO_CHECK to receive those packets?


Edit: On the other hand, I notice that setting the checksum to 0 is only a 'should' in the RFC. Can your sender turn checksums on?

Don't you need to set

SO_NO_CHECK
to receive those packets?

Maybe on Linux, but this is BSD Sockets (-: I believe the BSD equivalent is

UDP_NOCKSUM
. This, however, disables checksums on the outgoing side. Similarly, for the
net.inet.udp.checksum
sysctl.

On the incoming side UDP seems to treat a checksum of 0 as valid. Check out the first if statement in

udp_input_checksum
in the Darwin source.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for the prompt reply.


The problem however, seems to be that the checksum verification is offloaded to the NIC. There are kernel parameters (ending in hwcksum_rx for example) that indicate this. This means that the NIC does not even deliver the UDP packets with a zero checksum to the system and the function udp_input_checksum is not even called.

It also seems that the involved kernel parameters cannot be altered. (not even when the System Integrity Protection is switched off, which would not have been a viable solution anyway).

So the question remains, how to get the UDP packets with their checksum set to zero?

Honestly that sounds like a bug in the driver; it seems obvious that the driver’s checksum offload should behave the same as kernel’s.

I don’t know if there’s a way around this; if you want a definitive answer, you should open a DTS tech support incident and I, or one of my colleagues, can dig into it.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Did you get it sorted out? We're running into the exact same issue on a Big Sur system now: IPsec ESP-in-UDP packets are dropped on that system since the packets from the VPN gateway have a UDP checksum of 0 (prior packets with IKE on that same port with valid UDP checksum are received just fine). We can see the packets in tcpdump but they do not get delivered to the socket (a normal UDP socket).

I figured it out. The UDP checksum was a red herring. That actual problem is the kernel having explicit code to grab ESP-in-UDP packets. For macOS 10.15.6 xnu's sources, it can be found at bsd/netinet/udp_usrreq.c in function void udp_input(struct mbuf *m, int iphlen). There's this check:

Code Block c
if (ipsec_bypass == 0 && (esp_udp_encap_port & 0xFFFF) != 0 &&
(uh->uh_dport == ntohs((u_short)esp_udp_encap_port) ||
uh->uh_sport == ntohs((u_short)esp_udp_encap_port))) {
...
}


ipsec_bypass gets set to 0 at various places. esp_udp_encap_port is exposed via the sysctl net.inet.ipsec.esp_port and defaults to 0. But if it gets set to 4500, the system will now "hijack" the UDP packets if they have a SA != 0. That is why the IKE packets (which have a valid checksum in our case) get passed to our socket, but the actual ESP packets are re-routed to the kernel IPsec code.

There are two solutions: Either sysctl -w net.inet.ipsec.esp_port=0 (or something not equal to 4500), or open your NAT-T socket from an arbitrary port. RFC 3947 says we MUST use 4500 for source and destination, but on the other hand a server must handle NAT-T traffic from arbitrary source ports; that's the point of NAT-T.