PF IPv6 not working with PF

Hi, i have a problem with PF and IPv6 in 2 different servers working very well under IPv4

If i disable PF (pfctl -d), i can access to the server and ping outside:

ping6 ipv6.google.com
PING6(56=40+8+8 bytes) 2a02:xx:x:xxx:20c:29ff:fece:xxxx --> 2a00:1450:4003:802::200e
16 bytes from 2a02:xx:x:xxx:20c:29ff:fece:xxxx, icmp_seq=0 hlim=58 time=0.868 ms
16 bytes from 2a02:xx:x:xxx:20c:29ff:fece:xxxx, icmp_seq=1 hlim=58 time=0.802 ms

But with PF activated, ping is not working and server is not accesible by IPv6. I'm trying with a minial pf.conf, its as follow:

Code:
ext_if="vmx0"
set skip on lo0
set block-policy drop
set loginterface $ext_if

ext_if_ip="xx.xx.xx.xx"
ext_if_ipv6="2a02:xx:x:xxx:20c:29ff:fece:xxxx"

# ICMP Types
icmp_types = "{ echorep, unreach, squench, echoreq, timex, paramprob }"
icmp6_types = "{ unreach, toobig, timex, paramprob, echoreq, echorep, neighbradv, neighbrsol, routeradv, routersol }"

scrub in all

webports = "{http, https}"

block in all
pass quick on lo0 all
pass quick on $ext_if proto ipv6

pass inet proto icmp icmp-type echoreq
pass inet6 proto ipv6-frag
pass in on $ext_if inet6 proto icmp6 all icmp6-type $icmp6_types allow-opts

pass proto tcp from any to any port $webports
pass proto tcp from any to $ext_if_ipv6 port $webports

I tried a lot of configs, but the problem is always with the default directive of "block in all" (or another block by default conf)

IPv6 config in my rc.conf is:

ipv6_network_interfaces="vmx0"
ifconfig_vmx0_ipv6="inet6 accept_rtadv"

Do you have any idea?

Thank you very much
 
Aside from that you want to remove the 'pass inet6 proto ipv6-frag' line, and use 'scrub in all fragment reassemble'. pf can reassemble fragmented IPv6 packets, which means your rules cannot be trivially bypassed by fragmenting the packet.
 
You need to allow some outgoing ICMP6 too. Most notably NDP. Currently you're only allowing some incoming ICMP6.

https://en.wikipedia.org/wiki/Neighbor_Discovery_Protocol

Thank you for your reply

I tried with:

## allow icmp6 for getting address using IPv6 autoconfiguration from router
pass inet6 proto ipv6-icmp all icmp6-type routeradv
pass inet6 proto ipv6-icmp all icmp6-type routersol

## allow icmp6 for getting neighbor addresses
pass inet6 proto ipv6-icmp all icmp6-type neighbradv
pass inet6 proto ipv6-icmp all icmp6-type neighbrsol

## allow icmp6 echo, not required, but sometimes nice
pass in inet6 proto ipv6-icmp all icmp6-type echoreq

## pass icmp-types: unreachable, time exceeded, parameter problem
pass in inet6 proto ipv6-icmp all icmp6-type {1 3 4}

Also i tried with:

scrub in on $ext_if all fragment reassemble

But nothing worked. This is the full pf.conf i have now:

Code:
ext_if="vmx0"

set skip on lo0
set block-policy drop
set loginterface $ext_if

ext_if_ip="xx.xx.xx.xx"
ext_if_ipv6="2a02:xx:x:xxx:20c:29ff:fece:xxxx"

icmp_types = "{ echorep, unreach, squench, echoreq, timex, paramprob }"
icmp6_types = "{ unreach, toobig, timex, paramprob, echoreq, echorep, neighbradv, neighbrsol, routeradv, routersol }"

scrub in on $ext_if all fragment reassemble

martians = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \
              10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \
              0.0.0.0/8, 240.0.0.0/4  255.255.255.255/32 \
              ::/128 ::1/128 ::ffff:0:0/96 ::/96 100::/64 \
              2001:10::/28 2001:db8::/32 fc00::/7 fe80::/10 \
              fec0::/10 ff00::/8 }"

webports = "{http, https}"

## Set default policy ##
block return in log all

# Drop all Non-Routable Addresses
block drop in quick on $ext_if from $martians to any
block drop out quick on $ext_if from any to $martians

block in quick from <fail2ban>

## Blocking spoofed packets
antispoof quick for $ext_if

pass in quick proto tcp to $ext_if_ip port 19000 keep state (max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)

pass inet proto icmp icmp-type echoreq

# Allow essential outgoing traffic
pass out on $ext_if inet proto { tcp, udp, icmp }
pass out on $ext_if inet6 proto { tcp, udp, icmp6 }
pass out on $ext_if inet6 proto icmp6 all icmp6-type echoreq keep state

pass proto tcp from any to any port $webports
pass proto tcp from any to $ext_if_ipv6 port $webports

## allow icmp6 for getting address using IPv6 autoconfiguration from router
pass inet6 proto ipv6-icmp all icmp6-type routeradv
pass inet6 proto ipv6-icmp all icmp6-type routersol

## allow icmp6 for getting neighbor addresses
pass inet6 proto ipv6-icmp all icmp6-type neighbradv
pass inet6 proto ipv6-icmp all icmp6-type neighbrsol

## allow icmp6 echo, not required, but sometimes nice
pass in inet6 proto ipv6-icmp all icmp6-type echoreq

## pass icmp-types: unreachable, time exceeded, parameter problem
pass in inet6 proto ipv6-icmp all icmp6-type {1 3 4}

Also i tried with this pf.conf:

Code:
ext_comcast_if="vmx0"

set skip on lo0
set block-policy drop
set loginterface $ext_comcast_if

scrub in on $ext_comcast_if all fragment reassemble

block in all

icmp6_types="{ 2, 128 }" # packet too big, echo request (ping6)
# Neighbor Discovery Protocol (NDP) (types 133-137):
#   Router Solicitation (RS), Router Advertisement (RA)
#   Neighbor Solicitation (NS), Neighbor Advertisement (NA)
#   Route Redirection
icmp6_types_ext_if="{ 128, 133, 134, 135, 136, 137 }"
tcp46_services="{ 22 }" # ssh; we allow this in for ALL machines for TCP[46]
tcp46_services_ext_if="{ 53 }" # DNS zone transfer
udp6_services_ext_if="{ 53, 123, 1194, 546}" # 546 == dhcpv6-client

# IPv6
pass in quick on $ext_comcast_if inet6 proto ipv6-icmp icmp6-type $icmp6_types keep state
pass in quick on $ext_comcast_if inet6 proto ipv6-icmp from any to { ($ext_comcast_if ), ff02::1/16 } icmp6-type $icmp6_types_ext_if keep state
pass in quick on $ext_comcast_if inet6 proto tcp from any to any port $tcp46_services flags S/SA keep state
pass in quick on $ext_comcast_if inet6 proto tcp from any to ( $ext_comcast_if ) port $tcp46_services_ext_if flags S/SA keep state
pass in quick on $ext_comcast_if inet6 proto udp from any to ( $ext_comcast_if ) port $udp6_services_ext_if keep state

Nothing worked.
 
So, the problem is in PF or not? I tried with totally different pf.conf's and nothing worked.

And when i disable PF, or delete the default directive of "block in all", it works

What can i do?

Thank you
 
If my recollection is correct;
set skip on lo0
is essentially the same as:
pass quick on lo0 all
but you have both :)

--Chris
 
Hi

Some thoughts about your config upfront:
  • PF enforces a specific ordering of statements that needs to be adhered to: Macros, Tables, Options, Normalization, Queueing, Translation, Filtering.
  • Why would you even bother to define macros for the IPv4/IPv6 addresses of your interface? These addresses are configured on that interface so just use $ext_if if those adresses are static, or ($ext_if) to have PF automatically adjust to any address changes on that interface (which is more likely with IPv6 SLAAC).
  • Do not manually deal with ICMP/ICMP6 error message types (unreach, timex, paramprob) unless you have to. PF normally matches those error messages dynamically to connections via it's state table, so there should normally be no reason to deal with those manually.
  • For IPv6 SLAAC to work correctly, you need to allow outbound icmp6-type routersol and inbound icmp6-type routeradv.
  • For IPv6 Neighbor Discovery Protocol to work correctly, you need to allow icmp6-types neighbrsol, neighbradv in both directions.
  • If you want ping/ping6 to work you need to allow icmp/icmp6 type echoreq. PF will statefully match the resulting echorep packets without need for additional rules.
  • For scrub rules, fragment reassemble is the default if no fragmentation modifier is specified, so you don't need to specify it explicitly.
This is the full pf.conf i have now:

Code:
ext_if="vmx0"

set skip on lo0
set block-policy drop
set loginterface $ext_if

ext_if_ip="xx.xx.xx.xx"
ext_if_ipv6="2a02:xx:x:xxx:20c:29ff:fece:xxxx"

icmp_types = "{ echorep, unreach, squench, echoreq, timex, paramprob }"
icmp6_types = "{ unreach, toobig, timex, paramprob, echoreq, echorep, neighbradv, neighbrsol, routeradv, routersol }"

scrub in on $ext_if all fragment reassemble

martians = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \
              10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \
              0.0.0.0/8, 240.0.0.0/4  255.255.255.255/32 \
              ::/128 ::1/128 ::ffff:0:0/96 ::/96 100::/64 \
              2001:10::/28 2001:db8::/32 fc00::/7 fe80::/10 \
              fec0::/10 ff00::/8 }"

webports = "{http, https}"

## Set default policy ##
block return in log all

# Drop all Non-Routable Addresses
block drop in quick on $ext_if from $martians to any
block drop out quick on $ext_if from any to $martians

block in quick from <fail2ban>

## Blocking spoofed packets
antispoof quick for $ext_if

pass in quick proto tcp to $ext_if_ip port 19000 keep state (max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)

pass inet proto icmp icmp-type echoreq

# Allow essential outgoing traffic
pass out on $ext_if inet proto { tcp, udp, icmp }
pass out on $ext_if inet6 proto { tcp, udp, icmp6 }
pass out on $ext_if inet6 proto icmp6 all icmp6-type echoreq keep state

pass proto tcp from any to any port $webports
pass proto tcp from any to $ext_if_ipv6 port $webports

## allow icmp6 for getting address using IPv6 autoconfiguration from router
pass inet6 proto ipv6-icmp all icmp6-type routeradv
pass inet6 proto ipv6-icmp all icmp6-type routersol

## allow icmp6 for getting neighbor addresses
pass inet6 proto ipv6-icmp all icmp6-type neighbradv
pass inet6 proto ipv6-icmp all icmp6-type neighbrsol

## allow icmp6 echo, not required, but sometimes nice
pass in inet6 proto ipv6-icmp all icmp6-type echoreq

## pass icmp-types: unreachable, time exceeded, parameter problem
pass in inet6 proto ipv6-icmp all icmp6-type {1 3 4}


Try this config and let me know if it works. Only inbound traffic is filtered, filtering outbound traffic also can be done after it works so far.

NOTE: If this machine resides within a RFC1918 private network, it's most certainly a bad idea to filter these addresses by means of the <martians> table. If this is the case, then either remove the address range in use on your network from the <martians> table, or drop the table (and all references to it) entirely, as weeding out those addresses is likely the job of your perimeter firewall in that scenario.
Code:
### MACROS ###

# Our external interface.
ext_if = "vmx0"

# Ports where web services reside.
webports = "{http, https}"

# IPv6 link local prefix.
PFX_LNKLOC = "FE80::/10"

# IPv6 Solicited Node Multicast Prefix.
MC_SOLNOD = "FF02::1:FF00:0/104"

# IPv6 All Nodes Link Local Multicast Address.
MC_NODLNK = "FF02::1"

# Stateful TCP options.
TCP_STATE = "flags S/FSRA keep state"

### TABLES ###

table <martians> const { \
        0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192/24, \
        192.0.2/24, 192.88.99/24, 192.168/16, 198.18/15, 198.51.100/24, \
        203.0.113/24, 224/4, 240/4, 255.255.255.255, \
        ::1/128, ::FFFF:0:0/96, 64:FF9B::/96, 100::/64, 2001:20::/28, \
        2001:DB8::/32, FC00::/7 }

table <fail2ban> persist
table <bruteforce> persist

### OPTIONS ###

set skip on lo0
set block-policy drop
set loginterface $ext_if

### NORMALIZATION ###

scrub in on $ext_if

### QUEUEING ###

### TRANSLATION ###

### FILTERING ###

# By default all inbound traffic is blocked and logged unless explicitly stated otherwise.
block in log all

# Allow NS from unspecified to solicited node multicast address (DAD).
pass quick inet6 proto icmp6 from :: to $MC_SOLNOD icmp6-type neighbrsol no state

# Allow BOOTP/DHCP DISCOVER.
pass quick inet proto udp from 0.0.0.0 port bootpc to 255.255.255.255 port bootps no state

# Block bogus incoming traffic.
block in log quick on $ext_if from { no-route, urpf-failed }

# Allow IPv6 Router Discovery.
pass in quick inet6 proto icmp6 from $PFX_LNKLOC to $MC_NODLNK icmp6-type routeradv no state

# Allow IPv6 Neighbor Discovery (ND/NUD/DAD).
pass in quick inet6 proto icmp6 from { $PFX_LNKLOC, ($ext_if:network) } to { ($ext_if), $MC_SOLNOD } icmp6-type neighbrsol no state
pass in quick inet6 proto icmp6 from { $PFX_LNKLOC, ($ext_if:network) } to { ($ext_if), $MC_NODLNK } icmp6-type neighbradv no state

# Allow this machine to be pinged from the outside.
pass in quick inet6 proto icmp6 icmp6-type echoreq
pass in quick inet proto icmp icmp-type echoreq

# Block traffic from/to forbidden addresses.
block in log quick on $ext_if from { <martians>, <fail2ban>, <bruteforce> }
block out log quick on $ext_if to { <martians>, <fail2ban>, <bruteforce> }

# Allow any outgoing traffic.
pass out quick

# No idea what you got on port 19000 but I guess you know :)
pass in quick proto tcp to port 19000 $TCP_STATE (max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)

# Allow access to web services.
pass in quick proto tcp to port $webports $TCP_STATE
 
Hi

Some thoughts about your config upfront:
  • PF enforces a specific ordering of statements that needs to be adhered to: Macros, Tables, Options, Normalization, Queueing, Translation, Filtering.
  • Why would you even bother to define macros for the IPv4/IPv6 addresses of your interface? These addresses are configured on that interface so just use $ext_if if those adresses are static, or ($ext_if) to have PF automatically adjust to any address changes on that interface (which is more likely with IPv6 SLAAC).
  • Do not manually deal with ICMP/ICMP6 error message types (unreach, timex, paramprob) unless you have to. PF normally matches those error messages dynamically to connections via it's state table, so there should normally be no reason to deal with those manually.
  • For IPv6 SLAAC to work correctly, you need to allow outbound icmp6-type routersol and inbound icmp6-type routeradv.
  • For IPv6 Neighbor Discovery Protocol to work correctly, you need to allow icmp6-types neighbrsol, neighbradv in both directions.
  • If you want ping/ping6 to work you need to allow icmp/icmp6 type echoreq. PF will statefully match the resulting echorep packets without need for additional rules.
  • For scrub rules, fragment reassemble is the default if no fragmentation modifier is specified, so you don't need to specify it explicitly.



Try this config and let me know if it works. Only inbound traffic is filtered, filtering outbound traffic also can be done after it works so far.

NOTE: If this machine resides within a RFC1918 private network, it's most certainly a bad idea to filter these addresses by means of the <martians> table. If this is the case, then either remove the address range in use on your network from the <martians> table, or drop the table (and all references to it) entirely, as weeding out those addresses is likely the job of your perimeter firewall in that scenario.
Code:
### MACROS ###

# Our external interface.
ext_if = "vmx0"

# Ports where web services reside.
webports = "{http, https}"

# IPv6 link local prefix.
PFX_LNKLOC = "FE80::/10"

# IPv6 Solicited Node Multicast Prefix.
MC_SOLNOD = "FF02::1:FF00:0/104"

# IPv6 All Nodes Link Local Multicast Address.
MC_NODLNK = "FF02::1"

# Stateful TCP options.
TCP_STATE = "flags S/FSRA keep state"

### TABLES ###

table <martians> const { \
        0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192/24, \
        192.0.2/24, 192.88.99/24, 192.168/16, 198.18/15, 198.51.100/24, \
        203.0.113/24, 224/4, 240/4, 255.255.255.255, \
        ::1/128, ::FFFF:0:0/96, 64:FF9B::/96, 100::/64, 2001:20::/28, \
        2001:DB8::/32, FC00::/7 }

table <fail2ban> persist
table <bruteforce> persist

### OPTIONS ###

set skip on lo0
set block-policy drop
set loginterface $ext_if

### NORMALIZATION ###

scrub in on $ext_if

### QUEUEING ###

### TRANSLATION ###

### FILTERING ###

# By default all inbound traffic is blocked and logged unless explicitly stated otherwise.
block in log all

# Allow NS from unspecified to solicited node multicast address (DAD).
pass quick inet6 proto icmp6 from :: to $MC_SOLNOD icmp6-type neighbrsol no state

# Allow BOOTP/DHCP DISCOVER.
pass quick inet proto udp from 0.0.0.0 port bootpc to 255.255.255.255 port bootps no state

# Block bogus incoming traffic.
block in log quick on $ext_if from { no-route, urpf-failed }

# Allow IPv6 Router Discovery.
pass in quick inet6 proto icmp6 from $PFX_LNKLOC to $MC_NODLNK icmp6-type routeradv no state

# Allow IPv6 Neighbor Discovery (ND/NUD/DAD).
pass in quick inet6 proto icmp6 from { $PFX_LNKLOC, ($ext_if:network) } to { ($ext_if), $MC_SOLNOD } icmp6-type neighbrsol no state
pass in quick inet6 proto icmp6 from { $PFX_LNKLOC, ($ext_if:network) } to { ($ext_if), $MC_NODLNK } icmp6-type neighbradv no state

# Allow this machine to be pinged from the outside.
pass in quick inet6 proto icmp6 icmp6-type echoreq
pass in quick inet proto icmp icmp-type echoreq

# Block traffic from/to forbidden addresses.
block in log quick on $ext_if from { <martians>, <fail2ban>, <bruteforce> }
block out log quick on $ext_if to { <martians>, <fail2ban>, <bruteforce> }

# Allow any outgoing traffic.
pass out quick

# No idea what you got on port 19000 but I guess you know :)
pass in quick proto tcp to port 19000 $TCP_STATE (max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)

# Allow access to web services.
pass in quick proto tcp to port $webports $TCP_STATE

It works like a charm, thank you very much!

Since this point i can now filter outgoing traffic and tune some options :)
 
It works like a charm, thank you very much!

Since this point i can now filter outgoing traffic and tune some options :)

Glad to hear it works.
Here is a modified version that also filters outgoing traffic. In addition to the previous version, it takes care of allowing outbound router solicitations, so your machine can pick up the IPv6 router on the attached network, and to let outbound neighbor solicitations and advertisements pass. It uses two macros TCP_OUT_OK and UDP_OUT_OK to define the portnumbers you want to allow for outbound tcp/udp traffic, these can be adjusted as needed. Once you got it in place, you can use something like tcpdump -netttti pflog0 to check for any blocked packets that should be allowed through.
Code:
### MACROS ###

# Our external interface.
ext_if = "vmx0"

# Ports where web services reside.
webports = "{http, https}"

# TCP ports allowed outbound.
TCP_OUT_OK = "{ domain, http, https }"

# UDP ports allowed outbound.
UDP_OUT_OK = "{ domain, ntp }"

# IPv6 link local prefix.
PFX_LNKLOC = "FE80::/10"

# IPv6 Solicited Node Multicast Prefix.
MC_SOLNOD = "FF02::1:FF00:0/104"

# IPv6 All Nodes Link Local Multicast Address.
MC_NODLNK = "FF02::1"

# IPv6 All Routers Link Local Multicast Address.
MC_RTRLNK = "FF02::2"

# Stateful TCP options.
TCP_STATE = "flags S/FSRA keep state"

### TABLES ###

table <martians> const { \
        0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192/24, \
        192.0.2/24, 192.88.99/24, 192.168/16, 198.18/15, 198.51.100/24, \
        203.0.113/24, 224/4, 240/4, 255.255.255.255, \
        ::1/128, ::FFFF:0:0/96, 64:FF9B::/96, 100::/64, 2001:20::/28, \
        2001:DB8::/32, FC00::/7 }

table <fail2ban> persist
table <bruteforce> persist

### OPTIONS ###

set skip on lo0
set block-policy drop
set loginterface $ext_if

### NORMALIZATION ###

scrub in on $ext_if

### QUEUEING ###

### TRANSLATION ###

### FILTERING ###

# By default all traffic is blocked and logged unless explicitly stated otherwise.
block log all

# Allow NS from unspecified to solicited node multicast address (DAD).
pass quick inet6 proto icmp6 from :: to $MC_SOLNOD icmp6-type neighbrsol no state

# Allow BOOTP/DHCP DISCOVER.
pass quick inet proto udp from 0.0.0.0 port bootpc to 255.255.255.255 port bootps no state

# Block bogus incoming traffic.
block in log quick on $ext_if from { no-route, urpf-failed }

# Allow IPv6 Router Discovery (RA in / RS out).
pass in quick inet6 proto icmp6 from $PFX_LNKLOC to $MC_NODLNK icmp6-type routeradv no state
pass out quick inet6 proto icmp6 from ($ext_if) to $MC_RTRLNK icmp6-type routersol no state

# Allow IPv6 Neighbor Discovery (ND/NUD/DAD).
pass in quick inet6 proto icmp6 from { $PFX_LNKLOC, ($ext_if:network) } to { ($ext_if), $MC_SOLNOD } icmp6-type neighbrsol no state
pass in quick inet6 proto icmp6 from { $PFX_LNKLOC, ($ext_if:network) } to { ($ext_if), $MC_NODLNK } icmp6-type neighbradv no state
pass out quick inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } no state

# Allow ping/ping6 in both directions (in/out).
pass quick inet6 proto icmp6 icmp6-type echoreq
pass quick inet proto icmp icmp-type echoreq

# Block traffic from/to forbidden addresses.
block in log quick on $ext_if from { <martians>, <fail2ban>, <bruteforce> }
block out log quick on $ext_if to { <martians>, <fail2ban>, <bruteforce> }

# No idea what you got on port 19000 but I guess you know :)
pass in quick on $ext_if proto tcp to port 19000 $TCP_STATE (max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)

# Allow access to web services.
pass in quick on $ext_if proto tcp to port $webports $TCP_STATE

# Allow outbound traffic.
pass out quick on $ext_if proto tcp to port $TCP_OUT_OK $TCP_STATE
pass out quick on $ext_if proto udp to port $UDP_OUT_OK
 
Back
Top