This introduces a new netgraph node type that prevents the upstream network from spoofing ethernet and IP addresses. It is called 'ng_antispoof' (name is open for debate, of course).
What it does:
It validates the upstream address of each packet against a set of rules. If at least one rule matches, the packet is passed through, otherwise it is blocked.
Each rule consists of an ethernet address and an IP or IPv6 address (in a simplified point of view).
How it works:
Each node provides three hooks:
- filter: Upstream node to be protected (e.g. a jail, a VM, ...).
- downstream: Downstream node (e.g. a bridge device, the internet, ...).
- nomatch: Useful for debugging with tcpdump(1). If connected, blocked traffic is forwarded on this hook instead of being discarded. This is output only, traffic arriving on this hook is immediately discarded.
___ +----------------+ __.( ).__ | | ( downstream )<------->| | .._ ( ) _ _.. | | I==========I | ng_antispoof |<------->I filter I | | I==========I nomatch <-------------| | | | +----------------+
Use Case:
Prevent VNET jails from spoofing IP/MAC addresses while using pf(4) as the firewall on the host system.
Example:
Given a virtual network interface host_if on the host system and jail_if in a VNET enabled jail, restrict the jail to the IP 192.168.1.42 with the MAC 1a:00:de:ad:be:ef:
# Create ng_antispoof node ngctl mkpeer host_if: antispoof ether downstream ngctl name host_if:ether as # Add filter rule ngctl msg as: addinet '{ ether=1a:00:de:ad:be:ef ip_addr=192.168.1.42 }' # Plug the jail ngctl connect as: jail_if: filter ether
More Details:
Currently filter rules for IP and IPv6 address types can be created. The maximum number of rules is 65535 (UINT16_MAX).
# For IPv4, subnets can be provided in CIDR or netmask notation ngctl msg as: addinet '{ ether=0a:00:de:ad:be:ef ip_addr=192.168.1.42 }' ngctl msg as: addinet '{ ether=0a:00:de:ad:be:ef ip_addr=192.168.1.0/24 }' ngctl msg as: addinet '{ ether=0a:00:de:ad:be:ef ip_addr=192.168.1.0 ip_mask=255.255.255.0 }' # For IPv6, subnets can be provided in CIDR notation ngctl msg as: addinet6 '{ ether=0a:00:de:ad:be:ef ip6_addr=::1 }' ngctl msg as: addinet6 '{ ether=0a:00:de:ad:be:ef ip6_addr=1:2::/32 }'
Open Questions:
- Is it okay to add the IPv6 parsing type to ng_parse so it can be reused by others?
- Should the IPv4 and IPv6 prefix types for CIDR notation also go into ng_parse, or is this too custom already?
- What is the idiomatic way to provide tests for kernel modules?
- Should VLAN tags be filtered as well (currently all VLAN tags are ignored)?
- Filtering only the outermost tag would probably be the sensible approach here.
- Should a single rule filter a single VID, a range of VIDs or a list (of ranges) of VIDs?
- Filtering a single VID would be possible without any additional overhead (just 4 more bytes on the same cache line).
- Filtering a range would probably impose very little overhead (6 more bytes on the same cache line and an additional evaluation).
- Filtering a list would introduce additional complexity and probably fetch data (the list of allowed VIDs for the current rule) from main memory.