udp checksum implementation error in FreeBSD 7.2?

sthaug at nethelp.no sthaug at nethelp.no
Tue Jun 28 19:09:11 UTC 2011


> > What we observe is:
> > 
> > DHCP Request with UDP checksum set => Packet reaches DHCP Daemon and
> > is being
> > answered.
> > DHCP Request with UDP checksum 0x0000 => ICMP Port Unreachable from
> > FreeBSD.
> > 
> > Can someone confirm this non RFC conform behaviour and knows how to
> > fix it?
> > 
> Well, I took a quick look at the sources (which are in sys/netinet/udp_usrreq.c
> in a function called udp_input() at about line#300) and it only does the
> checksum if it is non-zero. It looks like:
>    if (uh->uh_sum) {
>       ....do checksum 
> (If you don't have kernel sources handy, you can find them here:
>   http://svn.freebsd.org/viewvc/base/releng/7.2)

As another poster commented, ISC dhcpd by default uses BPF, *not* the
kernel UDP implementation - this is done to be able to handle broadcast
packets. However, the mystery doesn't end here - because the ISC dhcpd
implementation *also* only cares about UDP packets with a non-zero
checksum. The code is in common/packet.c, routine decode_udp_ip_header()
which is used by BPF and other link level access methods (e.g. DLPI on
Solaris). From dhcp-4.2.0-P2/common/packet.c:

----------------------------------------------------------------------
  /* Compute UDP checksums, including the ``pseudo-header'', the UDP
     header and the data.   If the UDP checksum field is zero, we're
     not supposed to do a checksum. */

  data = upp + sizeof(udp);
  len = ulen - sizeof(udp);

  usum = udp.uh_sum;
  udp.uh_sum = 0;

  /* XXX: We have to pass &udp, because we have to zero the checksum
   * field before calculating the sum...'upp' isn't zeroed.
   */
  sum = wrapsum(checksum((unsigned char *)&udp, sizeof(udp),
                         checksum(data, len,
                                  checksum((unsigned char *)&ip.ip_src,
                                           8, IPPROTO_UDP + ulen))));

  udp_packets_seen++;
  if (usum && usum != sum) {
          udp_packets_bad_checksum++;
          if (udp_packets_seen > 4 &&
              (udp_packets_seen / udp_packets_bad_checksum) < 2) {
                  log_info ("%d bad udp checksums in %d packets",
                            udp_packets_bad_checksum, udp_packets_seen);
                  udp_packets_seen = udp_packets_bad_checksum = 0;
          }
          return -1;
  }
----------------------------------------------------------------------

The way I read the code - the UDP checksum is computed in all cases,
but is only *compared* with the original checksum field of the packet
if this field is non-zero.

Returning to the original claim of

> DHCP Request with UDP checksum 0x0000 => ICMP Port Unreachable from
> FreeBSD.

I can see (e.g. using tcpdump) FreeBSD handle packets with a UDP checksum
field of 0 just fine - for instance on a busy name server. So I am quite
confident that your observed ICMP Port Unreachable is not generated by
the FreeBSD kernel, as long as you have the DHCP server listening on UDP
port 67.

Steinar Haug, Nethelp consulting, sthaug at nethelp.no



More information about the freebsd-net mailing list