logo

English

이곳의 프로그래밍관련 정보와 소스는 마음대로 활용하셔도 좋습니다. 다만 쓰시기 전에 통보 정도는 해주시는 것이 예의 일것 같습니다. 질문이나 오류 수정은 siseong@gmail.com 으로 주세요. 감사합니다.

[Linux, OSX] pfctl - Packet FIlter Control 사용법

by digipine posted Nov 02, 2017
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print

 

Activation

PF is enabled by default. If you wish to disable it on boot, use the rcctl(8) tool to do so:

# rcctl disable pf

and reboot your system to have it take effect.

You can also manually activate and deactivate PF by using the pfctl(8) program:

# pfctl -e
# pfctl -d

to enable and disable, respectively. Note that this just enables or disables PF, it doesn't actually load a ruleset. The ruleset must be loaded separately, either before or after PF is enabled.

Configuration

PF reads its configuration rules from /etc/pf.conf at boot time, as loaded by the rc scripts. Note that while /etc/pf.conf is the default and is loaded by the system rc scripts, it is just a text file loaded and interpreted by pfctl(8) and inserted into pf(4). For some applications, other rulesets may be loaded from other files after boot. As with any well designed Unix application, PF offers great flexibility.

The pf.conf file has multiple parts:

  • Macros: User-defined variables that can hold IP addresses, interface names, etc.
  • Tables: A structure used to hold lists of IP addresses.
  • Options: Various options to control how PF works.
  • Filter Rules: Allows the selective filtering or blocking of packets as they pass through any of the interfaces.
    Filter rules can be given parameters to specify network address translation (NAT) and packet redirection.

Blank lines are ignored, and lines beginning with # are treated as comments.

Control

After boot, PF operation can be managed using the pfctl(8) program. Some example commands are:

     # pfctl -f /etc/pf.conf     Load the pf.conf file
     # pfctl -nf /etc/pf.conf    Parse the file, but don't load it
     # pfctl -sr                 Show the current ruleset
     # pfctl -ss                 Show the current state table
     # pfctl -si                 Show filter stats and counters
     # pfctl -sa                 Show EVERYTHING it can show
 
There are a few things that you need to have ready prior to following this how-to. I will outline how to get these things ready so that we can get to the meat-and-potatoes of this article. The first thing that you need is OpenBSD version 3.0 or later. At a mininum, you should have OpenBSD already installed. I have geared this article for OpenBSD 3.3. While you can find many good how-to's on setting up the firewall, my main focus is how to build and configure your firewall config correctly from the ground-up.
 

First and formost, you should have your firewall plugged into a network (with at least one other computer on it as well) so that it can receive and send traffic. If you do not have this, it will be very difficult to test. Often times if a network is not available, but you have two computers, you can connect the two computers directly to each other (network card to network card) by using a special Cat-5 cable called a "cross-over cable". I will not go into how to make one here, but any decent computer store carries them. If you plan on setting up NAT (Network Address Translation- where multiple computers can share a single IP address), then you should have two NICs (network interface cards) installed.

Few things are more important to have secure than a firewall. While building/configuring/testing your firewall, it should (ideally) NOT be accessible via the internet, and should be on an internal network. Please refer to RFC1918 for a list of private network blocks, but most ineternal networks are either 192.168.0.0/16 or 10.0.0.0/8. You should be comfortable with setting this up, and if you find this difficult, I would recommend that you do NOT set up a firewall at this point.

Setting Things Up

 

To activate PF, and have it start automatically on boot-up, edit your /etc/rc.conf.local file so that you add the line pf=YES to it. Although, you can edit the /etc/rc.conf file, it contains the system defaults, and should not be touched. Instead you can make your "overridden" changes in /etc/rc.conf.local, and the system will read that last, and update any changes you had made in it. Here is the contents of my /etc/rc.conf.local file:

Listing 1. /etc/rc.conf.local

 


#!/bin/sh -
pf=YES                  # Packet filter / NAT
pf_rules=/etc/pf.conf           # Packet filter rules file
pflogd_flags=                   # add more flags, ie. "-s 256"

    	     

Next, create a "pass all" pf.conf so that on boot-up, OpenBSD will read it in. Here is a good template for you to use:

Listing 2. /etc/pf.conf


## Macros
SYN_ONLY="S/FSRA"


## TABLES


## GLOBAL OPTIONS


## TRAFFIC NORMALIZATION


## QUEUEING RULES


## TRANSLATION RULES (NAT)


## FILTER RULES
pass in log all keep state
pass out log all keep state

    	     

If you look at Listing 2, you will see some place holders beginning with a "##". This will be explained later, but it is good to put it in there now so you know the order in which PF will process the rules file. If you look at the last two lines that begin with "pass..." you can see that it is going to log traffic going in and out of the system. As your system makes or receives connections, it is going to get logged in your /var/log/pflog file. I will show you later how you can read this (it's a binary file, and can not be read in a text editor). Ideally, while you are setting up your firewall, the machine should send and receive little to no traffic unless it is generated by you- it will help you see what is getting logged if you are the one creating traffic. At this point, go ahead and reboot your system for all the changes you have made to take affect, and to make sure everything starts up automatically.

Once you have rebooted, it's time to check to see if everything you made changes to came up successfully. You should try the below section, and make sure that the pflog0 interface is up, and that your rules got loaded.

Listing 3. Checking the pflog0 interface and pfctl -s rules output


cerberus:~# /sbin/ifconfig pflog0
pflog0: flags=141<UP,RUNNING,PROMISC> mtu 33224
cerberus:~#
cerberus:~# pfctl -s rules
pass in log all keep state
pass out log  all keep state
cerberus:~#

    	     

Here in Listing 3, you can see that pflog0 is "UP". This will allow you to attach TCPdump (explained later) to the interface to allow you to watch the traffic in real-time. Like I said above, if your computer is being heavily used, the information will fly by way too fast for you to read. Also, by running pfctl -s rules, you can see that your basic ruleset was loaded (not much of a firewall at this point since it's passing ALL traffic. hehe).

Taking it for a Spin

 

Now, it's time for you to watch some traffic. Although it will seem a bit cryptic, I will show you what to look for. Once you know how to read this, you will be able to better tailor your fireall to handle your network traffic. Listed below is the output of a few lines from TCPdump. Just type TCPdump -n -e -ttt -i pflog0 at the command-prompt to see what your firewall is doing.

Listing 4. Reading the output of TCPdump -n -e -ttt -i pflog0

 


cerberus:~# TCPdump -n -e -ttt -i pflog0
TCPdump: WARNING: pflog0: no IPv4 address assigned
TCPdump: listening on pflog0
Sep 17 17:07:07.833264 rule 37/0(match): pass in on fxp0: 11.22.33.44 > 192.168.1.2:
icmp: echo request
Sep 17 17:07:07.833486 rule 56/0(match): pass in on fxp1: 192.168.1.2 > 11.22.33.44:
icmp: echo reply
Sep 17 17:07:32.725126 rule 6/0(match): block in on fxp0: 55.66.77.88.14350 > 66.92.15.252.7777: S
3925150538:3925150538(0) win 5840 <mss 1460,sackOK,timestamp 311538809 0,nop,wscale 0> (DF)
Sep 17 17:07:37.443421 rule 14/0(match): pass in on fxp0: 55.66.77.88.14373 > 66.92.15.252.22: S
3920978973:3920978973(0) win 5840 <mss 1460,sackOK,timestamp 311539281 0,nop,wscale 0> (DF)
Sep 17 17:07:38.115817 rule 37/0(match): pass in on fxp0: 55.66.77.88 > 192.168.1.2: icmp: echo request
Sep 17 17:07:38.116021 rule 56/0(match): pass in on fxp1: 192.168.1.2 > 55.66.77.88: icmp: echo reply
^C
6 packets received by filter
0 packets dropped by kernel

    	     

Ok, the IPs 11.22.33.44, and 55.66.77.88 are fictitious, and have been changed so that the real IPs won't be shown here. The IP 192.168.1.2 is an internal system that is on a private network that is being NATted by the firewall. I will explain why the internal private network is being shown in the logs instead of the external (Internet) IP address later (see the NATting section at the end of this document if you can't wait).

I am going to dissect the below line to show you what each part means:

Sep 17 17:07:37.443421 rule 14/0(match): pass in on fxp0: 55.66.77.88.14373 > 66.92.15.252.22: S 3920978973:3920978973(0) win 5840 (DF)

 

Sep 17 17:07:37.443421 The current date/time including milliseconds.
rule 14/0(match): The ruleset number that caught the packet. You can view what number PF assigned to each rule by typing: pfctl -g -s rules| grep '^@'
pass in on fxp0: This says that the packet passed, coming in on fxp0 (fxp is a Intel Pro100 driver). You may have a different NIC, so fxp will not be there.
55.66.77.88.14373 > 66.92.15.252.22: S This indicates the from-ip:port > to-ip:port that the packet came from, and was destined to go to. The S at the end indicates the flag that the packet was set to (in this case it was a SYN flag).
3920978973:3920978973(0) win 5840 (DF) Ignore this stuff. It is beyond the scope of this document (mainly for more advanced uses).

 

This should give you somewhat of a base so that when you start building your ruleset, and by monitoring TCPdump, you will be able to make additions and/or corrections to your specific environment.

Now, you have a basic working knowlege of TCPdump and how to read it's output, and have the framework for a basic pf.conf file. Now it's time to give you a primer into the essential pfctl switches. I am not going to go into all the switches, just the ones you would use 90% of the time.

pfctl -d Diable the packet filter
pfctl -e Enable the packet filter
pfctl -Fa -f /etc/pf.conf Flush all (nat, filter, queue, state, info, table) rules and reload from the file /etc/pf.conf
pfctl -s rules Report on the currently loaded filter ruleset.
pfctl -s nat Report on the currently loaded nat ruleset.
pfctl -s state Report on the currently running state table (very useful).
pfctl -v -n -f /etc/pf.conf This does not actually load any rules, but allows you to check for errors in the file before you do load the ruleset. This is obviously good for testing.

Building Something Useful

Now it's time to roll up our sleeves, and delve into creating a useful PF config file. I would recommend that you leave your current /etc/pf.conf file alone (just in case you panic, and want to start fresh), and use a new one (we'll use /etc/pf.conf-new). The key is to start with a simple config, and build from there- if you get too zealous, and build a 60 line pf.conf file, and it doesn't do what it is supposed to do, you will have a hard time finding where the problem is. So let's start off simple, and build from there once we have a solid foundation.

There are essentially two camps for rules: allow all traffic by default, but explicitly deny what you don't want, and the other is to deny all traffic by default, and explicitly allow what you want. There is a big difference between the two, and can you guess which one I am going to show you? The latter one because it's the most secure.

First off, you should make a list of ports that you want to allow to the machine. At a mininum, allowing ssh (port 22/TCP), auth (port 113/TCP), and ICMP pings are good to start with. If you are running additional services (be VERY selective about what you run on a firewall- ideally, it shouldn't run any), you will obviously want to add those as well. We are going to replace the last two lines from Listing 2 with the following lines. I will explain what each line is later on.

Listing 5. /etc/pf.conf-new


## Macros
SYN_ONLY="S/FSRA"
EXT_NIC="fxp0"
INT_NIC="fxp1"

# Your Internet IP goes in the EXT_IP variable
EXT_IP="11.22.33.44"

# Your private network IP goes in the INT_IP variable
# if you have two NICs on the machine
INT_IP="192.168.1.1"

## TABLES


## GLOBAL OPTIONS


## TRAFFIC NORMALIZATION


## QUEUEING RULES


## TRANSLATION RULES (NAT)


## FILTER RULES

# Block everything (inbound AND outbound on ALL interfaces) by default (catch-all)
block all

# Default TCP policy
block return-rst in log on $EXT_NIC proto TCP all
   pass in log quick on $EXT_NIC proto TCP from any to $EXT_IP port 22 flags $SYN_ONLY keep state
   pass in log quick on $EXT_NIC proto TCP from any to $EXT_IP port 113 flags $SYN_ONLY keep state

# Default UDP policy
block in log on $EXT_NIC proto udp all
   # It's rare to be hosting a service that requires UDP (unless you are hosting
   # a dns server for example), so there typically won't be any entries here.

# Default ICMP policy
block in log on $EXT_NIC proto icmp all
   pass in log quick on $EXT_NIC proto icmp from any to $EXT_IP echoreq keep state

block out log on $EXT_NIC all
   pass out log quick on $EXT_NIC from $EXT_IP to any keep state

# Allow the local interface to talk unrestricted
pass in quick on lo0 all
pass out quick on lo0 all

    	     

We aren't using any of the cool features that PF has (aside from some MACRO definitions), but this is meant to be simple initially, and we can build on it later. One thing that tends to confuse alot of people is the concept of "IN" and "OUT" in a filter rule. The rules are always geared to itself (being the firewall), so when you see an "OUT" in a rule, that means it is leaving the firewall (regardless on what interface), and if you see an "IN" in a rule, then that means it is entering the firewall (regardless on what interface).

One thing that I am a stickler for is keeping the ruleset easy to read. There is some redundancy but it helps keep things grouped together so it's easy to make mental map of what you have- especially when you get to a BIG ruleset. Ok, now for the dissection (I am going to skip over the macro section as it should be pretty self-explanatory).

# Block everything (inbound AND outbound on ALL interfaces) by default (catch-all)
block all

The line above is about as wide open as you can get- it blocks everything on all interfaces and all protocols. This ensures that only what you explicitly allow will be allowed through

block return-rst in log on $EXT_NIC proto TCP all
   pass in log quick on $EXT_NIC proto TCP from any to $EXT_IP port 22 flags $SYN_ONLY keep state
   pass in log quick on $EXT_NIC proto TCP from any to $EXT_IP port 113 flags $SYN_ONLY keep state
     

These three lines are the grouping for all TCP-related traffic. The big differerence between the three lines is the word "quick". Without a "quick" option, the rule that matches a packet gets "tagged" with what the rule says to do, but the rule evalutation continues. If no other rules match, than the "tag" sticks, if a following rule matches, then the tag is replaced with what the new rule says. This is a nice way to set default behaviour for whatever you want, and then specify what you specifically want to do in the following rules. Here in this case, the last two lines have the "quick" option, so that if a packet matches either port 22/TCP or 113/TCP (as well as other constraints specified within the rule), then they are allowed through the firewall (hence the word "pass" in the beginning of the rule, and no other processing will be done on the packet. This is a critical piece to understand, so make sure it makes sense to you.

block in log on $EXT_NIC proto udp all
   # It's rare to be hosting a service that requires UDP (unless you are hosting
   # a dns server for example), so there typically won't be any entries here.
     

These three lines are much the same as the TCP section above, but this time it is a section focusing on UDP. Like it says in the ruleset, few machines need to listen on incoming UDP unless you are running a DNS server for example. For the most part, you can leave this section like it is. Here you can see that there is no "quick" option for this section- since no other rules match, UDP will be silently dropped. On a side note if a port is closed, the right thing to do is for the server to send back a return-reset if it is a TCP port, but since UDP and ICMP are stateless, you don't have to return anything, so dropping the packet without sending anything back is perfectly legal. I think that hiding the fact that you have a firewall is just as important as having one. No one needs to know you have one because it gives potential attackers more information about what you have. If you have a server hanging on the net, and it receives a UDP packet destined for a port it doesn't listen on, it will drop the packet with no reply, but if it receives a TCP packet, it sends back a tcp-reset packet letting the sender know it's not listening. So, the point is, if your firewall sends back a packet for a closed UDP port, then the attacker can say with a high probability that a firewall sent that, and not a server.

block in log on $EXT_NIC proto icmp all
   pass in log quick on $EXT_NIC proto icmp from any to $EXT_IP echoreq keep state
     

Here is the ICMP section. You should be getting the hang of this stuff by now, so I will keep it short. Although I did say that ICMP is stateless, PF can do some nice tricks to allow for some intelligence when dealing with UDP and ICMP. For example, if you restrict outbound ICMP traffic, but just allow ICMP-pings, if you have a "keep state" flag on that rule, then PF is smart enough to allow ICMP-replies to come back without you needing to create a specific rule for it to come back in. Nice eh?

block out log on $EXT_NIC all
   pass out log quick on $EXT_NIC from $EXT_IP to any keep state
     

Remember that one of the first lines in your new /etc/pf.conf-new file reads block all. This means it will block all outbound traffic as well as inbound traffic (ie. you won't be able to send any traffic out of your firewall. The above two lines permit unrestricted traffic to leave your firewall. If you are super-paranoid, you can lock this down further, and only allow specific traffic to leave the firewall (like web access for example).

# Allow the local interface to talk unrestricted
pass in quick on lo0 all
pass out quick on lo0 all
     

Just like I mentioned in the above paragraph, the block all line blocks ALL traffic on all interfaces, and that includes your local interface as well. The above two lines allow unrestricted access on your local interface. I am sure you notices that there is no "keep state" option on these two lines- the reason for that is that it requires more cpu processing to manage states, so in a case where all traffic is allowed, it's really pointless to keep state.

Final Touches

 

At this point, using the pfctl commands I listed above, fire up your new ruleset, and try things out. Since I like to have a "log" option for all rules, when you run tcpdump on your pflog0 interface, you should see log entries of connections coming in and out whether it is passed or denied. If you see traffic that is getting blocked, but you want to allow it, you can add that rule to your /etc/pf.conf-new file, and reload, and you should see it passing.

To go a bit Deeper

 

 

At this point, you should have the basics down pretty well, but alot was left out that makes PF so great. In this section, I am going to expand on what we discussed earlier, and show you some of the more popular features. I think the best way to approach this is by going through each section of your new /etc/pf.conf-new file, and explain some of these new features.

Macros

 

 

The first section is your macro section. This is just a fancy way of creating names (variables), and assigning values to them. This gets really handy when you have to move to a new set of IP addresses, and if you used macros everywhere, you don't have to find them all in your pf.conf file- you just change the macro definition, reload the config, and your'e done. Keep in mind that you can use macros for substituting anything- such as interface names, ips, ports, parts of rules, etc.

Tables

 

 

The next section is your tables section. This is a very unique feature, and is kinda like macros, but can be changed without reloading your config, and if the table has alot of IPs (like 1000's), PF will able to do some serious optimization that it wouldn't be able to do with macros. Yes, you read that right, you can add/remove IPs from a table from the commandline, and your firewall will automagically know the new config. Very nice! I am not gong to re-create the pf.conf for you, but I will show you what I have set up for my tables on my firewalls.

 

## TABLES
table <block_hosts> persist
table <private> const { 10/8, 172.16/12, 192.168/16, 224/8 }
     

Essentially, you create a name for a table (like "block_hosts"), and you can optionally add a flag like const or persist. What const means is that once the table is loaded, you can not change the values of it from the commandline. For persist, it means that the table will stay in ram so you can add to it later even if there are no entries in it. Normally, when a table no longer has any data in it, it is unloaded from memory- persist prevents that from happening. Now, to actually use a table in your rules, you would do something like I have below (which I place at the top of my filter rule section).

# Global filter stuff
block in log quick on $EXT_NIC from <block_hosts> to any
block in log quick on $EXT_NIC from <private> to any
     

Global Options

 

This section is where you define generally how PF behaves. You will find a wide variety of options to set, and although I will not go through them all, I will list some of the more interesting ones.

 

## GLOBAL OPTIONS
set loginterface $EXT_NIC
set block-policy return
     

The first option (set loginterface $EXT_NIC) is a must for just about every situation. It allows you to gather some really interesting statistics that normally aren't captured. Here's an example output of one of my firewalls. To get this information, use pfctl -s info. I don't think I need to explain the below output.

cerberus:~/# pfctl -s info
Status: Enabled for 69 days 07:04:35            Debug: None

Interface Stats for fxp0              IPv4             IPv6
  Bytes In                       385597759                0
  Bytes Out                      179194907                0
  Packets In
    Passed                          801631                0
    Blocked                         190642                0
  Packets Out
    Passed                          713633                0
    Blocked                              4                0

State Table                          Total             Rate
  current entries                       16
  searches                        15272773            2.6/s
  inserts                           218763            0.0/s
  removals                          218747            0.0/s
Counters
  match                           13746071            2.3/s
  bad-offset                             0            0.0/s
  fragment                               0            0.0/s
  short                                  0            0.0/s
  normalize                              0            0.0/s
  memory                             37403            0.0/s
     

The next line (set block-policy return) can be seen as a "catch-all" option. Although in your filter rules section, you specify explicitly how to handle every packet, this should mainly be seen as a saftey-net in case something new comes along that you don't have a rule for.

Traffic Normalization

 

With PF, it doesn't just pass packets on through, you also have the ability for it to clean up "dirty" packets before it is passed on. To go into detail of what all this means is beyond the scope of this article, but it is generally a good idea to do this. Below is some of the more interesting normalization options that you can use.

 

## TRAFFIC NORMALIZATION
scrub in on $EXT_NIC all fragment reassemble
scrub out on $EXT_NIC all fragment reassemble random-id no-df

# For NFS
scrub in on $INT_NIC all no-df
scrub out on $INT_NIC all no-df
     

Notice that I scrub all traffic going in and out of my external interface. The option random-id is a obscure option that prevents monitoring systems from detecting how many systems your firewall is NATting for. With the random-id option, they won't be able to detect this. The no-df option is especially good if you are running nfs over that interface (like I am on my internal interface). Although, this isn't specific to the traffic normalization section (but kinda goes along with it), when you put a modulate state option at the end of your filter rules, it hides the shortcomings of different network stacks found on some OS's. The benefit is that if your firewall is port-forwarding traffic to different systems (like port 80 traffic going to Linux, and port 25 traffic going to Solaris), external monitoring systems will be able to determine that 1) You are running two different servers one for each port, and 2) What OS is running on those systems. Please note that if you use modulate state for TCP filter rules, you do not need the keep state option. Also, modulate state works with TCP only.

Queueing

 

Unfortunately, to properly discuss PF's queueing facility, It would probably be best to write up a separate paper discussing this topic. If there is enough interest, I may do that.

Translation (NAT)

 

 

Having a firewall that doesn't do NAT is like using a table with only three legs- the two just go hand in hand. Of course, you must have two interfaces in two different subnet masks for this to work. On a typical firewall, your external inteface is connected to your Internet, and your internal interface is connected to a hub or switch for your internal network (refer back to the private network IP addresses listed earlier). With translation (NAT), you can do some really cool stuff, such as: 1) creating a static map where a specific internal IP address is mapped to a specific external IP address (if you have multiple IPs bound to on your external NIC), 2) Forwarding traffic for a specific port coming in on your ext. NIC, and passing it on to a server on your internal network for it to be handled, and 3) Allowing your private network of computers to access the internet via a single IP address. I will go through how to do each one.

 

rdr on $EXT_NIC proto TCP from any to 33.11.33.55 port 25 -> 192.168.1.33 port 25
binat on $EXT_NIC from 192.168.1.55 to any -> 11.22.44.33
nat on $EXT_NIC from 192.168.0.0/16 to any -> 22.33.11.55
     

You should recognise the macros in the above three lines. The first line basically says that traffic coming in on the external interface going for IP address 33.11.33.55 port 25 should be passed on to 192.168.1.33 on port 25.

The second line says that the machine at 192.168.1.55 on the internal interface should "own" the IP address 11.22.44.33 on the external interface. What this means is that all outbound traffic leaving 192.168.1.55 will go out through the IP address 11.22.44.33, and vice-versa. It is important to note that filtering rules still are applied for all traffic.

The last line says that NATting will occur for all machines on the 192.168.0.0/16 network destined to any ip address to appear on the internet as though it is coming from 22.33.11.55.

NOTE: This is one part that will screw up alot of people. Please note that NAT translation happens before any filtering happens. What this means is that if you do any filtering for a NATted IP address, your filter rule has to specify the NATted IP address, not it's external IP address (which would be needed in your ruleset for the port redirection above to work). I will give you an example below.

pass in log quick on $EXT_NIC proto TCP from any to 192.168.1.33 port 25 keep state
     

Grouping

 

Although, this isn't really a section, it's a very nice feature that PF has. Simply put, it allows you to combine similar rules into one rule keeping your ruleset nice and tidy. It can be completely explained in the below examples:

 

     pass in log quick on $EXT_NIC proto tcp from any to 11.22.33.44 port 80 keep state
     pass in log quick on $EXT_NIC proto tcp from any to 11.22.33.44 port 443 keep state
     

Becomes...

     pass in log quick on $EXT_NIC proto tcp from any to 11.22.33.44 port { 80, 443 } keep state
     

and

     pass in log quick on $EXT_NIC proto tcp from any to 192.168.1.33 port 53 keep state
     pass in log quick on $EXT_NIC proto udp from any to 192.168.1.33 port 53 keep state
     

Becomes...

     pass in log quick on $EXT_NIC proto { tcp, udp } from any to 192.168.1.33 port 53 keep state
     

Nice eh? This can be a real space-saver, and will make your firewall config much easier to manage. I used the "{ and }" grouping symbols in my table definitions above, and you can use them everywhere in your pf.conf file.

Conclusion

 

I hope this helped you understand how to set up a PF firewall. While just using these guidelines will build a pretty solid firewall, there are many parts of PF that you should take a deeper look into that you may want to leverage. If you have any comments or suggestions for other articles, please send them to me at the email address listed above.

Resources

 

 

 

 
PFCTL(8)                  BSD System Manager's Manual                 PFCTL(8)

NAME
     pfctl -- control the packet filter (PF) and network address translation (NAT) device

SYNOPSIS
     pfctl [-AdeghmNnOqRrvz] [-a anchor] [-D macro= value] [-F modifier] [-f file] [-i interface]
           [-K host | network] [-k host | network] [-o level] [-p device] [-s modifier] [-t table -T command
           [address ...]] [-x level]

DESCRIPTION
     The pfctl utility communicates with the packet filter device.  It allows ruleset and parameter configu-ration configuration
     ration and retrieval of status information from the packet filter.

     Packet filtering restricts the types of packets that pass through network interfaces entering or leav-ing leaving
     ing the host based on filter rules as described in pf.conf(5).  The packet filter can also replace
     addresses and ports of packets.  Replacing source addresses and ports of outgoing packets is called NAT
     (Network Address Translation) and is used to connect an internal network (usually reserved address
     space) to an external one (the Internet) by making all connections to external hosts appear to come
     from the gateway.  Replacing destination addresses and ports of incoming packets is used to redirect
     connections to different hosts and/or ports.  A combination of both translations, bidirectional NAT, is
     also supported.  Translation rules are described in pf.conf(5).

     The packet filter does not itself forward packets between interfaces.  Forwarding can be enabled by
     setting the sysctl(8) variables net.inet.ip.forwarding and/or net.inet6.ip6.forwarding to 1.  Set them
     permanently in sysctl.conf(5).

     The pfctl utility provides several commands.  The options are as follows:

     -A      Load only the queue rules present in the rule file.  Other rules and options are ignored.

     -a anchor
             Apply flags -f, -F, and -s only to the rules in the specified anchor.  In addition to the main
             ruleset, pfctl can load and manipulate additional rulesets by name, called anchors.  The main
             ruleset is the default anchor.

             Anchors are referenced by name and may be nested, with the various components of the anchor
             path separated by `/' characters, similar to how file system hierarchies are laid out.  The
             last component of the anchor path is where ruleset operations are performed.

             Evaluation of anchor rules from the main ruleset is described in pf.conf(5).

             Private tables can also be put inside anchors, either by having table statements in the
             pf.conf(5) file that is loaded in the anchor, or by using regular table commands, as in:

                   # pfctl -a foo/bar -t mytable -T add 1.2.3.4 5.6.7.8

             When a rule referring to a table is loaded in an anchor, the rule will use the private table if
             one is defined, and then fall back to the table defined in the main ruleset, if there is one.
             This is similar to C rules for variable scope.  It is possible to create distinct tables with
             the same name in the global ruleset and in an anchor, but this is often bad design and a warn-ing warning
             ing will be issued in that case.

             By default, recursive inline printing of anchors applies only to unnamed anchors specified
             inline in the ruleset.  If the anchor name is terminated with a `*' character, the -s flag will
             recursively print all anchors in a brace delimited block.  For example the following will print
             the ``authpf'' ruleset recursively:

                   # pfctl -a 'authpf/*' -sr

             To print the main ruleset recursively, specify only `*' as the anchor name:

                   # pfctl -a '*' -sr

     -D macro=value
             Define macro to be set to value on the command line.  Overrides the definition of macro in the
             ruleset.

     -d      Disable the packet filter.

     -X token
             Release the pf enable reference represented by the token passed.

     -e      Enable the packet filter.

     -E      Enable the packet filter and increment the pf enable reference count.

     -F modifier
             Flush the filter parameters specified by modifier (may be abbreviated):

             -F nat        Flush the NAT rules.
             -F queue      Flush the queue rules.
             -F rules      Flush the filter rules.
             -F states     Flush the state table (NAT and filter).
             -F Sources    Flush the source tracking table.
             -F info       Flush the filter information (statistics that are not bound to rules).
             -F Tables     Flush the tables.
             -F osfp       Flush the passive operating system fingerprints.
             -F all        Flush all of the above.

     -f file
             Load the rules contained in file.  This file may contain macros, tables, options, and normal-ization, normalization,
             ization, queueing, translation, and filtering rules.  With the exception of macros and tables,
             the statements must appear in that order. Use of this option, could result in flushing of rules
             present in the main ruleset added by the system at startup. See /etc/pf.conf for further
             details.

     -g      Include output helpful for debugging.

     -h      Help.

     -i interface
             Restrict the operation to the given interface.

     -K host | network
             Kill all of the source tracking entries originating from the specified host or network.  A sec-ond second
             ond -K host or -K network option may be specified, which will kill all the source tracking
             entries from the first host/network to the second.

     -k host | network
             Kill all of the state entries originating from the specified host or network.  A second -k host
             or -k network option may be specified, which will kill all the state entries from the first
             host/network to the second.  For example, to kill all of the state entries originating from
             ``host'':

                   # pfctl -k host

             To kill all of the state entries from ``host1'' to ``host2'':

                   # pfctl -k host1 -k host2

             To kill all states originating from 192.168.1.0/24 to 172.16.0.0/16:

                   # pfctl -k 192.168.1.0/24 -k 172.16.0.0/16

             A network prefix length of 0 can be used as a wildcard.  To kill all states with the target
             ``host2'':

                   # pfctl -k 0.0.0.0/0 -k host2

     -m      Merge in explicitly given options without resetting those which are omitted.  Allows single
             options to be modified without disturbing the others:

                   # echo "set loginterface fxp0" | pfctl -mf --M -mf-M

     -M      Enable port to name translation while displaying rule.

     -N      Load only the NAT rules present in the rule file.  Other rules and options are ignored.

     -n      Do not actually load rules, just parse them.

     -O      Load only the options present in the rule file.  Other rules and options are ignored.

     -o level
             Control the ruleset optimizer, overriding any rule file settings.

             -o none       Disable the ruleset optimizer.
             -o basic      Enable basic ruleset optimizations.  This is the default behaviour.
             -o profile    Enable basic ruleset optimizations with profiling.
             For further information on the ruleset optimizer, see pf.conf(5).

     -p device
             Use the device file device instead of the default /dev/pf.

     -q      Only print errors and warnings.

     -R      Load only the filter rules present in the rule file.  Other rules and options are ignored.

     -r      Perform reverse DNS lookups on states when displaying them.

     -s modifier
             Show the filter parameters specified by modifier (may be abbreviated):

             -s nat         Show the currently loaded NAT rules.
             -s queue       Show the currently loaded queue rules.  When used together with -v, per-queue
                            statistics are also shown.  When used together with -v -v, pfctl will loop and
                            show updated queue statistics every five seconds, including measured bandwidth
                            and packets per second.
             -s rules       Show the currently loaded filter rules.  When used together with -v, the per-rule perrule
                            rule statistics (number of evaluations, packets and bytes) are also shown.  Note
                            that the ``skip step'' optimization done automatically by the kernel will skip
                            evaluation of rules where possible.  Packets passed statefully are counted in
                            the rule that created the state (even though the rule isn't evaluated more than
                            once for the entire connection).
             -s Anchors     Show the currently loaded anchors directly attached to the main ruleset.  If -a
                            anchor is specified as well, the anchors loaded directly below the given anchor
                            are shown instead.  If -v is specified, all anchors attached under the target
                            anchor will be displayed recursively.
             -s states      Show the contents of the state table.
             -s Sources     Show the contents of the source tracking table.
             -s info        Show filter information (statistics and counters).  When used together with -v,
                            source tracking statistics are also shown.
             -s References  Show pf-enable reference statistics (pid/name of enabler, token, timestamp).
             -s labels      Show per-rule statistics (label, evaluations, packets total, bytes total, pack-ets packets
                            ets in, bytes in, packets out, bytes out) of filter rules with labels, useful
                            for accounting.
             -s timeouts    Show the current global timeouts.
             -s memory      Show the current pool memory hard limits.
             -s Tables      Show the list of tables.
             -s osfp        Show the list of operating system fingerprints.
             -s Interfaces  Show the list of interfaces and interface drivers available to PF.  When used
                            together with -v, it additionally lists which interfaces have skip rules acti-vated. activated.
                            vated.  When used together with -vv, interface statistics are also shown.  -i
                            can be used to select an interface or a group of interfaces.
             -s all         Show all of the above, except for the lists of interfaces and operating system
                            fingerprints.

     -T command [address ...]
             Specify the command (may be abbreviated) to apply to the table.  Commands include:

             -T kill       Kill a table.
             -T flush      Flush all addresses of a table.
             -T add        Add one or more addresses in a table.  Automatically create a nonexisting table.
             -T delete     Delete one or more addresses from a table.
             -T expire number
                           Delete addresses which had their statistics cleared more than number seconds ago.
                           For entries which have never had their statistics cleared, number refers to the
                           time they were added to the table.
             -T replace    Replace the addresses of the table.  Automatically create a nonexisting table.
             -T show       Show the content (addresses) of a table.
             -T test       Test if the given addresses match a table.
             -T zero       Clear all the statistics of a table.
             -T load       Load only the table definitions from pf.conf(5).  This is used in conjunction
                           with the -f flag, as in:

                                 # pfctl -Tl -f pf.conf

             For the add, delete, replace, and test commands, the list of addresses can be specified either
             directly on the command line and/or in an unformatted text file, using the -f flag.  Comments
             starting with a `#' are allowed in the text file.  With these commands, the -v flag can also be
             used once or twice, in which case pfctl will print the detailed result of the operation for
             each individual address, prefixed by one of the following letters:

             A    The address/network has been added.
             C    The address/network has been changed (negated).
             D    The address/network has been deleted.
             M    The address matches (test operation only).
             X    The address/network is duplicated and therefore ignored.
             Y    The address/network cannot be added/deleted due to conflicting `!' attributes.
             Z    The address/network has been cleared (statistics).

             Each table maintains a set of counters that can be retrieved using the -v flag of pfctl.  For
             example, the following commands define a wide open firewall which will keep track of packets
             going to or coming from the OpenBSD FTP server.  The following commands configure the firewall
             and send 10 pings to the FTP server:

                   # printf "table <test> { ftp.openbsd.org }\n \
                       pass out to <test>\n" | pfctl -f-# -f#
                   # ping -qc10 ftp.openbsd.org

             We can now use the table show command to output, for each address and packet direction, the
             number of packets and bytes that are being passed or blocked by rules referencing the table.
             The time at which the current accounting started is also shown with the ``Cleared'' line.

                   # pfctl -t test -vTshow
                      129.128.5.191
                       Cleared:     Thu Feb 13 18:55:18 2003
                       In/Block:    [ Packets: 0        Bytes: 0        ]
                       In/Pass:     [ Packets: 10       Bytes: 840      ]
                       Out/Block:   [ Packets: 0        Bytes: 0        ]
                       Out/Pass:    [ Packets: 10       Bytes: 840      ]

             Similarly, it is possible to view global information about the tables by using the -v modifier
             twice and the -s Tables command.  This will display the number of addresses on each table, the
             number of rules which reference the table, and the global packet statistics for the whole ta-ble: table:
             ble:

                   # pfctl -vvsTables
                   --a-r-  test
                       Addresses:   1
                       Cleared:     Thu Feb 13 18:55:18 2003
                       References:  [ Anchors: 0        Rules: 1        ]
                       Evaluations: [ NoMatch: 3496     Match: 1        ]
                       In/Block:    [ Packets: 0        Bytes: 0        ]
                       In/Pass:     [ Packets: 10       Bytes: 840      ]
                       In/XPass:    [ Packets: 0        Bytes: 0        ]
                       Out/Block:   [ Packets: 0        Bytes: 0        ]
                       Out/Pass:    [ Packets: 10       Bytes: 840      ]
                       Out/XPass:   [ Packets: 0        Bytes: 0        ]

             As we can see here, only one packet - the initial ping request - matched the table, but all
             packets passing as the result of the state are correctly accounted for.  Reloading the table(s)
             or ruleset will not affect packet accounting in any way.  The two ``XPass'' counters are incre-mented incremented
             mented instead of the ``Pass'' counters when a ``stateful'' packet is passed but doesn't match
             the table anymore.  This will happen in our example if someone flushes the table while the
             ping(8) command is running.

             When used with a single -v, pfctl will only display the first line containing the table flags
             and name.  The flags are defined as follows:

             c    For constant tables, which cannot be altered outside pf.conf(5).
             p    For persistent tables, which don't get automatically killed when no rules refer to them.
             a    For tables which are part of the active tableset.  Tables without this flag do not really
                  exist, cannot contain addresses, and are only listed if the -g flag is given.
             i    For tables which are part of the inactive tableset.  This flag can only be witnessed
                  briefly during the loading of pf.conf(5).
             r    For tables which are referenced (used) by rules.
             h    This flag is set when a table in the main ruleset is hidden by one or more tables of the
                  same name from anchors attached below it.

     -t table
             Specify the name of the table.

     -v      Produce more verbose output.  A second use of -v will produce even more verbose output includ-ing including
             ing ruleset warnings.  See the previous section for its effect on table commands.  A third use
             of -v will produce additional queue statistics related information.

     -w wait
             Show queue statistics at intervals of wait seconds.

     -x level
             Set the debug level (may be abbreviated) to one of the following:

             -x none       Don't generate debug messages.
             -x urgent     Generate debug messages only for serious errors.
             -x misc       Generate debug messages for various errors.
             -x loud       Generate debug messages for common conditions.

     -z      Clear per-rule statistics.

FILES
     /etc/pf.conf  Packet filter rules file.
     /etc/pf.os    Passive operating system fingerprint database.

SEE ALSO
     pf.conf(5), pf.os(5), sysctl.conf(5), ftp-proxy(8), sysctl(8)

HISTORY
     The pfctl program and the packet filter mechanism first appeared in OpenBSD 3.0.

BSD                            October 11, 2013                            BSD
TAG •

List of Articles
No. Subject Author Date Views
43 [Linux] ubuntu 16.04에 QT Creator 설치하기 digipine 2017.11.02 24321
42 Phabricator 설치 가이드 우분투 16.04 기준 digipine 2017.11.02 6187
41 Wi-Fi Display Standard Miracast Protocol Log digipine 2017.11.02 844
40 Git Http Backend Upload Size 설정 - Http 500 Error 해결 digipine 2017.11.02 2074
» [Linux, OSX] pfctl - Packet FIlter Control 사용법 digipine 2017.11.02 3166
38 IPv6 프로그래밍 가이드 digipine 2017.11.02 1349
37 TCP/IP State Transition - TCP 스택 포팅 시 참조 file digipine 2017.11.02 194627
36 Git Commnd 사용법 정리 digipine 2017.11.02 262
35 Git Commit 취소 관련 명령어 정리 1 digipine 2017.11.02 1327
34 리눅스 커널의 Swap Memory에 대해서 digipine 2017.11.02 669
33 Linux Kernel 컴파일 및 Patch 방법 digipine 2017.11.02 1097
32 XOR Encryption : 단순하면서도 강력한 암호/복호화 기법 digipine 2017.11.02 1733
31 Phabricator 설치 가이드 우분투 12.04 기준 digipine 2017.11.02 1225
30 Ubuntu Git - Latest Version Install digipine 2017.11.02 375
29 [ubuntu, 우분투] sendmail 설치 digipine 2017.11.02 2311
28 JDK Install ubuntu digipine 2017.11.02 355
27 Git 서버 구축 - 우분투[Ubuntu] digipine 2017.11.02 305
26 임베디드SW 개발자센터 이용안내(성남시 분당구, 개발공간 무료제공) digipine 2017.11.02 584
25 ATmega8 MCU 간의 TWI 기능을 이용한 I2C 통신 digipine 2017.11.02 5975
24 Mac Address 를 String 으로 변환하는 간편한 방법 digipine 2017.11.02 428
Board Pagination Prev 1 2 3 4 5 6 Next
/ 6