NetworkConfiguration / dhcpcd

DHCP / IPv4LL / IPv6RA / DHCPv6 client.
https://roy.marples.name/projects/dhcpcd
BSD 2-Clause "Simplified" License
343 stars 111 forks source link

Add option to reject DHCPOFFER and ADV for some value(s) of IPv4 and IPv6 #211

Open moviuro opened 1 year ago

moviuro commented 1 year ago

I'm using dhcpcd(8) to connect an OpenBSD box to my ISP's network. That setup was quite involved (in French) but it now works, mostly.

Sometimes, Orange will block my machine though: ping(8) can no longer reach 1.1.1.1 or 8.8.8.8, so I rebind: from crontab(5):

*       *       *       *       *       -s sleep "$(jot -r 1 10 30)" && ! ping -nc 1 8.8.8.8 > /dev/null 2>&1 && ! ping -nc 1 9.9.9.9 > /dev/null 2>&1 && ifconfig vlan832 && dhcpcd -n
# I will post the output of ifconfig(8) whenever I can, I have just added it to the cronjob

Most of the time, that works, however I sometimes get "punished" and exiled in a 172.16.x.y network, instead of getting a real public IPv4 like I usually get. Same with IPv6 where I get a 2a01:cfc6:xxx:yyy::/64 address instead of a working one.

This situation causes my machine to wait until the next cronjob trigger to find out that it is actually still not connected to the internet, and rad(8) will happily start advertising its 2a01:cfc6:xxx:yyy::/64 prefix to clients in the LAN, causing lots of headaches to the admin (me).

Therefore, I need to reject/refuse known-bad IPs that are delivered to me and not set them on the interfaces. Roy suggested hooks would therefore be inappropriate, as hooks are executed after interfaces have been configured.

zacknewman commented 1 year ago

The rules in pf.conf(5) seem overly restrictive which may cause issues with your ISP. ICMPv6 is quite important, so I think trying to limit it to just a couple types is going to cause more trouble than it's worth. If you insist on filtering it, I would at least follow RFC 4890.

Have you tried having something as basic as the following rules?

set block-policy drop
set skip on lo
wan = <wan_interface>
lan1 = <lan_1>
lan2 = <lan_2>
⋮
lanm = <lan_m>
match out on $wan inet from { $lan1:network $lan2:network … $lanm:network } nat-to $wan
block in quick from urpf-failed
pass out quick
pass in quick inet6 proto icmp6
pass in quick inet proto icmp
pass in quick on $wan inet6 proto udp to port 546
⋮
pass in quick to !<martians

I don't know why you are hardcoding the link-local address of your ISP for example. Also there were a few bugs in dhcpcd that were affecting my OpenBSD setup, but those have since been fixed and pushed to OpenBSD 7.3-stable.

Also if you know the link-local address of your $wan interface will be used, then there is a good chance it won't change and so you could have something like pass in quick on $wan inet6 proto udp to $<wan_link_local> port 546. You can't use self since addresses are determined at pf(4) ruleset load time. Do note that RFC 8415 does not actually state that a link-local address MUST/SHOULD be used though. I know in my situation my ISP sends DHCPv6 info from a GUA to the static link-local address assigned to my $wan.

zacknewman commented 1 year ago

Also, why is 2a01:cfc6:xxx:yyy::/64 not a "working" address? Unless the interface identifier belongs here, the IP address should be a perfectly fine GUA according to IANA which has 2a00::/12 as an allocated GUA block to RIPE NCC.

moviuro commented 1 year ago

ICMP6 is supposedly not blocked. I have the following:

# grep icmp6 /etc/pf.conf
## RFC 4890, icmp6(4)
icmp6_types="{unreach, toobig, timex, paramprob, echoreq, echorep}"
match out log on egress inet6 proto icmp6 from (self) to fe80::ba0:bab icmp6-type { neighbrsol neighbradv } set (prio 6, tos 0xc0)
pass in log quick on egress inet6 proto icmp6 from fe80::ba0:bab to (self) icmp6-type $icmp6_types
pass in log quick on egress inet6 proto icmp6 from fe80::ba0:bab to (self) icmp6-type { routeradv neighbrsol neighbradv }
pass in quick log inet6 proto icmp6 icmp6-type $icmp6_types
pass in quick log on { $guest_if $in_phy_if $kid_if $iot_if } inet6 proto icmp6 #to (self)
# pfctl -vvs rules|grep icmp6
@34 match out log on egress inet6 proto ipv6-icmp from (self:9) to fe80::ba0:bab icmp6-type neighbrsol set (prio 6, tos 0xc0)
@35 match out log on egress inet6 proto ipv6-icmp from (self:9) to fe80::ba0:bab icmp6-type neighbradv set (prio 6, tos 0xc0)
@53 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type unreach
@54 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type toobig
@55 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type timex
@56 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type paramprob
@57 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type echoreq
@58 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type echorep
@59 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type routeradv
@60 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type neighbrsol
@61 pass in log quick on egress inet6 proto ipv6-icmp from fe80::ba0:bab to (self:9) icmp6-type neighbradv
@66 pass in log quick inet6 proto ipv6-icmp all icmp6-type unreach
@67 pass in log quick inet6 proto ipv6-icmp all icmp6-type toobig
@68 pass in log quick inet6 proto ipv6-icmp all icmp6-type timex
@69 pass in log quick inet6 proto ipv6-icmp all icmp6-type paramprob
@70 pass in log quick inet6 proto ipv6-icmp all icmp6-type echoreq
@71 pass in log quick inet6 proto ipv6-icmp all icmp6-type echorep
# pkg_info|grep dhcpcd
dhcpcd-10.0.1pl20230518v0 DHCPv4/IPv4LL/IPv6RS/DHCPv6 quad stack client

I'll keep tcpdump -tttnei vlan832 icmp6 tcpdump -tttnei pflog0 icmp6 running tonight, see what happens.

Also, I have no idea why Orange delivers the 2a01:cfc6:xxx:yyy::/64 IP, but I know for sure that no client gets any reply to their connections if they use that IP. But hey, it's not the first time they would do something dumb: they had their own 1.1.1.1 before Cloudflare unveiled the true one... https://seclists.org/nanog/2018/Apr/84

zacknewman commented 1 year ago

ICMP6 is supposedly not blocked. I have the following:

# grep icmp6 /etc/pf.conf
## RFC 4890, icmp6(4)
icmp6_types="{unreach, toobig, timex, paramprob, echoreq, echorep}"
match out log on egress inet6 proto icmp6 from (self) to fe80::ba0:bab icmp6-type { neighbrsol neighbradv } set (prio 6, tos 0xc0)
pass in log quick on egress inet6 proto icmp6 from fe80::ba0:bab to (self) icmp6-type $icmp6_types
pass in log quick on egress inet6 proto icmp6 from fe80::ba0:bab to (self) icmp6-type { routeradv neighbrsol neighbradv }
pass in quick log inet6 proto icmp6 icmp6-type $icmp6_types
pass in quick log on { $guest_if $in_phy_if $kid_if $iot_if } inet6 proto icmp6 #to (self)

You are only allowing certain ICMPv6 traffic. For example, I don't see where you are allowing ICMPv6 type 133 (aka Router Solicitation) from egress. I don't know why you are matching out only certain ICMPv6 types to a single IPv6 address. It would be much simpler to have pass out quick, pass in quick inet6 proto icmp6, and pass in quick on $wan inet6 proto udp to port 546.

pass out quick allows you to only have to focus on ingress traffic which should be both easier to maintain and more ideal since you would ideally block "bad" traffic as quickly as possible. Yes, technically that allows your router to communicate to any IP (including "martians"); but at that point your router is compromised or you at least have software you shouldn't be running.

Allowing all ICMPv6 traffic not just certain types from/to certain IPs is going to have very little effect on your overall security. Last, you must allow DHCPv6 traffic which per RFC 8415 means UDP traffic from any address and port to port 546 on any address on your egress interface.

I would recommend at least starting out with that much simpler ruleset to see if things work out better. You can iteratively add rules later if this is overly permissive for your needs. I have helped people in the past who insisted their ISP was doing something wrong when in reality it was their pf(4) rules that were the issue.

Also, I have no idea why Orange delivers the 2a01:cfc6:xxx:yyy::/64 IP, but I know for sure that no client gets any reply to their connections if they use that IP. But hey, it's not the first time they would do something dumb: they had their own 1.1.1.1 before Cloudflare unveiled the true one... https://seclists.org/nanog/2018/Apr/84

Wow, sounds like your ISP sucks ass and really may be the problem here. I still recommend trying a more permissive-but-likely-secure-enough ruleset to see if things "magically" work, but that's your call to make.

moviuro commented 1 year ago

No obvious issues with my vlan832 interface when I can't ping anything.

From root@... Wed Jun 14 14:56:53 2023
Delivered-To: root@...
Date: Wed, 14 Jun 2023 14:56:53 +0200 (CEST)
From: root@... (Cron Daemon)
To: root@...
Subject: Cron <root@...> sleep "$(jot -r 1 10 30)" && ! ping -nc1 x.y.z.a > /dev/null 2>&1 && ! ping -nc 1 8.8.8.8 > /dev/null 2>&1 && ! ping -nc 1 9.9.9.9 > /dev/null 2>&1 && ifconfig vlan832 && dhcpcd -n
Auto-Submitted: auto-generated
X-Cron-Env: ...

vlan832: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
        lladdr ...
        description: ISP link
        index 11 priority 0 llprio 6
        encap: vnetid 832 parent em1 txprio packet rxprio outer
        groups: vlan egress
        media: Ethernet autoselect (1000baseT full-duplex)
        status: active
        inet6 fe80::....%vlan832 prefixlen 64 scopeid 0xb
        inet 90.AA.BB.CC netmask 0xfffffc00 broadcast 90.AA.BB.255
sending signal HUP to pid 6244