Hi everyone,
I'm on 11.2-STABLE and I'm trying to modify an ICMPv6 packet via a divert socket. The rationale is simple: The router that UPC (Swiss cable company) ships is, frankly, terrible and sends its own DNS servers as part of the ICMPv6 router advertisement. I don't want to use them. Since I already have bridged IPv6 and NATed IPv4 (that rubbish box can't assign IPv4 address ranges outside of 192.168.x.y... sigh) and there is no way to configure the DNS servers on the router, I'm stripping those fields. Should be simple enough, right? Unfortunately, I was unsuccessful so far and I think there are multiple blockers. Let me start with the pf rule I'm using:
This should divert ICMPv6 router advertisements to a divert socket on port 134. But that's when dmesg starts yelling at me:
Question #1: Is there any way to enable support or is this simply not supported at all?
But even if I try to divert other arbitrary IPv4 packets, I'm not receiving them in my purge script (see below).
Question #2: Any hints on why my purge script isn't picking up anything? kldload tells me that ipdivert is already loaded or in kernel. pflog does contain the packets, so the rule apparently works.
Cheers
Lennart
---
I'm on 11.2-STABLE and I'm trying to modify an ICMPv6 packet via a divert socket. The rationale is simple: The router that UPC (Swiss cable company) ships is, frankly, terrible and sends its own DNS servers as part of the ICMPv6 router advertisement. I don't want to use them. Since I already have bridged IPv6 and NATed IPv4 (that rubbish box can't assign IPv4 address ranges outside of 192.168.x.y... sigh) and there is no way to configure the DNS servers on the router, I'm stripping those fields. Should be simple enough, right? Unfortunately, I was unsuccessful so far and I think there are multiple blockers. Let me start with the pf rule I'm using:
Code:
pass in log on $ext_if inet6 proto icmp6 all icmp6-type routeradv divert-to 134
This should divert ICMPv6 router advertisements to a divert socket on port 134. But that's when dmesg starts yelling at me:
Code:
pf: divert(9) is not supported for IPv6
Question #1: Is there any way to enable support or is this simply not supported at all?
But even if I try to divert other arbitrary IPv4 packets, I'm not receiving them in my purge script (see below).
Question #2: Any hints on why my purge script isn't picking up anything? kldload tells me that ipdivert is already loaded or in kernel. pflog does contain the packets, so the rule apparently works.
Cheers
Lennart
---
Python:
#!/usr/bin/env python3
import socket
import struct
import sys
try:
socket.IPPROTO_DIVERT
except AttributeError:
socket.IPPROTO_DIVERT = 254
NATIVE_U16_STRUCT = struct.Struct('@H')
NETWORK_U16_STRUCT = struct.Struct('!H')
NEXT_HEADER_U16, *_ = NATIVE_U16_STRUCT.unpack(b'\x00\x3a')
def recalculate_checksum(view: memoryview, array: bytearray) -> None:
checksum = 0
# Reset checksum to zero
NATIVE_U16_STRUCT.pack_into(array, 42, 0)
# Pseudo-header: Source/Destination
checksum += sum(value for value, *_ in NATIVE_U16_STRUCT.iter_unpack(view[8:40]))
# Pseudo-header: two zero bytes (ignored), followed by the length
payload_length, *_ = NATIVE_U16_STRUCT.unpack_from(view, 4)
checksum += payload_length
# Pseudo-header: three zero bytes (ignored), followed by
# next header field (in 16-bit representation, native decoding)
checksum += NEXT_HEADER_U16
# Calculate checksum for ICMPv6 payload
checksum += sum(value for value, *_ in NATIVE_U16_STRUCT.iter_unpack(view[40:]))
# Add carry bits to checksum
checksum = (checksum >> 16) + (checksum & 0xffff)
checksum += (checksum >> 16)
# Invert checksum
checksum ^= 0xffff
# Update checksum
NATIVE_U16_STRUCT.pack_into(array, 42, checksum)
def handle(view: memoryview, array: bytearray) -> memoryview:
# IPv6 header + ICMPv6 *Router Advertisement* require at least 56 bytes
if len(view) < 56:
return view
# Ensure next header is an ICMPv6 packet
if view[6] != 58:
return view
# Ignore ICMPv6 messages that are not the router advertisement
if view[40] != 134:
return view
# Disable the *MO* flags
# (*managed address configuration*/*other configuration*)
array[45] &= 0x3f
# Go through options...
offset = 56
while True:
option = view[offset:]
# Option header has 2 bytes
if len(option) < 2:
recalculate_checksum(view, array)
return view
# Validate length
length = option[1] * 8
if len(option) < length:
recalculate_checksum(view, array)
return view
# Strip DNS servers
if option[0] == 25:
# Move remaining ICMP options forward and update view
remaining = view[offset + length:]
array[offset:offset + len(remaining)] = remaining
view = view[:len(view) - length]
# Update payload length
NETWORK_U16_STRUCT.pack_into(array, 4, len(view) - 40)
print('STRIPPED!');
else:
offset += length
def main():
# Create socket
with socket.socket(family=socket.AF_INET, type=socket.SOCK_RAW, proto=socket.IPPROTO_DIVERT) as fd:
# Bind socket
fd.bind(('0.0.0.0', 134))
# Create buffer
array = bytearray(4096)
view = memoryview(array)
# Enter loop
while True:
# Receive packet
n_read, address = fd.recvfrom_into(view)
in_view = view[:n_read]
# Modify packet (if needed)
print('HANDLE!');
try:
out_view = handle(in_view, array)
except Exception as exc:
print('Could not modify packet: {}'.format(exc), file=sys.stderr)
# Send packet
fd.sendto(out_view, address)
if __name__ == '__main__':
main()