PF How to apply non-numeric mask to IPv6 address

Hi,

I am experimenting with FreeBSD 11.2 as a firewall and I'd like to write firewall rules for IPv6. Since most ISPs only assign dynamic IPv6 prefixes to their customers I need to ignore the IPv6 prefix and only match the host part of an IPv6 address (which is static in my case). This is what I am trying to do:

Code:
pass in quick on pppoe0 inet6 proto tcp to {::1:1:1:1/::ffff:ffff:ffff:ffff} port {22} keep state label "SSH" # Allow SSH to raspberry pi only, ignore /64 prefix

Unfortunately [FONT=courier new]pfctl[/FONT] gives me a syntax error with this notation. I have also tried the [FONT=courier new]/!64[/FONT] syntax which ip6tables uses for this, but this also does not work.

So the big question is: how can I apply a (hexadecimal?) mask to an IPv6 address, so [FONT=courier new]pf[/FONT] ignores the prefix and only matches the host part? Or is there another way of accomplishing this?

BTW: I am getting a new IPv6 prefix every 24 hours, so I cannot hard-code it into the firewall rules.


Thanks in advance,
suzaku
 
Hi,

I am experimenting with FreeBSD 11.2 as a firewall and I'd like to write firewall rules for IPv6. Since most ISPs only assign dynamic IPv6 prefixes to their customers I need to ignore the IPv6 prefix and only match the host part of an IPv6 address (which is static in my case). This is what I am trying to do:

Code:
pass in quick on pppoe0 inet6 proto tcp to {::1:1:1:1/::ffff:ffff:ffff:ffff} port {22} keep state label "SSH" # Allow SSH to raspberry pi only, ignore /64 prefix

Unfortunately [FONT=courier new]pfctl[/FONT] gives me a syntax error with this notation. I have also tried the [FONT=courier new]/!64[/FONT] syntax which ip6tables uses for this, but this also does not work.

So the big question is: how can I apply a (hexadecimal?) mask to an IPv6 address, so [FONT=courier new]pf[/FONT] ignores the prefix and only matches the host part? Or is there another way of accomplishing this?

BTW: I am getting a new IPv6 prefix every 24 hours, so I cannot hard-code it into the firewall rules.


Thanks in advance,
suzaku

The network mask is incorrect, you can't do that

For example in IPv4, such masks are invalids
0:255:255:255
0.0.224.255

Or we can call that inverted masks that are not useful here

Masks always start from the left to the right, so :

255.0.0.0
255.224.0.0
255.255.255.0
255.255.255.255

This is the same thing in ipv6, masks are working in the same way

::1:1:1:1 = 0:0:0:0:1:1:1:1
::ffff:ffff:ffff:fff = 0:0:0:0:ffff:ffff:ffff:fff => forbidden mask



To understand why, refer to the CIDR notation

255.255.0.0 => CIDR mask 16

255.255.0.0 => Binary 11111111 11111111 0000000 0000000

CIDR mask 16 means : counting 16 bits flagged to 1
But you will note that CIDR notation can't take into account the position of the bit

so the following mask : 11101111 11111111 10000000 00000000 = 239 255 128.0

Would have exactly the same CIDR =>16, creating a lot of conflicts

So a network mask means : "block of contiguous bits flagged to '1', ranked from the left to the right"
So CIDR mask 16 will resolve only in : 11111111 11111111 00000000 00000000 = 255.255.0.0, and so 239.255.128.0 is a forbidden mask

This is the same thing in ipv6, the difference is : "ffffffff" => in binary "1111111111111111" (so for 8 blocks, max CIDR value is 128)

Some firewalls don't report network mask error. So depending on the firewall, a forbidden mask will be either interpreted as 0.0.0.0 / :: (authorize all) or 255.255.255.255 / IPv6 mask CIDR 128 (one address)

In packet filter dig a little, you should use dynamic tables

Code:
table <example1> const {192.168.1.0/24}  # static table, can't be modified. But after compilation, if this table is not used, the table will be destroyed
table <example2> const {192.168.1.0/24} persist # same thing except that table stays in memory even if it is not in use
table <example3> {} persist # THIS IS the dynamic table you should use

pass in to <example3>

PF initializes with an empty table example3 but keeps it in memory

Further you can add, remove addresses with external command pfctl

pfctl add -t example -T command ipv6prefix:1:1:1:1

Command can be : add, delete, flush ... see man pfctl

You should write a shell script that will be start at boot, or that will be launched later via a cron task.
This script should grab the IPv6 address assigned to the computer, and then you can use pfctl to add this address
If you are not aware of Bourn Shell scripting, this is probably the time to start learning.

if your interface is em0

Code:
A=${ifconfig em0 | grep "inet6 2" | tail -n 1 | cut -w -f3}
pfctl -t example3 -T add -$A

Should grab the IPv6 global permanent address and assign it to variable A
Address is added to the table. Just note that ipv6 global routable addresses always start with "2"

The more easy way to learn is to start by analysing and modifying existing scripts.
And little by little, you will understand

Also, if your station is a simple workstation, the common user should not complicate things using it's own fixed IP.
Use the default stateless addresses (or ipv6 autoconfig).

In stateless mode your computer will get :

Address a : A permanent IPv6 link local address fe80:w:j:y:z, that will be the same after reboot. The last "w:j:y:z" is calculated according to the MAC address, so this block is permanent (in a given operating system, Windows works in a same way but generate a different address compared to FreeBSD)

Address b : permanent IPv6 global address : ipv6prefix:w:j:y:z, the last block w:j:y:z will be exactly the same as the block w:j:y:z of the Link Local Address, in your case only prefix changes. So you shouldn't worry using your own fixed IP

Address c : An auto-generated temporary address : ipv6prefix:r:r:r:r where r:r:r:r are defined on a random basis, and will change regularly

Address a : is only local, nobody can get access from global internet
Address b : is not used by default. The user can bind this address to a local server and use it to authorize incoming trafic

Address c : is used as the default outbound trafic address and subsequent incoming trafic linked to firewall stateful packet inspection. So this is the only address to be exposed publicly to the internet. For that reason, in the common stateless configuration, this address will regularly change to reduce the risk of attack. As address changes in a block of 64 bit, running an intrusive scan is difficult.
 
Last edited:
Hi,

first of all, thank you very much for your detailed explanation.

The network mask is simply incorrect, you can't do that

Ahh, okay. I didn't realize that pf only supports actual network masks. I thought this is a simple mask used only for matching, at least that's the behavior I am used to from my current firewall.

Some firewalls doesn't report network mask error. So depending on the firewall, a forbidden mask will be either interpreted as 0.0.0.0 (authorize all) or 255.255.255.255 (allow one address only or CIDR 32)

I think we are talking about different things here. I know that ::ffff:ffff:ffff:ffff is not a valid network mask, but I assumed that when matching packets the firewall only applies a bit mask to the IP address and matches based on that mask. Apparently that's not how pf works right now, as it actually needs a network mask.

In packet filter dig a little, you should use dynamic tables
[...]
PF initializes with an empty table example3 but keeps it in memory

Further you can add, remove addresses with external command pfctl

pfctl add -t example -T command ipv6prefix:1:1:1:1

Thanks, that looks like a workaround for my problem. I still find shell scripting a bit fragile for a firewall, especially since I regularily have to watch for chaning prefixes somehow. But it looks like it can get the job done.

Also, if your station is a simple workstation, the common user should not complicate things usings it's own fixed IP.

Use the default stateless addresses (or ipv6 autoconfig).

I use IPv6 autoconfig for all machines on my network except one server. I need the server (Raspberry Pi) to have a predictable public IPv6 address, so I am setting a static IPv6 token (host part) on it. As soon as a new prefix is announced on the network the server picks it up and automatically generates a new public IPv6 address using the new prefix and the static token. It then registers the new public IPv6 address using a dyn-dns service.

And that's where the firewall rules kick in ;) Right now I am using OpenWRT for this (with the IPv6 mask matching mentioned above) and it gets the job done, but I'd actually like to replace the old OpenWRT box with a more up to date FreeBSD firewall for various reasons.

One more idea: I think it should be possible to use DNS names in pf, did I get this right? Are these DNS names resolved to IP addresses only once or are they resolved regularily? In the latter case I could simply use my server's dyn-dns name in the firewall and the problem would be solved without any spooky shell magic.
 
One more idea: I think it should be possible to use DNS names in pf, did I get this right? Are these DNS names resolved to IP addresses only once or are they resolved regularily? In the latter case I could simply use my server's dyn-dns name in the firewall and the problem would be solved without any spooky shell magic.

Hi, actually pf has this feature, for example:
Code:
table <testtable> persist file "/etc/pf/testtable.txt"
and in testtable.txt i put facebook.com, reload the pf, and this is the result:
Code:
pfctl -t testtable -T show
   157.240.7.35
   2a03:2880:f10c:283:face:b00c:0:25de

Oh one more thing, not sure if this related but try add these on top of your Filtering section before any other IPv6 filtering rules. I had IPv6 filtering issues before (RTO, unstable connection, etc), this helped me get things done.
Code:
pass in  log quick inet6 proto ipv6-icmp from {any} to {any} icmp6-type {1,2,135,136} keep state"
pass out log quick inet6 proto ipv6-icmp from {fe80::/10} to {fe80::/10,ff02::/16} icmp6-type {129,133,134,135,136} keep state
pass in  log quick inet6 proto ipv6-icmp from {fe80::/10} to {fe80::/10,ff02::/16} icmp6-type {128,133,134,135,136} keep state
pass in  log quick inet6 proto ipv6-icmp from {ff02::/16} to {fe80::/10} icmp6-type {128,133,134,135,136} keep state=
 
Back
Top