Skip to content
This repository was archived by the owner on Oct 20, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions scapy/layers/inet.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,14 +843,22 @@ def post_build(self, p, pay):
tmp_len = len(p)
p = p[:4] + struct.pack("!H", tmp_len) + p[6:]
if self.chksum is None:
# A Padding layer that belongs to an encapsulated protocol (for
# instance Ethernet over VXLAN) is data as far as UDP is
# concerned. The len field tells how many of the following
# padding bytes are inside the datagram, the remaining ones
# being padding of an under layer.
ck_p = p
if tmp_len > len(p):
ck_p += self.payload.build_padding()[:tmp_len - len(p)]
if isinstance(self.underlayer, IP):
ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, p)
ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, ck_p)
# According to RFC768 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501
if ck == 0:
ck = 0xFFFF
p = p[:6] + struct.pack("!H", ck) + p[8:]
elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p) # noqa: E501
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, ck_p) # noqa: E501
# According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501
if ck == 0:
ck = 0xFFFF
Expand Down
210 changes: 210 additions & 0 deletions test/scapy/layers/inet.uts
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,216 @@ assert bpkt.chksum == 0x70bd and bpkt.payload.chksum == 0xbb17

conf.debug_dissector = True

= UDP checksum covers the padding of an encapsulated layer
~ udp
# The Padding of an inner Ethernet frame is data for the outer UDP layer.
# GH#4906: Ether / Dot1Q / IPv6 / UDP / VXLAN / Dot3 / LLC / data / Padding
pkt_data = bytes.fromhex("ffffffffffffffffffffffff8100000086dd60000000010e1100000000000000000000000000000000000000000000000000000000000000000018bd2118010e01a8738a32362d5473c879f89f16a8f9cd673fec8d6b000d9a2976054be71268024a7817f64d63fad1d4eb2db918f004c198dc03e7ec4e836ca554e1b4f0c157c61728ae722bd55eac05d698f04a0df32b8d21f031ae70a7ad530d027915763af0e736fa6d61a334289b3aef64855b44ed09f02de48485a3337575d409561fe192bca02214a8a5b10cc66c756992eadaeaf56f8eeeb58c18cce9f36fd8983ff7890dedc28ed254080c180231440188d5afc32bff2193a8f94a1b683b1e850a613eaf42ccd37e5af706d93b11e31649a1b9f39c613ce5a4263fcfe7873da6fdd9d09c90f15b687efc3342189cbac70ab686d1dc5f000015128c4d3400080000034584f14d8e217811d6cee444")
pkt = Ether(pkt_data)
assert pkt[UDP].chksum == 0x1a8
del pkt[UDP].chksum
assert Ether(raw(pkt))[UDP].chksum == 0x1a8

= UDP checksum covers the padding of an encapsulated layer - IPv4
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IP(raw(IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
assert isinstance(pkt[LLC].payload.payload, Padding)
assert pkt[UDP].chksum == 0x94fb
del pkt[UDP].chksum
assert IP(raw(pkt))[UDP].chksum == 0x94fb

= UDP checksum covers the padding of an encapsulated layer - IPv6
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IPv6(raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
assert pkt[UDP].chksum == 0x499a
del pkt[UDP].chksum
assert IPv6(raw(pkt))[UDP].chksum == 0x499a

= UDP checksum ignores the padding of an under layer (control)
~ udp
# The trailer of the outer Ethernet frame is not part of the datagram
pkt = Ether(raw(Ether(dst="00:11:22:33:44:55", src="66:77:88:99:aa:bb")/IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1, dport=2)/Raw(b"ab")) + b"\x00" * 16)
assert pkt[UDP].len == 10 and isinstance(pkt[Raw].payload, Padding)
del pkt[UDP].chksum
assert Ether(raw(pkt))[UDP].chksum == 0x8e61

= UDP checksum with a len field larger than the available data (control)
~ udp
with no_debug_dissector():
pkt = IP(raw(IP()/UDP(len=100)/Raw(b"AAAA")))

assert pkt[UDP].chksum == 0x7e8f

= UDP checksum with a len field smaller than the payload (control)
~ udp
with no_debug_dissector():
pkt = IP(raw(IP()/UDP(len=10)/Raw(b"AAAAAAAAAAAA")))

assert pkt[UDP].chksum == 0x79dc

= UDP checksum covers the inner padding but not the trailer of the outer frame
~ udp
# Both kinds of padding at once: the inner Ethernet frame is padded inside the
# datagram, the outer Ethernet frame has a trailer outside of it.
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = Ether(raw(Ether(dst="00:11:22:33:44:55", src="66:77:88:99:aa:bb")/IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)) + b"TTTTTTTTTTTTTTTT")
assert pkt[UDP].len == 45 and len([x for x in pkt.iterpayloads() if isinstance(x, Padding)]) == 2
del pkt[UDP].chksum
assert Ether(raw(pkt))[UDP].chksum == 0x94fb

= UDP checksum covers the padding of an encapsulated layer - IPv6 extension header
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IPv6(raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/IPv6ExtHdrDestOpt()/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
assert isinstance(pkt[UDP].underlayer, IPv6ExtHdrDestOpt)
assert pkt[UDP].chksum == 0x499a
del pkt[UDP].chksum
assert IPv6(raw(pkt))[UDP].chksum == 0x499a

= UDP checksum with a len field covering only a part of the padding
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IP(raw(IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
# One single byte of the padding is inside the datagram
pkt[UDP].len = 38
del pkt[UDP].chksum
with no_debug_dissector():
assert IP(raw(pkt))[UDP].chksum == 0xd5f3

= UDP checksum with a len field covering only a part of the padding - IPv6
~ udp
# in6_chksum takes the length of the pseudo header from the buffer it is
# given, where in4_chksum uses the one of the IP layer: both arms of the
# dispatch need the padding bytes that the len field puts in the datagram.
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IPv6(raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 38
del pkt[UDP].chksum
with no_debug_dissector():
assert IPv6(raw(pkt))[UDP].chksum == 0x8a99

= UDP checksum with a len field claiming more padding than the capture holds
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IP(raw(IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 49
del pkt[UDP].chksum
with no_debug_dissector():
assert IP(raw(pkt))[UDP].chksum == 0x94f7

= UDP checksum with a len field claiming more padding than the capture holds - IPv6
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IPv6(raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 49
del pkt[UDP].chksum
with no_debug_dissector():
assert IPv6(raw(pkt))[UDP].chksum == 0x4996

= UDP checksum with a len field below the payload while padding follows (control)
~ udp
# The len field is not an authority to remove data, only to add padding: the
# checksum must be the one of the payload that is built.
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IP(raw(IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 36
del pkt[UDP].chksum
with no_debug_dissector():
assert IP(raw(pkt))[UDP].chksum == 0xd645

= UDP checksum with a len field one short of the whole padding
~ udp
# One byte of the inner padding is left out of the datagram by the len field.
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IP(raw(IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 44
del pkt[UDP].chksum
with no_debug_dissector():
assert IP(raw(pkt))[UDP].chksum == 0xe4fc

= UDP checksum with a len field one short of the whole padding - IPv6
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IPv6(raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 44
del pkt[UDP].chksum
with no_debug_dissector():
assert IPv6(raw(pkt))[UDP].chksum == 0x999c

= UDP checksum with the maximum len field
~ udp
# 0xffff is the largest value the field holds: the slice bound is far past the
# padding, which build_padding() then returns whole.
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IP(raw(IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 65535
del pkt[UDP].chksum
with no_debug_dissector():
assert IP(raw(pkt))[UDP].chksum == 0x9528

= UDP checksum with the maximum len field - IPv6
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IPv6(raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 65535
del pkt[UDP].chksum
with no_debug_dissector():
assert IPv6(raw(pkt))[UDP].chksum == 0x49c7

= UDP checksum with a len field covering only a part of the padding - IPv6 extension header
~ udp
# The boundary lengths on the second operand of the under layer dispatch: an
# extension header derives the pseudo header the same way IPv6 does.
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IPv6(raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/IPv6ExtHdrDestOpt()/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 38
del pkt[UDP].chksum
with no_debug_dissector():
assert IPv6(raw(pkt))[UDP].chksum == 0x8a99

= UDP checksum with a len field reaching into the trailer of the outer frame
~ udp
# build_padding() walks every following Padding layer, so a len field larger
# than the inner padding keeps taking bytes from the outer trailer.
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = Ether(raw(Ether(dst="00:11:22:33:44:55", src="66:77:88:99:aa:bb")/IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)) + b"TTTTTTTTTTTTTTTT")
assert pkt[UDP].payload.build_padding() == b"PPPPPPPP" + b"T" * 16
pkt[UDP].len = 53
del pkt[UDP].chksum
with no_debug_dissector():
assert Ether(raw(pkt))[UDP].chksum == 0x43a2

= UDP checksum with a len field past every padding layer available
~ udp
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = Ether(raw(Ether(dst="00:11:22:33:44:55", src="66:77:88:99:aa:bb")/IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)) + b"TTTTTTTTTTTTTTTT")
pkt[UDP].len = 70
del pkt[UDP].chksum
with no_debug_dissector():
assert Ether(raw(pkt))[UDP].chksum == 0xf23f

= UDP checksum with a len field equal to the payload while padding follows (control)
~ udp
# tmp_len == len(p) leaves the guard false: the padding stays out of the
# checksum, as it does for a packet built from scratch.
inner = raw(Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")) + b"PPPPPPPP"
pkt = IP(raw(IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Raw(inner)))
pkt[UDP].len = 37
del pkt[UDP].chksum
with no_debug_dissector():
assert IP(raw(pkt))[UDP].chksum == 0xd644

= UDP checksum of a packet built from scratch with an explicit Padding (control)
~ udp
# Nothing changes when post_build() computes the len field itself: tmp_len is
# then len(p) and the padding is outside of the datagram.
pkt = IP(src="1.2.3.4", dst="5.6.7.8")/UDP(sport=1111, dport=4789)/VXLAN(flags=0x08, vni=42)/Dot3(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:ff", len=7)/LLC(dsap=0x06, ssap=0x06, ctrl=3)/Raw(b"abcd")/Padding(b"PPPPPPPP")
bpkt = IP(raw(pkt))
assert bpkt[UDP].len == 37
assert bpkt[UDP].chksum == 0xd64c

= IP with forced-length 0
p = IP()/TCP()
p[IP].len = 0
Expand Down