Configuring native IPv6 in pfSense firewall

August 28th, 2009 Remco Bressers 13 comments

pfSense

Today, we’re going to talk about pfSense. A software stateful-firewall based on the excellent pf firewall in FreeBSD. It’s an easy to install from-ISO appliance.

From the pfSense website:

pfSense is a free, open source customized distribution of FreeBSD tailored for use as a firewall and router. In addition to being a powerful, flexible firewalling and routing platform, it includes a long list of related features and a package system allowing further expandability without adding bloat and potential security vulnerabilities to the base distribution. pfSense is a popular project with more than 1 million downloads since its inception, and proven in countless installations ranging from small home networks protecting a PC and an Xbox to large corporations, universities and other organizations protecting thousands of network devices.

pfSense is a nice piece of software, but the developers don’t seem to be very interested in integrating IPv6 support in the interface. Too bad, because IPv6 is hot and will replace IPv4 within the next few years. I’m not going to integrate IPv6 in the GUI of pfsense with this tutorial, but after following the instructions you will have a working IPv6 router/firewall with support for stateless autoconfiguration. The configuration is built from my own needs, so if it doesn’t match your expectations please add your features.

For this setup i use pfSense 1.2.3-RC1 which is out for quite a while and pretty stable in it’s use.

I’m not going to discuss the installation of pfSense. If you can’t install the pfSense ISO, you shouldn’t be doing IPv6 on it anyway :) . First of all, make sure you enable SSH in pfSense. You can find the feature at “System” > “Advanced”

Enable SSH on pfSense

After enabling, connect (via SSH) to the pfSense box. Ofcourse, if you’re sitting behind the box you can do it on the console also :) .

You will be presented a nice text menu:

*** Welcome to pfSense 1.2.3-RC1-pfSense on myFirewall ***

WAN*                     ->    bce0    ->    123.123.123.1
LAN*                     ->    bce1    ->    192.168.0.254

pfSense console setup
***********************
0)  Logout (SSH only)
1)  Assign Interfaces
2)  Set LAN IP address
3)  Reset webConfigurator password
4)  Reset to factory defaults
5)  Reboot system
6)  Halt system
7)  Ping host
8)  Shell
9)  PFtop
10)  Filter Logs
11)  Restart webConfigurator
12)  pfSense PHP shell
13)  Upgrade from console
14)  Disable Secure Shell (sshd)

We want to go to the CLI shell. Select 8.

On my box, i’m using Broadcom network interfaces. On FreeBSD these are named ‘bce0′ and ‘bce1′. You can find the respective names with the ‘ifconfig’ command. On my setup, bce0 is the outside interface and bce1 is the inside interface.
My setup is fully native-IPv6, which means that i’m not doing any tunnelling at all. On the outside interface, i have an IPv6 address from my provider’s /64 block he used for my connection. On the inside, i have  a /64 of IPv6 addresses which are publically reachable (global-unicast). Ofcourse i’m using fake addresses to prevent my firewall being bombed all-over :) .

Let’s say, these are my network variables :

  • The WAN IPv6 network is : 2001:4cb8:a95:1::/64
  • The WAN IPv6 address is : 2001:4cb8:a95:1::2
  • The WAN IPv6 default gateway is : 2001:4cb8:a95:1::1
  • The LAN IPv6 network is : 2001:4cb8:b95:1::/64
  • The LAN IPv6 address is : 2001:4cb8:b95:1::1

With this information, we’re going to create our boot-script to configure the interfaces and routing.

cd /usr/local/etc/rc.d
vi 00_config-ipv6-if.sh

#!/bin/sh
#
# IFOUT = outside interface
# IFIN = inside interface
# DFGW = default gateway
IFOUT="bce0"
IFIN="bce1"
DFGW="2001:4cb8:a95:1::1"

####### Configure the stuff

# Configure the interfaces
ifconfig $IFOUT inet6 alias 2001:4cb8:a95:1::2 prefixlen 64
ifconfig $IFIN inet6 alias 2001:4cb8:b95:1::1 prefixlen 64

# Set the default route
route -n add -inet6 default $DFGW

# Configure IPv6 forwarding
sysctl net.inet6.ip6.forwarding=1

# My /etc/rtadvd.conf looks like this
#
# bce1:\
#   :addrs#1:addr="2001:4cb8:b95:1::":prefixlen#64:tc=ether:
#
# Startup rtadvd
/usr/sbin/rtadvd -d -D -c /etc/rtadvd.conf $IFIN

Ok, that’s pretty much all there is to enable IPv6 and configure the static routing to the ISP.
Next, we need to change permissions on this file :

chmod 755 /usr/local/etc/rc.d/00_config-ipv6-if.sh

After bootup, IPv6 will be running on the pfSense box, but it won’t do a thing. This is because we need to change the filter (PF) also. This is going to be our next script.

cd /usr/local/etc/rc.d
vi 10_config-ipv6-pf.sh

#!/bin/sh
#
# IFOUT = outside interface
# IFIN = inside interface
# DFGW = default gateway
IFOUT="bce0"
IFIN="bce1"

####### Configure the stuff

# Configure PF
# pfSense puts it's rules in /tmp/rules.debug for debugging purposes after boot
# We will use these rules, add IPv6 additions, read the config with pfctl and
# disable and enable PF
cat /tmp/rules.debug | sed "/User-defined rules follow/{
p;s/.*/\
pass in quick on $IFIN inet6 from any to any\\
pass out quick on $IFIN inet6 from any to any\\
pass out quick on $IFOUT inet6 from any to any\\
pass quick proto ipv6-icmp from any to any\\
pass in on $IFOUT inet6 proto tcp from any to any port 22\\
/;}" > /tmp/rules.config-ipv6.txt

# Read the new PF configuration file
pfctl -f /tmp/rules.config-ipv6.txt
pfctl -d; pfctl -e

And change the permissions also:

chmod 755 /usr/local/etc/rc.d/10_config-ipv6-pf.sh

Finally, we need to configure the router advertisement daemon (rtadvd) to get stateful autoconfiguration to work.

vi /etc/rtadvd.conf

bce1:\
  :addrs#1:addr="2001:4cb8:b95:1::":prefixlen#64:tc=ether:

After rebooting the pfSense firewall (or run script 00 and 10) IPv6 will work on your box.
But.. when you change filter rules (or anything actually) in the GUI, the filter settings are overwritten and your IPv6 connectivity will break.
After some searching on the box, i noticed that after changing things in the GUI the function filter_configure_sync() is called and the rules will be flushed.
This function can be found in /etc/inc/filter.inc (line 78). In the function, there’s a hook to a plugin directory. When the function filter_configure_sync() is called, the function will look in the /usr/local/pkg/pf directory for scripts, which will be executed. This only happens if scripts end with “.sh” as the extension.
We will symlink the 10_config-ipv6-pf.sh script to this location to make it work.

ln -s /usr/local/etc/rc.d/10_config-ipv6-pf.sh /usr/local/pkg/pf/

Congratulation! You got yourself a working IPv6 setup.

If you want to know more ins and outs about IPv6, i suggest reading the book “Running IPv6″ by Iljitsch van Beijnum. You can find more information at http://runningipv6.net/

Configuring NAT on Juniper J-series

July 17th, 2008 Remco Bressers 6 comments

In the past, i configured a lot of nifty things on Juniper M-series routers like BGP, OSPF and all sorts of routing stuff. Back to basics (NAT) would have to be a piece of cake :-) .

But…

Configuring NAT on a J-series Juniper box is pretty well documented in the Juniper documentation. I mean.. VERY well documented in a way nobody seems to get the whole point about NAT’ting on the box.

The most straightforward NAT configuration is never being discussed anywhere in the documentation and that makes it pretty hard to get it to work. After some braincracks, i finally managed to get it to work in a way i want it.

The usual CPE setup applies here, so we have one single public IP address on the outside and an RFC1918 192.168.1.0/24 subnet on the inside interface. Our default gateway resides at 217.1.10.254. Our mailserver is at 192.168.1.254, doing only SMTP.

NAT the easy way

“NAT the easy way”

Alright, now we know how to setup the network, let’s configure the J-box. Forget the J-web interface as we’re not going to use it. JunOS CLI it is.
Please note that i’m not running JunOS enhanced services in this example. With ES it should be a little more straightforward and easier to configure, but i just wanted it to run on the plain vanilla JunOS 9.1
Log in to the box, and start configuring the default stuff:
remco@router> configure
remco@router# set system host-name myrouter
remco@router# set system domain-name remcobressers.nl
remco@router# set root-authentication plain-text-password

    Enter your password here for root access.

remco@router# set domain-search remcobressers.nl
remco@router# set time-zone Europe/Amsterdam
remco@router# set location country-code nl
remco@router# set system name-server 217.1.10.10 217.1.10.11
remco@router# set system login user remco uid 2000 class super-user authentication plain-text-password

    Enter your user password to enter the CLI.

remco@router# set system services ssh

Alright. We’re all set. Now let’s configure the interfaces. Let’s say ge-0/0/0 is our outside WAN interface and ge-0/0/1 is the inside LAN interface.

remco@router# edit interfaces ge-0/0/0
[edit interfaces ge-0/0/0]
remco@router# set description "WAN"
remco@router# set unit 0 family inet address 217.1.10.1/24

remco@router# top
[edit]
remco@router# edit interfaces ge-0/0/1
[edit interfaces ge-0/0/1]
remco@router# set description "LAN"
remco@router# set unit 0 family inet address 192.168.1.1/24
remco@router# top
[edit]
remco@router# set routing-options static route 0.0.0.0/0 next-hop 217.1.10.1

Ok. Our basic setup is completed. Let’s configure NAT. We have an internal server at 192.168.1.254 which does SMTP, so we need to configure 2 things:

  • NAT from LAN to the WAN (overload)
  • NAT port forwarding from WAN to 192.168.1.254 SMTP on the LAN

Shouldn’t be to difficult.

remco@router# edit services service-set wan-service-set
[edit services service-set wan-service-set]
remco@router# set nat-rules nat-outgoing
remco@router# set nat-rules nat-incoming
remco@router# set interface-service service-interface sp-0/0/0.0
remco@router# up
[edit services]
remco@router# edit nat
[edit services nat]
remco@router# set pool nat-pool address-range low 217.1.10.1 high 217.1.10.1
remco@router# set pool nat-pool port automatic
remco@router# edit rule nat-outgoing
[edit services nat rule nat-outgoing]
remco@router# set match-direction output
remco@router# set term 1 then translated source-pool nat-pool
remco@router# set term 1 then translated translation-type source dynamic
remco@router# up
[edit services nat]
remco@router# edit rule nat-incoming
[edit services nat rule nat-incoming]
remco@router# set match-direction input
remco@router# set term smtp from destination-address 217.1.10.1/32
remco@router# set term smtp from applications junos-smtp
remco@router# set term smtp then translated destination-prefix 192.168.1.254/32
remco@router# set term smtp then translated translation-type destination static
remco@router# set term other from destination-address 217.1.10.1/32
remco@router# set term other then no-translation

Alright. This looks like a little confusing. It all comes down to the following.

  1. Create a service-set named “wan-service-set”, which holds our nat rules “nat-outgoing” and “nat-incoming”. Services needs a virtual services interface. In this case, the default is sp-0/0/0.0.
  2. In the NAT configuration, we create a pool, which holds one single public IP address (217.1.10.1). The ports are dynamically assigned.
  3. Our outgoing NAT rule is used to translate our internal traffic to the Internet on the public address. Our source pool is the pool we just created.
  4. Our incoming NAT rule is used to translate incoming SMTP traffic to our internal SMTP server at 192.168.1.254.
  5. We use the application “helper” junos-smtp instead of creating our own application. The result is the same.
  6. Other incoming traffic won’t be translated (this is important to include).

We now need to configure the service-set “wan-service-set” to the interface we do the translation on, which is the outside interface ge-0/0/0.

remco@router# top
[edit]
remco@router# edit interfaces ge-0/0/0 unit 0 family inet
[edit interfaces ge-0/0/0 unit 0 family inet]
remco@router# set service input service-set wan-service-set
remco@router# set service output service-set wan-service-set

Alright, that’s about it. You can commit the configuration now with the “commit” command.
Your configuration will now look like this :

remco@router# top
[edit]
remco@router# show

## Last changed: 2008-07-17 21:13:49 CEST
version 9.1R1.8;
system {
    host-name myrouter;
    domain-name remcobressers.nl;
    domain-search remcobressers.nl;
    time-zone Europe/Amsterdam;
    location country-code nl;
    root-authentication {
        encrypted-password "**************"; ## SECRET-DATA
    }
    name-server {
        217.1.10.10;
        217.1.10.11;
    }
    login {
        user remco {
            uid 2000;
            class super-user;
            authentication {
                encrypted-password "***********"; ## SECRET-DATA
            }
        }
    }
    services {
        ssh {
        }
    }
}
interfaces {
    ge-0/0/0 {
        description "WAN";
        unit 0 {
            family inet {
                service {
                    input {
                        service-set wan-service-set;
                    }
                    output {
                        service-set wan-service-set;
                    }
                }
                address 217.1.10.1/24;
            }
        }
    }
    sp-0/0/0 {
        unit 0 {
            family inet;
        }
    }
    ge-0/0/1 {
        description "LAN";
        unit 0 {
            family inet {
                address 192.168.1.1/24;
            }
        }
    }
}
routing-options {
    static {
        route 0.0.0.0/0 next-hop 217.1.10.254;
    }
}
services {
    service-set wan-service-set {
        nat-rules nat-outgoing;
        nat-rules nat-incoming;
        interface-service {
            service-interface sp-0/0/0.0;
        }
    }
    nat {
        pool nat-pool {
            address-range low 217.1.10.1 high 217.1.10.1;
            port automatic;
        }
        rule nat-outgoing {
            match-direction output;
            term 1 {
                then {
                    translated {
                        source-pool nat-pool;
                        translation-type {
                            source dynamic;
                        }
                    }
                }
            }
        }
        rule nat-incoming {
            match-direction input;
            term smtp {
                from {
                    destination-address {
                        217.1.10.1/32;
                    }
                    applications junos-smtp;
                }
                then {
                    translated {
                        destination-prefix 192.168.1.254/32;
                        translation-type {
                            destination static;
                        }
                    }
                }
            }
            term other {
                from {
                    destination-address {
                        217.1.10.1/32;
                    }
                }
                then {
                    no-translation;
                }
            }
        }
    }
}
Categories: Networking Tags: