Commander Gizmo wrote:I'm sure this has already been covered somewhere, but you got me interested. A quick google search had me reading some interesting papers. Forgive me if my interpretation is wrong, as it's been a lot of years since I renewed my CCNA.
According to the
IETF RFC 6935 and
RFC 2460, IPv6 requires that a zero value UDP checksum is invalid and the packet must be dropped. Therefore, to send such a packet, you must use a checksum of 0xFFFF, which indicates "checksum not calculated", or zero. Thus, some equipment apparently erroneously views 0xFFFF = 0x0000 and drops the packet, such as
an example here, and
one here. Basically, it appears to be a somewhat widespread problem in developers not considering the edge case where a checksum of 0xFFFF will need special handling to be distinguished from 0x0000 when doing binary comparisons.
In IPv4, setting the UDP checksum field to 0x0000 indicates that the checksum is not calculated, causing nodes to pass on data that only the end receiver verifies. IPv6 mandates that all UDP packets must have checksums calculated, so 0x0000 is never valid. (Except "Applicability Statement for the Use of IPv6 UDP Datagrams with Zero Checksums" [RFC6936]. This is used for ie: tunneling, where inspection of every UDP packet on all the hops causes higher load than if the validation is left to only the receiver to unbox the tunneled data.)
The stored value in the header,( ie: checksum result) is the
bitwise compliment of the 16 bit 1's compliment sum of certain fields. Because the sum of 1's compliment numbers can only be 0x0000 (ie: +0) if every number is 0, and a UDP packet can never be all 0, the result 0xFFFF(ie: ~(+0)) is not possible; only 0x0000 (ie: ~(-0)) is possible, because by adding you can only get 0xFFFF (ie: -0).
Where the checksum result comes out to 0x0000 naturally, it is transformed to 0xFFFF because of that conflict with the meaning of 0x0000;
0xFFFF does NOT mean that the checksum is not calculated. This transformation is the same in IPv4 as it is in IPv6. Some vendors, however, decided to implement 0xFFFF as a code for diagnostic packets only, perhaps due to lax adherence to standards. Some others, I'm sure, forgot the transformation step, and when calculating the checksum don't transform the result 0x0000 to 0xFFFF on send, or from 0xFFFF to 0x000 on receive(see below for RFC 1142/1624). This implies that roughly 0.0015% of valid random packets get dropped as invalid by only certain devices on the network.
RFC 768: UDP IPv4 wrote:
If the computed checksum is zero, it is transmitted as all ones (the
equivalent in one's complement arithmetic). An all zero transmitted
checksum value means that the transmitter generated no checksum (for
debugging or for higher level protocols that don't care).
RFC 2460: UDP IPv6 wrote:
Unlike IPv4, when UDP packets are originated by an IPv6 node,
the UDP checksum is not optional. That is, whenever
originating a UDP packet, an IPv6 node must compute a UDP
checksum over the packet and the pseudo-header, and, if that
computation yields a result of zero, it must be changed to hex
FFFF for placement in the UDP header.
It's important to note that UDP packets deviate from other IP packets in this regard; in other IP packets, 0xFFFF is not usually considered valid (RFC 1624), and 0x0000 has no special meaning so is a valid checksum. Most of your other links are to discussions on IP in general or TCP/IP. This is one of the reasons why RFC 1624 has a discussion on this topic of positive/negative zeroes, and the
avoiding of comparing checksums directly. As well, an earlier RFC, 1141, included a technical issue regarding positive/negative zeroes due to the nature of 1's compliment arithmetic.
RFC 1624 wrote:
Although this equation appears to work, there are boundary conditions
under which it produces a result which differs from the one obtained
by checksum computation from scratch. This is due to the way zero is
handled in one's complement arithmetic.
In one's complement, there are two representations of zero: the all
zero and the all one bit values, often referred to as +0 and -0.
One's complement addition of non-zero inputs can produce -0 as a
result, but never +0. Since there is guaranteed to be at least one
non-zero field in the IP header, and the checksum field in the
protocol header is the complement of the sum, the checksum field can
never contain ~(+0), which is -0 (0xFFFF). It can, however, contain
~(-0), which is +0 (0x0000).
RFC 1141 yields an updated header checksum of -0 when it should be
+0. This is because it assumed that one's complement has a
distributive property, which does not hold when the result is 0 (see
derivation of [Eqn. 2]).
...
If an end system verifies the checksum by including the checksum
field itself in the one's complement sum and then comparing the
result against -0, as recommended by RFC 1071, it does not matter if
an intermediate system generated a -0 instead of +0 due to the RFC
1141 property described here. In the example above:
0xCD7A + 0x3285 + 0xFFFF = 0xFFFF
0xCD7A + 0x3285 + 0x0000 = 0xFFFF
However, implementations exist which verify the checksum by computing
it and comparing against the header checksum field.
It is recommended that intermediate systems compute incremental
checksum using the method described in this document, and end systems
verify checksum as per the method described in RFC 1071.
Knowing what we know now, the standard -should- have been designed so that 0xFFFF was never a valid checksum, the error in RFC 1141 should not have been made, and UDP should have been designed so that 0xFFFF signalled that the checksum should not be computed by intermediate nodes instead of 0x0000, but hindsight is 20/20. The creators obviously envisaged an implementation like that presented in RFC 1071 where 0x0000 and 0xFFFF are identical from a checksumming standpoint, and didn't expect a comparison of checksums directly.
Expressed as pseudo-code:
Code: Select all
Correct non-RFC 1071 UDP checksum
checksumA = packet.checksum
if checksumA == 0x0000 then process(packet)
if checksumA == 0xFFFF then checksumA = 0x0000
packet.checksum = 0x0000
checksumB = calculate_checksum(packet)
if checksumA == checksumB then process(packet) else drop(packet)
Correct RFC 1071 UDP checksum
checksum = packet.checksum
if checksum == 0x0000 then process(packet)
checksum = calculate_checksum(packet)
if checksum == 0xFFFF then process(packet) else drop(packet)
What actually happens on some non-RFC 1071 systems
checksumA = packet.checksum
if checksumA == 0x0000 then process(packet)
packet.checksum = 0x0000
checksumB = calculate_checksum(packet)
if checksumA == checksumB then process(packet) else drop(packet)
The last one fails in two cases: one, UDP packets which have a final checksum result of 0x0000(ie: ~(-0)) that was transformed to 0xFFFF, and two, packets which are generated with the strange checksum result of 0xFFFF(ie: ~(+0)) from RFC 1141()includes non-UDP packets). This unfortunately exists in production code/devices.
tl;dr:
Systems that do not adhere to the RFC 1071 method of checksum verification run the risk of compatibility issues with RFC 1141 systems which haven't switched to RFC 1624, and systems that do not properly follow RFC 768/2460 for UDP IPv4 and IPv6 respectively will drop certain UDP packets in error. Systems which DO follow RFC 1071 will be unaffected, because that method of checksum verification is equivalent in the case of 0x0000 AND 0xFFFF.