Skip to content
Open
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
26 changes: 20 additions & 6 deletions scapy/layers/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,9 +558,21 @@ def m2i(self, pkt, x):
x = x[: operator.floordiv(self.af_length, 8)]
return inet_ntop(self.af_familly, x)

def _pack_subnet(self, subnet):
# type: (bytes) -> bytes
def _pack_subnet(self, subnet, plen=None):
# type: (bytes, Optional[int]) -> bytes
packed_subnet = inet_pton(self.af_familly, plain_str(subnet))
if plen is not None:
# RFC 7871: ADDRESS MUST be truncated to the number of bits
# indicated by SOURCE PREFIX-LENGTH, padded with 0 bits to the
# end of the last octet needed. Use ceil(plen / 8) bytes and
# zero out any host bits in the last partial byte.
num_bytes = operator.floordiv(plen + 7, 8)
result = bytearray(packed_subnet[:num_bytes])
rem = plen % 8
if rem and result:
result[-1] &= (0xff << (8 - rem)) & 0xff
return bytes(result)
# When prefix length is not known, strip trailing zero bytes.
for i in list(range(operator.floordiv(self.af_length, 8)))[::-1]:
if packed_subnet[i] != 0:
i += 1
Expand All @@ -571,21 +583,23 @@ def i2m(self, pkt, x):
# type: (Optional[Packet], Optional[Union[str, Net]]) -> bytes
if x is None:
return self.af_default
plen = getattr(pkt, 'source_plen', None)
try:
return self._pack_subnet(x)
return self._pack_subnet(x, plen)
except (OSError, socket.error):
pkt.family = 2
return ClientSubnetv6("", "")._pack_subnet(x)
return ClientSubnetv6("", "")._pack_subnet(x, plen)

def i2len(self, pkt, x):
# type: (Packet, Any) -> int
if x is None:
return 1
plen = getattr(pkt, 'source_plen', None)
try:
return len(self._pack_subnet(x))
return len(self._pack_subnet(x, plen))
except (OSError, socket.error):
pkt.family = 2
return len(ClientSubnetv6("", "")._pack_subnet(x))
return len(ClientSubnetv6("", "")._pack_subnet(x, plen))


class ClientSubnetv6(ClientSubnetv4):
Expand Down
49 changes: 49 additions & 0 deletions test/scapy/layers/dns_edns0.uts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,55 @@ assert raw(d) == raw_d
d = DNSRROPT(raw_d)
assert EDNS0ClientSubnet in d.rdata[0] and d.rdata[0].family == 2 and d.rdata[0].address == "2001:db8::"

= source_plen with trailing-zero address byte (GH #4942)
# RFC 7871: ADDRESS must be truncated to ceil(source_plen/8) bytes.
# For source_plen=23, ceil(23/8)=3 bytes are required even when the
# 3rd byte is 0x00 (101.132.0.0 -> 0x65 0x84 0x00).

b = EDNS0ClientSubnet(source_plen=23, address='101.132.0.0')
raw_b = raw(b)
# optlen must be 7 (4 fixed bytes + 3 address bytes)
assert raw_b == b'\x00\x08\x00\x07\x00\x01\x17\x00\x65\x84\x00', repr(raw_b)

b2 = EDNS0ClientSubnet(raw_b)
assert b2.source_plen == 23
assert b2.address == '101.132.0.0'

= source_plen with non-zero last byte (sanity check)
# For source_plen=24 on 10.20.30.0, exactly 3 bytes are needed.
b = EDNS0ClientSubnet(source_plen=24, address='10.20.30.0')
raw_b = raw(b)
assert raw_b == b'\x00\x08\x00\x07\x00\x01\x18\x00\x0a\x14\x1e', repr(raw_b)
b2 = EDNS0ClientSubnet(raw_b)
assert b2.source_plen == 24
assert b2.address == '10.20.30.0'

= source_plen=0 produces empty address
b = EDNS0ClientSubnet(source_plen=0, address='0.0.0.0')
raw_b = raw(b)
# optlen == 4 (no address bytes), source_plen=0
assert raw_b == b'\x00\x08\x00\x04\x00\x01\x00\x00', repr(raw_b)

= host bits in partial last byte are zeroed out (IPv4)
# source_plen=23 means bits 0-22 are significant; bit 23 (LSB of byte 3)
# must be cleared. 101.132.255.0 has byte 3 = 0xff -> should become 0xfe.
b = EDNS0ClientSubnet(source_plen=23, address='101.132.255.0')
raw_b = raw(b)
assert raw_b == b'\x00\x08\x00\x07\x00\x01\x17\x00\x65\x84\xfe', repr(raw_b)
b2 = EDNS0ClientSubnet(raw_b)
assert b2.source_plen == 23
assert b2.address == '101.132.254.0'

= host bits in partial last byte are zeroed out (IPv6)
# source_plen=33 means 5 bytes needed, last byte has 1 significant bit.
# 2001:db8:ffff:: -> byte 5 = 0xff -> masked to 0x80
b = EDNS0ClientSubnet(family=2, source_plen=33, address='2001:db8:ffff::')
raw_b = raw(b)
# 4 fixed + 5 address bytes = optlen 9
assert raw_b[2:4] == b'\x00\x09', repr(raw_b) # optlen=9
assert raw_b[6] == 33 # source_plen
assert raw_b[-1] == 0x80, repr(raw_b) # last byte masked


+ EDNS0 - Cookie

Expand Down
Loading