PF rules logic

Hi everyone,

I've had no problems with my PF rules for a while, until recently I installed FreeRADIUS. The problems I'm having now is that I don't seem to grasp the logic of PF rules. What I need is let in packets on port 1812 (radius) and block all the rest.

Here is the ruleset I've created for the purpose, but somehow the needed packets get BLOCKED here. Since the interface in question is my outer world interface, I want to keep stuff blocked there -- except for radius. Or any other port I might need to keep open.

Code:
out_if = "re0"
lan_if = "re1"
all_if = "{ re0 re1 }"

set block-policy return
set skip on lo0
#set skip on tap0
#set skip on bridge0

nat on $out_if from 192.168.26.0/27 to any -> ($out_if)
anchor openvpn

block all

pass quick on $lan_if from any to any keep state
pass in log quick on $out_if proto udp from 192.168.24.1 to 192.168.24.5 port 1812
pass out quick all
Just purposefully simplified it to block all that is not explicitly allowed.
And the word "quick" would imply that incoming packets on port 1812 ARE allowed. Yet here's what I have in the tcpdump output:
Code:
00:00:11.460004 rule 1/0(match): block in on re0: 192.168.24.1.32772 > 192.168.24.5.1812: RADIUS, Access-Request (1), id: 0x00 length: 1472
 00:00:00.000010 rule 1/0(match): block in on re0: 192.168.24.1 > 192.168.24.5: ip-proto-17
 00:00:02.924586 rule 1/0(match): block in on re0: 52.41.222.26.443 > 192.168.24.5.60882: Flags [P.], seq 359337947:359337982, ack 1573383051, win 114, options [nop,nop,TS val 2468663184 ecr 2933380], length 35
 00:00:27.655471 rule 1/0(match): block in on re0: 192.168.24.1.32772 > 192.168.24.5.1812: RADIUS, Access-Request (1), id: 0x00 length: 1472
 00:00:00.000011 rule 1/0(match): block in on re0: 192.168.24.1 > 192.168.24.5: ip-proto-17
 00:00:31.037882 rule 1/0(match): block in on re0: 192.168.24.1.32772 > 192.168.24.5.1812: RADIUS, Access-Request (1), id: 0x00 length: 1472
 00:00:00.000011 rule 1/0(match): block in on re0: 192.168.24.1 > 192.168.24.5: ip-proto-17
The funny thing is I tried putting the "block all" rule #first or #last -- no matter the position, the incoming packets are never identified in the "pass in quick port 1812" rule, but invariably get blocked in "block all"...

That's really driving me mad, because on OpenBSD it's all logical and all WORKS the way it is written in the documentation.
 
Your internal RADIUS traffic seems to be going in/out your external interface, you have LAN traffic on the out_if. It looks like you connected things the wrong way around.
 
Your internal RADIUS traffic seems to be going in/out your external interface, you have LAN traffic on the out_if. It looks like you connected things the wrong way around.
I have to explain it briefly then. This PF host is behind the WiFi router (dd-wrt), which is connected to the ISP.

So the PF host is on LAN actually, but it has 2 NICS. One is facing LAN and I call it $out_if, the other one I sometimes connect to a laptop or to the router I want to test/fix/whatever. So I consider it "internal" because it doesn't even face LAN (it is 192.168.26.0/27). The NAT rule is for that network to have access to LAN 192.168.24.0 and to its default gateway 192.168.24.1.

So my LAN iface is where Radius server is listening (runs on this same machine) for the signal from my AP with the address 192.168.24.1, which uses WPA2 Enterprise (EAP-TLS) mode for WiFi. I could, of course, disable PF on this machine. But I prefer, for testing purposes, to keep it the way it could be on a server -- so that this configuration could be used on an actual server.

This is why I expect the traffic from 192.168.24.1 (AP) > 192.168.24.5 (PF+Radius host) port 1812 to be ACCEPTED and not rejected. At least, I believed the rules there were set precisely to accomplish that end... But for some reason they don't.

Therefore the configuration is correct, though the terms may sound confusing. Radius traffic is MEANT to come in/out of re0. It's not for the outer world, but for the LAN. Still, I want to be all set for the real situation with RADIUS serving hosts across Internet.
 
I have come accross a number of posts by one of the FreeRADIUS developers (or so I understood) where he even says it is better to switch firewall off on the Radius host. But I can't do that on a host facing the outer world, so presently I bind it to a VPN interface instead, so the host has to connect first to OpenVPN and only then to Radius... There are NO filtering rules there for tun0, still there appear to be some problems.

But that's a separate story. This post is about my LAN Radius server which I want to work from behind a firewall for testing purposes. So far it has shown that PF on FreeBSD is not working exactly the way I imagined... I'd like to understand it better then, because all the PF-related documentation is about the OpenBSD version of PF, which I have no problems with. It does what the documentation says.
 
So, the way I see it, my PF rules are SUPPOSED to work this way:
Code:
block all 
pass quick on $lan_if from any to any keep state
pass in log quick on $out_if proto udp from 192.168.24.1 to 192.168.24.5 port 1812 
pass out quick all
Top to bottom, THE LAST MATCH RULE WORKS. Correct me right away if this is NOT the case with the FreeBSD version of PF.

If it's correct, then this is the way I expect it to work:
1) block all -- will block everything unless allowed in the rules that FOLLOW. DON'T STOP HERE, but check what other rules below are all about. Should some of them MATCH it will be the one applied. No one match -- this one will appy, block all.

2) pass QUICK on $lan_if from any to any keep state -- if this rule matching, stop here and pass in.
3) pass in log quick on $out_if proto udp from 192.168.24.1 to 192.168.24.5 port 1812 -- if this rule matches (packets from 192.168.24.1 destined at 192.168.24.5 port 1812) stop here and ALLOW.
4) pass out quick all -- let all outbound traffic go out freely. If this one applies, don't go any further!

So I really see no reason why the first rule "block all" should catch all the traffic meant for the 3d rule specifically:
00:00:11.460004 rule 1/0(match): block in on re0: 192.168.24.1.32772 > 192.168.24.5.1812: RADIUS, Access-Request (1), id: 0x00 length: 1472
 
Top to bottom, THE LAST MATCH RULE WORKS. Correct me right away if this is NOT the case with the FreeBSD version of PF.
Not if you "short-circuit" the rules with the quick keyword. First match with a quick keyword will stop processing the rest of the rules.
 
Not if you "short-circuit" the rules with the quick keyword. First match with a quick keyword will stop processing the rest of the rules.
Well yes, and for that very purpose I have the quick keyword only in the pass rules where I also specified the port and source-destination IP. Still, the first one to match turns out to be block all? Don't get it :)
 
Maybe it would help if you posted the output of # pfctl -s rules so we could see how your rules are being handled.
 
Yeah, I'd like to see that output too. I'm suspecting the openvpn anchor may have some rules that are blocking the traffic.
 
Code:
pass in log quick on $out_if proto udp from 192.168.24.1 to 192.168.24.5 port 1812

Shouldn't there be an = symbol in there between port and 1812? An example from my ruleset:

Code:
block quick proto { tcp, udp } from any to any port = 0
 
Shouldn't there be a = symbol in there between port and 1812?
Both notations are accepted.

Code:
root@armitage:~ # cat pf.test
pass in proto tcp from any to any port 22
pass in proto tcp from any to any port = 22
root@armitage:~ # pfctl -nf pf.test
root@armitage:~ #
 
Both notations are accepted.

Code:
root@armitage:~ # cat pf.test
pass in proto tcp from any to any port 22
pass in proto tcp from any to any port = 22
root@armitage:~ # pfctl -nf pf.test
root@armitage:~ #
Ah good point, I'll check. From my experience, it may be accepting both but silently applying only the "=" one...
 
Yeah, it looks like the '=' is more or less implied if there's only one port. If you look at the actual rules with pfctl -s rules you'll see it does have the '=' in there. Probably similar to 'keep state' being implied too.
 
Here, it is the same as in the rules, so I didn't mention it in the OP:
Code:
anchor "openvpn" all
block return log all
pass quick on re1 all flags S/SA keep state
pass in log quick on re0 proto tcp from any to any port = 1812 flags S/SA keep state
pass in log quick on re0 proto udp from any to any port = radius keep state
pass out quick all flags S/SA keep state
Anchor openvpn there is to dynamically load some rules when tun0 goes up/down. All the rest seems to be in harmony with all the logic...

Except that I even changed my fixed source/destination to "from any to any" for the port 1812 -- to no avail.
 
Interesting! Checked again in the FreeBSD Handbook (the only hope) and added a scrub rule:
Code:
scrub in all no-df max-mss 1440 fragment reassemble
And now it all works! So should I assume that PF was invisibly doing some filtering on packet attributes for my good?

What inspired me to try this was some occasional message in -vv tcpdump output about some "bad values" in some of the blocked packets.
 
Is the RADIUS traffic passing through the OpenVPN connection? If the MTU isn't set correctly you may be getting fragmented packets which would be difficult to firewall. The scrub reassembles those packets before it processes them.
 
Is the RADIUS traffic passing through the OpenVPN connection? If the MTU isn't set correctly you may be getting fragmented packets which would be difficult to firewall. The scrub reassembles those packets before it processes them.
On my home LAN it doesn't. It's just a dd-wrt AP directed to FreeRAIDUS running on my working desktop -- all in the home LAN. Still, it wasn't enough to just allow traffic on port 1812, but also to add this scrub rule.

However, I have a more "real-world" installation where the communication is over OpenVPN. There were scrub rules on that remote machine (basic ones like scrub all or something ) and Radius communication didn't go well, so when I added these specific rules mentioned here it got fixed, too. I'm not clear about the "needed" size of MTU, though, so I used the one recommended in the manual...

I remember using that clamp-to-mss rule in iptables when dealing with ADSL modem connection years ago. There were problems loading many sites, so the routers you buy in a shop had that thing by default. Since then I never cared about such things and even don't have the slightest idea of what those values are supposed to be.
 
I think it's the basic scrub rule, it's the one I use on my FreeBSD laptops and have been for a long time. I've seen used in other rulesets before and recommended as a scrub rule.

I do see yours is what they recommend in the Handbook.
 
Yes, but I don't seem to find much explanation by googling. And whatever I do find is always about the OpenBSD PF version, which currently has different syntax and realization of some of the functions.
 
And in any case, never does it mention anywhere that if you don't enable scrub rules, your PF firewall may actually block the packets it's told directly to pass in by explicit filter rules. I'm under the impression that, if indeed this is its normal behaviour for "some" cases, this should be explicitly mentioned in all PF manuals. Right there where it explains what one needs to let certain packets in (pass in on $iface etc.).

Or otherwise I'm missing something important in the whole concept... Of one thing I'm sure: it was not Radius ignoring some packets, it was PF blocking what it was told to let in.
 
And in any case, never does it mention anywhere that if you don't enable scrub rules, your PF firewall may actually block the packets it's told directly to pass in by explicit filter rules. I'm under the impression that, if indeed this is its normal behaviour for "some" cases, this should be explicitly mentioned in all PF manuals.
Code:
TRAFFIC NORMALIZATION
     [b]Traffic normalization is used to sanitize packet content in such a way
     that there are no ambiguities in packet interpretation on the receiving
     side.[/b]  The normalizer does IP fragment reassembly to prevent attacks that
     confuse intrusion detection systems by sending overlapping IP fragments.
     Packet normalization is invoked with the scrub directive.

There's also a whole section about fragment handling:
Code:
FRAGMENT HANDLING
     The size of IP datagrams (packets) can be significantly larger than the
     maximum transmission unit (MTU) of the network.  In cases when it is nec-
     essary or more efficient to send such large packets, the large packet
     will be fragmented into many smaller packets that will each fit onto the
     wire.  Unfortunately for a firewalling device, only the first logical
     fragment will contain the necessary header information for the subproto-
     col that allows pf(4) to filter on things such as TCP ports or to perform
     NAT.
Code:
     For instance, the rule

           pass in proto tcp from any to any port 80

     never applies to a fragment, even if the fragment is part of a TCP packet
     with destination port 80, because without reassembly this information is
     not available for each fragment.  This also means that fragments cannot
     create new or match existing state table entries, which makes stateful
     filtering and address translation (NAT, redirection) for fragments impos-
     sible.

pf.conf(5).

Basically it comes down to always using scrub (fragment reassembly is the default option) to ensure your rules do what you expect them to do.
 
So to summarize, fragments are a class of TCP packet that PacketFilter cannot handle, but will pretend to handle, even after you've stripped your pf.conf down to two lines, reread all the documentation and thoroughly questioned your own sanity. For my response to the exact same issue, though i had not yet stated the issue, see Thread fundamentals-of-packet-filtering-with-pf.78739. FUN
 
I am not sure I am understanding you correctly.
If I understand correctly, you can configure pf to use automatic fragment scrubbing, and you usually will want this unless you have some particular reason not to do automatically (using default scrub rules), but following rules you define yourself (which you probably can if you are a network wizard).
 
Back
Top