Tag Archive: Stateful Firewall

After the basics in part I, on to IPv6 and NAT. The title is misleading here: iptables exists for IPv6 and iptables can do NAT, but iptables cannot do NAT for IPv6 connections.

As for IPv6, this part is very simple: just add a ‘6’ between ‘ip’ and ‘tables’…


… and it will work for IPv6. As you can see above, since IPv6 addresses are longer, rules tend to split over two rows in a smaller console window.

NAT is an entirely different matter as it involves the translation of an IP address in the IP header of a packet. It doesn’t have much use for a standalone system, but if the Linux is used for routing it’s often needed.

I mentioned before iptables uses chains, but it also uses tables. The table that has been discussed so far is the ‘filter’ table. The ‘nat’ table takes care of NAT. Since it’s an entirely different table, it has its own set of chains:

  • PREROUTING, which applies NAT before the packet is routed or checked by the ‘filter’ table. It is most useful for destination NAT.
  • INPUT is not present in all recent versions anymore and does not serve any real purpose anymore.
  • OUTPUT is for packets originating from the local machine. In general they don’t need NAT s this is rarely used.
  • POSTROUTING applies NAT after the routing of the packet, if it hasn’t been filtered by the ‘filter’ table. It is most useful for source NAT.

A look at the current rule set can be done with iptables -L -v -t nat and rules can be added the same way as in the filter table, except that the parameter -t nat is added every time. The only difference is in the action to take for a rule that matches, the -j parameter.

For the ‘filter’ tables, possible target are ACCEPT and DROP, but for the ‘nat’ table this is different:

  • DNAT specifies destination NAT. It must be followed by –to-destination and the destination. The destination can be an IP address, but if a port was specified in the rule it can also be a socket, e.g. This makes port translations possible. A typical use case is if you want to make a server inside your network with a private IP address reachable from the internet.
  • SNAT is source NAT, and typically used for static NAT translations for an inside host with a private IP address towards its public IP address. It must be followed by the –to parameter that defines an IP address. It can also define a pool of IP addresses (e.g. and a range of source ports.
  • MASQUERADE is a special case: it is a source NAT behind the outgoing interface’s IP address (hide-NAT). This is ideal for interfaces which use DHCP to receive a public IP address from a provider. No other parameters need to be specified, so it’s not required to change this rule every time the public IP address changes.

These targets by themselves do not block or allow a connection. It’s still required to define the connection in the main ‘filter’ table and allow it.

Examples for NAT:

  • Forward incoming SIP connections (control traffic and voice payload) towards an inside IP phone at Allow the control traffic only from one outside SIP server at The outside interface is eth1.
    iptables -t nat -A PREROUTING -i eth1 -p udp –dport 16384 -j DNAT –to-destination
    iptables -t nat -A PREROUTING -i eth1 -p udp –dport 5060 -j DNAT –to-destination
    iptables -A FORWARD -d -p udp –dport 16384 -j ACCEPT
    iptables -A FORWARD -s -d -p udp –dport 5060 -j ACCEPT
  • Use IP addres as outgoing IP address for SMTP traffic from server
    iptables -t nat -A POSTROUTING -s -p tcp –dport 25 -j SNAT –to
    iptables -A FORWARD -s -p tcp –dport 25 -j ACCEPT
  • Use the interface IP address for all other outgoing connections on interface eth1.
    iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE


Notice that for rules in the ‘filter’ table that correspond to a NAT rule in the PREROUTING table, the IP addresses are used that are seen after the NAT has taken place, and for the POSTROUTING it’s the original IP addresses that are used. This is because the following order, as mentioned earlier, is very important here.

This is the IPv6 and NAT part of iptables. Up next: optimization and hardening of the rule set.

Most modern Linux distributions come with a firewall package already active. Since it’s often set in an ‘allow-all’ mode, people are often unaware of it.


Meet iptables, a basic yet powerful stateful firewall. You can see a default ‘allow-all’ policy above. Note that there are three different chains: INPUT, FORWARD and OUTPUT. Traffic can only match one of these three chains.

  • INPUT is for all traffic that is destined for the local Linux. It’s typically used to filter local services, e.g. you can allow only certain subnets to connect to the Linux via SSH, or shield off a port used by a process that you don’t want to be visible from the internet. This is also for response traffic from connections initiated locally.
  • FORWARD is for all traffic traversing the device. This requires routing functionality to be activated. On Debian-based Linux versions you can do this by adding or modifying the line net.ipv4.ip_forward=1 in /etc/sysctl.conf and perhaps adding some static routes.
  • OUTPUT is all traffic that originates from the local Linux. This is both for outbound connections as for response traffic for local services.

Looking at the current rule set can be done with iptables -L -v, where the optional -v parameter displays extra detail. Adding a rule can be done with iptables -A followed by the chain and the parameters of the rule. The most common ones are:

  • -p defines the protocol: udp, tcp, icmp or an IP Protocol number. You can modify the /etc/protocols file to make Linux recognize an IP Protocol number by name.
  • -i is the incoming interface. This is not supported in the OUTPUT chain for obvious reasons.
  • -o is the outgoing interface. This is not supported in the INPUT chain.
  • -s is the source subnet or host.
  • -d is the destination subnet or host.
  • – -dport (without the space) is the destination port, only valid if the protocol is defined as TCP or UDP. This can be a single port or a range, separated by a double colon.
  • – -sport (without the space) is the source port, or range of ports.
  • -j is the action to take. For the purposes of this article, let’s assume only ACCEPT and DROP are possible. More options will be discussed in upcoming blog posts.

The parameters not defined in a rule are assumed to have the value ‘any’. Examples:

  • Add a rule to allow SSH to the local Linux from one single host
    iptables -A INPUT -p tcp -s –dport 22 -j ACCEPT
  • Allowing subnet to do Remote Desktop to server
    iptables -A FORWARD -p tcp -s -dport 3389 -j ACCEPT
  • Block any traffic through the device towards UDP ports 10000 to 11000, regardless of source and destination:
    iptables -A FORWARD -p udp –dport 10000:11000 -j DROP
  • Don’t allow any traffic from interface eth4 to interface eth6:
    iptables -A FORWARD -i eth4 -o eth6 -j DROP

There is one additional rule which you will likely need in the configuration, but which differs from the rest of the rules: the stateful traffic rule. Although iptables by default keeps a state tables, it does not use it for traffic matching unless you tell it to. The rules to do this:

iptables -A INPUT -m conntrack –ctstate ESTABLISHED -j ACCEPT
iptables -A FORWARD -m conntrack –ctstate ESTABLISHED -j ACCEPT
iptables -A OUTPUT -m conntrack –ctstate ESTABLISHED -j ACCEPT

This calls in the module for connection tracking (-m conntrack) and matches any connections which have been seen before (ESTABLISHED). It is best to add this rule first this to avoid any drop rules from dropping traffic from known connections.

To modify the default policy, use the -P switch. For example, to block all local incoming connections by default:

iptables -P INPUT DROP

WARNING: When configuring iptables for the first time, especially via SSH, you have to be careful not to lock yourself out of the system. On top of that, some processes use IP communication using the loopback IP address internally. If you tell iptables to block this, it may break some applications!

Review what you want to achieve. For example, say you want to change the default iptables policy to drop any incoming connections, except http traffic and ssh from your computer:

  1. First add the rules allowing internal and stateful traffic:
    iptables -A INPUT -m conntrack –ctstate ESTABLISHED -j ACCEPT
    iptables -A INPUT -i lo -j ACCEPT
  2. Then add the rules allowing the connections:
    iptables -A INPUT -p tcp –dport 80 -j ACCEPT
    iptables -A INPUT -p tcp -s –dport 22 -j ACCEPT
  3. Finally set the policy to deny by default:
    iptables -P INPUT DROP


The OUTPUT chain can stay with a default allow action. If you modify it as well, be sure to add the rules for internal and stateful traffic again.

You can check the state table live via cat /proc/net/ip_conntrack


Here you see an example of one SSH connection and a NTP connection in the state table.

These are the iptables basics. In upcoming blog posts, I’ll talk about NAT, IPv6, optimization, hardening iptables security and increasing the scalability for large rule sets.

ASA: nice-to-know features.

I’ve already made an introduction to the ASA, but when working with them on a regular basis, it’s nice to know some features that come with the product to explain how it reacts and help troubleshooting. So for the interested reader with little ASA experience, below a few features that have proven handy to me.

Full NAT & socket state
Most consumer-grade routers with NAT keep a NAT state table that keeps state only with the source socket . A socket is an IP address and port paired together. For example, the following setup:


When connecting to the web server, remote socket, a local socket, for example is created. The router will then do a NAT translation to its outside IP address (a NAT/PAT with overloading or hide NAT) to socket This means that if return traffic arrives for destination port 37004, it will be translated to port 37004. However, without stateful firewalling, any packet will be translated back in again on port 37004, regardless of source. This is how some software like torrent programs do NAT hole punching. Also, no matter how big the pool of private IP addresses, the public IP address translations have a maximum of about 64,000 ports available (okay, 65,535 technically but there are probably some reserved and a source port below 1,024 is generally not recommended).

The ASA handles this differently: in combination with the stateful firewall a full state is made for each connection, both source and destination socket. This means the above translation is still done but no return traffic from another source is allowed. On top of that, if another inside host makes a connection towards a different web server, the ASA can reuse that port 37004 for a translation. Return traffic from that different web server will be translated to the other inside host because the ASA keeps a full state. Result: no 64,000 ports per public IP address the device has, but 64,000 per remote public IP address! This allows for even more oversubscription of a single public IP address, assuming not everyone is going to browse the exact same website.


Sequence randomization
A bit further into layer 4: TCP uses sequence numbers to keep track of the right order in a packet flow. The initial sequence number is supposed to be random, but this is not often the case in practice. In fact, one quick Wireshark from a connection to Google gives me this:


The problem is that guessing sequence numbers allows an attacker to intercept a TCP connection or guess an operating system based on the sequence number pattern. That’s where the second nice-to-know ASA feature comes into play: sequence randomization. By adding a random number to each sequence number (the same random number for each packet per flow) it becomes impossible to guess the initial sequence number of the next connection, as well as difficult to do any OS fingerprinting based on it.

Inspect policy-maps
For someone not familiar with the ASA, this is often a point of trouble. By default the ASA has no awareness above layer 4. This means any information not in the UDP or TCP header isn’t checked. Examples are HTTP headers, the FTP port used for transfer (which is in the payload) and ICMP Sequence numbers.


ASA requires configuration of policy-maps for this. This is why by default ping requests through the ASA don’t work: it cannot create a state for it. And for HTTP inspect, it checks for proper HTTP headers as well as the presence of a user-agent header. This means non-HTTP traffic cannot be sent through port 80, and incoming telnets on port 80 towards web servers aren’t accepted either, preventing some scans.

Finally, one of the most useful functions. While many other platforms with a Unix-based OS allow some form of tcpdump, Cisco does not support it. However, you can do some form of capturing on an ASA, even with proper filtering.

First configure the ACL that will be used as a filter, otherwise you’ll capture all traffic for that interface.

ASA#configure terminal
ASA(config)#access-list ExampleCapture extended permit ip host any

Next, find the correct interface name: the ‘nameif’ because the usual interface name will not do.

ASA#show run int vlan16
interface Vlan16
nameif Internal
security-level 50
ip address

Now you can start and show the capture.

ASA#capture TestCap interface Internal access-list ExampleCapture
ASA#show capture TestCap
76 packets captured

1: 16:45:13.991556 802.1Q vlan#16 P0 > S 3599242286:3599242286(0) win 8192 <mss 1460,nop,wscale 8,nop,nop,sackOK>
2: 16:45:14.035474 802.1Q vlan#16 P0 > . ack 1303526390 win 17520
3: 16:45:14.037824 802.1Q vlan#16 P0 > P 3599242287:3599242338(51) ack 1303526390 win 17520
4: 16:45:14.067196 802.1Q vlan#16 P0 > . ack 1303526754 win 17156
5: 16:45:14.072887 802.1Q vlan#16 P0 > P 3599242338:3599242898(560) ack 1303526754 win 17156
6: …

Note that traffic is seen in only one direction here. To see return traffic, add the reverse flow to the capture ACL as well. Unfortunately, the capture must stay running while watching the output here. The capture can be stopped as following:

ASA#no capture TestCap

This will erase the capture also, so the show command will no longer work.

Additionally, you can do a real-time by adding the parameter ‘real-time’, but it’s a bit more tricky. This is not recommended for traffic-intensive flows, but ideal to see if a SYN is actually arriving or not.

ASA#capture TestCap interface External access-list ExampleCapture real-time
Warning: using this option with a slow console connection may
result in an excessive amount of non-displayed packets
due to performance limitations.

Use ctrl-c to terminate real-time capture

1: 16:45:51.755454 802.1Q vlan#16 P0 > . ack 2670019600 win 16220
2: 16:45:51.768698 802.1Q vlan#16 P0 > . ack 2670019768 win 17520
3: 16:45:51.768774 802.1Q vlan#16 P0 > . ack 2670019968 win 17320
4: 16:45:51.777501 802.1Q vlan#16 P0 > . ack 2670020104 win 17184
5: …

Just don’t forget to remove the ACL after you’re done.

A stateful firewall should be considered a mandatory part of any network design. End user systems and the internet simply cannot be trusted, and even on servers there’s always the unexpected open port possibility. Going completely Cisco here for a moment, it’s an ASA, or a Zone-Based Firewall (ZBFW) configuration on an edge router.

But, usually due to budget constraints, it’s not always feasible to go and put firewalls everywhere in the network. While the WAN or internet edge should really have it, internally it can be less of a need. This is usually where access control lists (ACLs) come into play.


The example LAN above is completely internal. The switches are layer 2 access switches, the router depicted in the middle is a layer 3 switch acting as a default gateway for the VLANs.

Now let’s assume there’s a web server on and a Voice server on You want the following rules applied:

  • From Clients access to the web server by http.
  • From the IP Phones access to the Voice server using SIP protocol.
  • Allow IP Phones to be pinged from the Voice server.
  • Deny everything else.

On an ASA or any other stateful firewall, this would be a rule specified on VLAN 10, a rule for Voice on VLAN 20 and a rule for ICMP echo on VLAN 800. The stateful part would take care of the rest and automatically allow return traffic for existing connections. For TCP, it does so by checking the three-way handshake (SYN, SYN/ACK, ACK) and checking the connection breakdown (FIN and RST flags). For UDP, most stateful firewalls use a pseudo-stateful behaviour: the first UDP packet in a permitted rule will be set in the state table, with a time-out. Return traffic is accepted as long as the time-out isn’t reached. For each outgoing UDP packet part of the same stream, the timer is reset to zero. This is usually sufficient for connectionless communication.

ACLs however simply perform filtering and do not keep track of sessions. To do a ‘deny everything else’, both incoming and outgoing connections will have to be filtered properly with an ACL. A start would be this:

ip access-list extended VLAN10-IN
permit ip any host
ip access-list extended VLAN20-IN
permit ip any host
ip access-list extended VLAN30-IN
permit ip host
permit ip host

But the above access-list is very general and leaves a lot of security issues. To make it mimic a stateful firewall, add more details:

ip access-list extended VLAN10-IN
permit tcp host eq 80
ip access-list extended VLAN20-IN
permit udp host eq 5060
permit icmp host echo-reply

ip access-list extended VLAN30-IN
permit tcp host eq 80 established
permit udp host eq 5060
permit icmp host echo

A breakdown of the above added parameters:

  • Specifying a subnet with wildcard rather than just ‘any’, even if there’s just one subnet behind that interface, prevents IP spoofing. If just the return traffic is filtered on the subnet, this still allows for initial SYN packets, so SYN flooding from a spoofed IP is possible when ‘any’ is used.
  • Always specify a destination port to prevent scanning of the servers and accidental (or malicious) connection to other services running on that server. Even if the server runs a software firewall: it shares the operating system with the services, who may open ports on this firewall.
  • ICMP: specific definition of echo and echo-reply makes sure the server can ping the IP Phones, but not the other way around.
  • In the return traffic, setting the source port to the service makes sure the server does not initiate connections, or in the case of UDP, starts a flow on a non-standard port. Since it’s not possible to use timers in the ACL based on outgoing connections, this way you can still do some filtering.
  • For TCP, the ‘established’ keyword only allows packets that have the ACK bit set. The initial SYN used for the connection buildup is not allowed – Effectively preventing the server from initiating a connection himself. Instead, he has to listen for connections from the clients, where the ACK bit isn’t checked.

While this does not create a state table on the switch or router, it really narrows down the attack surface. Connections cannot be initiated where you don’t want it, packets must go to and originate from ports you decide. The biggest security problem is that UDP packets or TCP packets with ACK already set can be used to flood the links, but even then only from certain ports. And, compared to a stateful firewall, this configuration is more complex, because with each rule change, you have to completely understand the flow, and also adapt the ACL for the return traffic.

It’s been a while since I’ve posted anything since I’ve been very busy with lots of personal things.  Until a few days ago, when I needed to set up an ASA urgently as a stateful firewall to protect a web server. The perfect basic setup, but still a challenge for someone inexperienced. So below some brief notes to get an ASA going with basic firewall functionality.

First step: basic configuration of the interfaces. The ASA5505 interfaces work somewhat like a layer 3 switch, with VLAN interfaces and switchports, that can be configured in access, trunk or routed interface mode. Other ASA models work more like a router, with layer 3 interfaces, and subinterfaces for trunk link behavior.
In this example, the first interface on my ASA5505 will become a routed interface for out-of-band access in a subnet, the second interface will also be routed and become the external connection with IP (ISP gateway, and the remaining interfaces will be switchports part of VLAN 5 with subnet as the internal network.

ASA5505#configure terminal
ASA5505(config)#interface Ethernet0/0
ASA5505(config-if)#no switchport
ASA5505(config-if)#ip address
ASA5505(config-if)#description Management
ASA5505(config-if)#nameif MGMT
ASA5505(config-if)#security-level 100
ASA5505(config)#interface Ethernet0/1
ASA5505(config-if)#no switchport
ASA5505(config-if)#ip address
ASA5505(config-if)#description ISP
ASA5505(config-if)#nameif External
ASA5505(config-if)#security-level 0
ASA5505(config)#interface Vlan 5
ASA5505(config-if)#ip address
ASA5505(config-if)#nameif Internal
ASA5505(config-if)#security-level 50
ASA5505(config)#interface range Ethernet0/2 – 7
ASA5505(config-if)#switchport mode access
ASA5505(config-if)#switchport access vlan 5

Some explanations here: the ‘nameif’ gives the interface an actual name that can be seen in ASDM and worked with for all firewall rules to come. The security-level is used by the ASA to determine which interface is considered a safe-zone, and which one is not. By default, traffic from safer interfaces (higher security-level) can flow to less secure interfaces, but not the other way around (except for the return packets of stateful connections). Equal security-level interfaces can’t pass traffic between each other by default, but this option can be toggled in the ASDM, or by the ‘same-security-traffic permit inter-interface’ command. Also, the ‘management-only’ command on the management interface prevents that interface from being part of the routing, so no packets from and to any other interface can be passed through this interface.

Next is setting up a local user account for SSH and ASDM access, as well as an enable password. Consider this mandatory for any ASA.

ASA5505(config)#user reggle privilege 15 password ********
ASA5505(config)#enable password ********
ASA5505(config)#aaa authentication ssh console LOCAL
ASA5505(config)#ssh MGMT
ASA5505(config)#http server enable
ASA5505(config)#http MGMT
ASA5505(config)#asdm image disk0:/asdm-645-206.bin

Again more explanation: the third line makes sure the local username database is used when trying to log in on SSH. RADIUS and TACACS+ are also possible of course, but I’m not going to cover that here. The fourth and sixth line define who may connect to SSH and ASDM, respectively. In this case I allow access from any IP (, but only on the Management interface, which is defined by the nameif configured earlier. You can add multiple lines with multiple subnets and interfaces, but this is a basic example.

The last line is also required or ASDM will not work. It defines where on the local system the ASDM installation file is located. The file has to be present! The version may differ of course and can be downloaded on the Cisco website. The positive side of this is that once all of this is configured, you can connect to the ASA with your browser and it will provide you with a download link for ASDM if you don’t have it installed already. This can save you a lot of searching when you urgently need to log in from a computer that doesn’t have this installed yet. Note that ASDM does require a modern 32-bit Java to function, even on 64-bit operating systems.
The last thing I do in the console after this is setting up basic static routing: a  default route towards the internet on the External interface. The Management interface is a bit trickier and I haven’t worked that one out yet: everything in the local subnet can connect, but I haven’t tested if setting a default route for the Management interface actually works, so I’m not going to define one here.

ASA5505(config)#route External

That’s the end of the console part. For firewalling and NAT, I prefer the ASDM now. Logging in is done by surfing to and giving the username and password configured earlier. It’s an easy to understand interface. First NAT, which you can find under ‘Configuration’ in the menu above, then ‘Firewall’ on the left, and finally ‘NAT rules’, also on the left. There you can add new rules on the interfaces.


In the NAT rule menu, you can add objects (which you can create there too) that must be translated. For example, a dynamic or hide NAT of the network behind the address of the External interface, Static NAT rules for servers are also possible and you can NAT those behind addresses not configured on any interface, for example if you’ve received a range to use from your provider or own an IP range. In the last case, make sure routing (likely BGP for your own IP range) is pointing towards the ASA for the IP’s you’re going to use.

When your changes are done, you can click apply on the bottom of the screen to push the configuration towards the ASA. The ‘Save’ button on top of the screen acts the same as ‘copy run start’.
Next, under ‘Access rules’ on the left you can define the actual firewall rules. Again the same simple interface to add objects in rules, source, destination, ports, time frames,… I’m not going to explain in detail, it’s easy to experiment with should you have the chance to do so. A few points to keep in mind though:

  • The rules are examined top-down. So if the first rule denies something, even a dozen allow rules after that will not change the decision.
  • Top-down means it takes CPU to match something on the bottom of a long rule list. Luckily, the ASA shows the number of hits on each rule in the ASDM, so you can place the most-used rules on top of the rule list to reduce CPU load.
  • When defining an object, use netmask to define a single IP.
  • If you’re using NAT on an external interface, don’t define any rule on that interface with the internal IP address of a NAT rule. It will of course not match any incoming packet.
  • I’m very fond of how easy the ASDM interface is, but keep in mind this is a GUI which pushes configuration towards a device. This push action can be interrupted, which may result in missing rules or configurations. So for larger configurations, it can’t hurt to push ‘Apply’ twice from time to time.

This configures a basic ASA firewall. Biggest challenge after the configuration: connecting the cables correctly!

I’ve already set up an IPv6 tunnel on three platforms: Vyatta, Cisco and Windows Server. This time, the same on OpenBSD. I’m not going to repeat myself, so for details about an IPv6 tunnel and how to get one, check the IPv6 tunnel article. I’ll be using  the same example values again:
Local IPv6 subnet:  2001:0:0:1234::/64
Tunnel subnet: 2001:0:0:1235::/64, with ::2 on our side and ::1 on the other endpoint side.
IPv6 DNS: 2000::2000
Device IPv4 address:
Tunnel endpoint:
Gateway to ISP:

I assume routing and IPv4 is configured properly already, with IP’s on interfaces and a default route towards the internet. If not, you’ve missed part I. Before starting the IPv6 part, remember that you’ll be creating a tunnel over an existing IPv4 network, so make sure pf allows the tunnel. I’ve added the following rules in /etc/pf.conf:

pass out quick on em0 from to
pass in quick on em0 from to

You’ll need to pass both ipv6ip and icmp, but since it’s just one trusted IP address, I’m doing a general rule. Don’t forget to activate the rule with ‘pfctl -f /etc/pf.conf’!

Next, creating the tunnel interface. In OpenBSD this is a ‘gif’, generic interface. To make it persist between reboots, create a /etc/hostname.gif0 file, zero for the first tunnel interface. The following lines go in the file:

!ifconfig gif0 inet6 alias 2001:0:0:1235::2 2001:0:0:1234::1 prefixlen 128
!route add -inet6 default 2001:0:0:1235::1

The internal IP is automatically translated by my router, but this may not always be the case. If not, use your external IP. The prefix length in the second line is 128, which is advised in the tunnelbroker configuration sample, but I’m not sure why. It wouldn’t work with 64 though. Finally, the third line adds a default route into the tunnel.

At this point the tunnel is up and running, but from the OpenBSD only. The devices on the connected subnet are not aware an IPv6 router is present. For this, the OpenBSD will have to send router advertisements. First, configure an IPv6 address on the interface, by adding the following line to /etc/hostname.em1:

!ifconfig em1 inet6 alias 2000:0:0:1234::1 prefixlen 64

Next, do the actual advertisements using the rtadvd deamon. In /etc/rc.conf, find the ‘rtadvd_flags:NO’ and change the ‘NO’ to the interface(s) that need it enabled, e.g. em1. Then create the file /etc/rtadvd.conf’ and enter the following:


This advertises the /64 prefix on the interface. A lot of other options are possible, such as the other-config-flag and managed-config-flag for DHCP options and a IPv6 DNS server, but I will not go into detail about that now. Finally, keep in mind icmp is used for router advertisements and neighbor discovery (the ARP replacement), so you’ll need to allow these. In /etc/pf.conf:

pass out quick on em1 inet6 proto icmp6
pass in quick on em1 inet6 proto icmp6

Finally, add some rules based on what you want to filter, e.g. a general rule blocking everything IPv6 inbound, and allowing outgoing connection of any kind (for now):

pass out quick on gif0 inet6 from 2000:0:0:1234::/64 to any
block in on gif0

After this, surfing to ipv6.google.com is possible from any computer in the local subnet.

In my previous post I described how to set up OpenBSD and enable simple routing between interfaces. This time an explanation about one of the core features: pf, or packet filter.

The pf service allows for stateful firewalling and NAT translations when routing. Starting where we took off in part I, we have an OpenBSD 5.0 with routing enabled and interfaces configured. pf is one of the few things to be active by default, so it’s already running now, but it doesn’t do anything as no rules have been defined yet. Defining rules has to be done in /etc/pf.conf . How? Again, by using a text editor like vi. Yes, that’s ‘vi /etc/pf.conf’ on the command line. There are already some basic rules defined in pf.conf, most of them commented out as examples.

A filtering rule consists of several keywords. A complete guide is available as a manual page, but I’ll list the basics here:

  • Most used are pass and block. The first permits a packet based on parameters defined further in the rule, the second drops the packet.
  • Next is in or out. Either filter incoming or outgoing on an interface. Incoming takes place before a route look-up so filtering mostly on incoming will take less CPU.
  • This one is optional: quick. Without it, pf will check all rules and treat a packet according to the last matching rule. However, if a rule contains the word quick and the packet matches, any following rules are no longer checked. Using this keyword makes the filtering behave more like a Cisco ACL (top-to-bottom), and will take less CPU as not all rules have to be examined every time.
  • on, followed by an interface, e.g. em0. It defines on which interface the filtering takes place.
  • proto, followed by a protocol, mostly tcp, udp or icmp. It’s an optional keyword: if it’s missing, filtering will be done with layer 3 information (IPv4, IPv6) only.
  • from defines where from. It can be any, an ip, or a subnet, e.g.
  • port is optional after this and defines the source port(s). It uses operators, e.g. port = 2000 is port 2000, port < 5000 is any port below 5000, and so on. Since it’s a source port and most applications use dynamic source ports, this isn’t used that much.
  • to defines the destination host or subnet. Same parameters as from.
  • A second port, again optional, after to. This is the destination port. Since these are usually well-known ports, it’s often used.

Many more options and keywords are possible, but knowing the above is sufficient for decent stateful firewalling.

The second part is address translation, or NAT. A basic setup is as following:

  • match is the first keyword. You can also use pass, but it will pass everything unless you define certain ports in the rule also.
  • Next are in or out, with out being the most used (and easiest to troubleshoot too, in my opinion).
  • Then: on for the interface, from and to. proto and port are optional.
  • Last: nat-to, followed by an (external) IP. It defines what the host or subnet defined in from will NAT to.

We can try this in an example. Let’s assume the OpenBSD has interface em0 connected to an ISP (IP and em1 to an internal network This shows what can be done with it:

match out quick on em0 from to any nat-to

pass out quick on em0 proto tcp from to any port = 80
pass out quick on em0 proto udp from to any port = 53
block out on em0

The above will NAT any internal address from em1 to the external IP. Everything will be blocked (the last rule), except TCP traffic to port 80 (which is http traffic) and UDP port 53 to IP (which are DNS requests to the IP of Google’s DNS).

The above works, but it’s a very incomplete example of course: no https (port 443) is possible for example, as are pings and the ability to choose another DNS server in case of a DNS problem. But it demonstrates the point. Don’t forget to do ‘:wq’ to save your changes!

Now just one more thing: these rules aren’t activated by default: you’ll need ‘pfctl -nf /etc/pf.conf’ and ‘pfctl -f /etc/pf.conf’. The first commands checks if the rules don’t contain any syntax errors, which you should always check, and the second command loads the rules in the current configuration.