IPFW - NAT and/for OpenVPN

Hello,

First, I run
Code:
FreeBSD next 10.0-STABLE FreeBSD 10.0-STABLE #31 r270258
, with ipfw_nat built as module and ipfw(4) built-in kernel:

Code:
options     IPFIREWALL
options     IPFIREWALL_DEFAULT_TO_ACCEPT
options     IPDIVERT

Here is my rc.conf:

Code:
gateway_enable="YES"
firewall_enable="YES"
firewall_type=open
firewall_script="/etc/ipfw.rules"

What I need is simple: block any new incoming traffic on all ports, allow incoming on some ports (SSH, HTTP, HTTPS), and make all traffic from OpenVPN (tun interface, 10.8.0.0/24 network) NAT on the external interface em0.

Currently, I have this configuration:

Code:
ipfw -q -f flush

cmd="ipfw add"
pif="em0"
tif="tun0"
tnet="10.8.0.0/24"

$cmd 0001 allow tcp     from any to any         established
$cmd 0002 allow ip      from any to any         out keep-state
$cmd 0005 allow all     from any to any         via lo0
$cmd 0006 allow all     from any to any         via $tif

$cmd 0010 check-state
$cmd 0011 allow tcp     from any to any 22      in via $pif

$cmd 0110 allow tcp     from any to any 22      in via $pif
$cmd 0120 allow tcp     from any to any 80      in via $pif
$cmd 0130 allow tcp     from any to any 443     in via $pif
$cmd 0140 allow tcp     from any to any 51413   in via $pif
$cmd 0150 allow udp     from any to any 51413   in via $pif
$cmd 0160 allow icmp    from any to any         in via $pif

#ipfw nat 1200 config if $pif deny_in
#$cmd 1201 nat 1200 all  from ${tnet} to any     out via $pif keep-state
#$cmd 1202 nat 1200 all  from any to any         in via $pif  keep-state

$cmd 9999 deny all from any to any in via $pif

After losing me in man-pages and searching on the Internet, I found different configurations with the natd daemon (isn't that ugly?) and IPFW nat configurations without any success.

Does someone could explain me how to setup that configuration and how it -should- work?

Oh and, a little subsidiary question: why, at rule numbered 0002, if I don't tell out for keep-state, all incoming traffic is allowed?

Thanks.
 
A stateful firewall with NAT is a little bit different from a simple firewall, and here comes a little guidance:

  • The incoming and outgoing traffic need to be handled by two separate NAT rules, s. the example below.
  • Packages passing the stateful NAT may be re-injected into the firewall, and therefore the sysctl variable net.inet.ip.fw.one_pass must be set to 0.
  • Regarding NAT for tun0 of OpenVPN, it makes a difference whether OpenVPN is the initiator of the connection or whether it is listening for incoming connections, please clarify - the rules below are for the first case – OpenVPN is the initiator.
  • There is no need to build a custom kernel for ipfw/NAT, all can be done by loadable modules.

In file /etc/rc.conf specify:
Code:
gateway_enable="YES"
firewall_enable="YES"
firewall_nat_enable="YES"
firewall_script="/etc/ipfw.rules"

To file /etc/sysctl.conf add:
Code:
net.inet.ip.fw.one_pass=0

For firewall configuration, the sript file /etc/ipfw.rules would be:
Code:
#!/bin/sh

/sbin/ipfw -q flush
/sbin/ipfw -q nat 1 config if em0 reset

/sbin/ipfw -q add 10 allow ip from any to any via lo0
/sbin/ipfw -q add 20 allow ip from any to any via tun0
/sbin/ipfw -q add 30 deny ip from any to any not antispoof in

# NAT rule for incomming packets.
/sbin/ipfw -q add 100 nat 1 ip from any to any via em0 in
/sbin/ipfw -q add 101 check-state

# Rules for outgoing traffic - allow everything that is not explicitely denied.
# for example outgoing SMTP may com from a spam bot, mDNS should not go into the WAN
# remove or modify the following rule, if said traffic is required
/sbin/ipfw -q add 1000 deny ip from not me to any 25,5353 via em0 out

# Allow all other outgoing connections.
/sbin/ipfw -q add 2000 skipto 10000 tcp from any to any via em0 out setup keep-state
/sbin/ipfw -q add 2010 skipto 10000 udp from any to any via em0 out keep-state

# Rules for incomming traffic - deny everything that is not explicitely allowed.
/sbin/ipfw -q add 5000 allow tcp from any to me 22,80,443 via em0 in setup keep-state

# Deny tcp/udp packets, but don't touch gre, esp, icmp traffic.
/sbin/ipfw -q add 9998 deny tcp from any to any via em0
/sbin/ipfw -q add 9999 deny udp from any to any via em0

# NAT rule for outgoing packets.
/sbin/ipfw -q add 10000 nat 1 ip from any to any via em0 out

# Allow anything else - just in case ipfw is not configured as open firewall.
/sbin/ipfw -q add 65534 allow ip from any to any
Note, in this ipfw/NAT stateful rule set template, most of the customizations would go after rule 1000 and before rule 9998.

Restart your system, in order to make everything active.
 
Thank you *very much* for your answer, I feel like a blind person who is getting a new eye. But missing the second one, so, I have a few questions, if you're ok :)

obsigna said:
  • The incoming and outgoing traffic need to be handled by two separate NAT rules, s. the example below.
  • Packages passing the stateful NAT may be re-injected into the firewall, and therefore the sysctl variable net.inet.ip.fw.one_pass must be set to 0.

Are these two prerequisites linked together?
obsigna said:
Regarding NAT for tun0 of OpenVPN, it makes a difference whether OpenVPN is the initiator of the connection or whether it is listening for incoming connections, please clarify - the rules below are for the first case – OpenVPN is the initiator.

Mmmmh not sure here. OpenVPN is here working as a server, so clients connects to via port 443 and changes the default route to go through the (client) tun interface. I cannot say if here OpenVPN is in listening or initiator. I would say OpenVPN is the initiator because answers will be transmitted to/through it.

obsigna said:
There is no need to build a custom kernel for ipfw/NAT, all can be done by loadable modules.

Yes, but I already have a custom kernel so this is not a problem.

obsigna said:
To file /etc/sysctl.conf add:
Code:
net.inet.ip.fw.one_pass=0

Is there any documents speaking about that? Is it related to the "stateful firewall"?

obsigna said:
Restart your system, in order to make everything active.

sh /etc/ipfw.rules should be ok?
 
Leryan said:
obsigna said:
  • The incoming and outgoing traffic need to be handled by two separate NAT rules, s. the example below.
  • Packages passing the stateful NAT may be re-injected into the firewall, and therefore the sysctl variable net.inet.ip.fw.one_pass must be set to 0.
Are these two prerequisites linked together?
You need two separate NAT rules in a stateful firewall rule set, because for incoming packets from outside, the state must be checked after translation, and for outgoing packets, the state must be set/kept before the outgoing NAT rule. See the respective chapter in the Handbook:
http://www.freebsd.org/doc/en_US.ISO885 ... twork-natd

The sysctl variable is briefly mentioned at the bottom of the NAT chapter of the ipfw(8) manual:
NAT chapter said:
...
To let the packet continue after being (de)aliased, set the sysctl variable net.inet.ip.fw.one_pass to 0. ...
At the time when I set up and tested my stateful, natting firewall configuration, it did not work, without setting net.inet.ip.fw.one_pass to 0. If you are curious, then please test it again, leaving that variable at its default value 1. Let us know the result, perhaps I did something wrong at that time.

Leryan said:
obsigna said:
Regarding NAT for tun0 of OpenVPN, it makes a difference whether OpenVPN is the initiator of the connection or whether it is listening for incoming connections, please clarify - the rules below are for the first case – OpenVPN is the initiator.
Mmmmh not sure here. OpenVPN is here working as a server, so clients connects to via port 443 and changes the default route to go through the (client) tun interface. I cannot say if here OpenVPN is in listening or initiator. I would say OpenVPN is the initiator because answers will be transmitted to/through it.
I need to re-phrase the question. Are the OpenVPN clients sitting in the same network with the OpenVPN server, or are the OpenVPN clients separated from the OpenVPN server by exactly that NAT that you are going to setup?

Leryan said:
obsigna said:
To file /etc/sysctl.conf add:
Code:
net.inet.ip.fw.one_pass=0
Is there any documents speaking about that? Is it related to the "stateful firewall"?
See my answer above!

Leryan said:
obsigna said:
Restart your system, in order to make everything active.
sh /etc/ipfw.rules should be ok?
Only if you add another command sysctl net.inet.ip.fw.one_pass=0.
 
Clients are not one the same network of OpenVPN: clients connects to OpenVPN with a public IP, then I want all client's traffic to be natted to this IP.

I've made some modifications to your configuration:

Code:
#!/bin/sh

cmd="/sbin/ipfw -q"
pif="vtnet0"

$cmd flush
$cmd nat 1 config if $pif reset

$cmd add 1 allow tcp from any to any established
$cmd add 10 allow ip from any to any via lo0
$cmd add 20 allow ip from any to any via tun0

# NAT rule for incomming packets.
$cmd add 100 nat 1 ip from any to any via $pif in
$cmd add 101 check-state

# Allow all other outgoing connections.
$cmd add 2000 skipto 10000 tcp from any to any via $pif out setup keep-state
$cmd add 2010 skipto 10000 udp from any to any via $pif out keep-state

# Rule for incomming traffic - deny everything that is not explicitely allowed.
$cmd add 5000 allow tcp from any to me 22,80,443 via $pif in setup keep-state
$cmd add 6000 allow icmp from any to any via $pif

# Deny all other incomming traffic
$cmd add 9999 deny all from any to any in via $pif

# NAT rule for outgoing packets.
$cmd add 10000 nat 1 ip from any to any via $pif out

$cmd add 65534 allow ip from any to any

I run a VM to make my tests, that's why I change from em0 to vtnet0, and it's a FreeBSD 10.0-RELEASE-p7.

The sysctl is to 0, but it does'nt work as intended. Just to precise, the OpenVPN configuration is the same I case use successfully on another (Linux) server. Same for clients.

Well, here is a tcpdump capture from the VM (freebsd), when I do a curl request on any http server outside:

Code:
root@1001-freebsd-pkg:~ # tcpdump -nli tun0 port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type NULL (BSD loopback), capture size 65535 bytes
capability mode sandbox enabled
13:50:43.351949 IP 10.8.0.14.60718 > 217.70.182.162.80: Flags [S], seq 3879904394, win 29200, options [mss 1366,sackOK,TS val 6284662 ecr 0,nop,wscale 7], length 0
13:50:44.348255 IP 10.8.0.14.60718 > 217.70.182.162.80: Flags [S], seq 3879904394, win 29200, options [mss 1366,sackOK,TS val 6284912 ecr 0,nop,wscale 7], length 0
^C
2 packets captured
6 packets received by filter
0 packets dropped by kernel
root@1001-freebsd-pkg:~ # tcpdump -nli vtnet0 not port 22
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vtnet0, link-type EN10MB (Ethernet), capture size 65535 bytes
capability mode sandbox enabled
13:50:59.347830 IP 80.215.134.116.2651 > 10.1.1.1.443: Flags [P.], seq 3781604094:3781604197, ack 1008829232, win 23176, options [nop,nop,TS val 6288660 ecr 2585622043], length 103
13:50:59.453755 IP 10.1.1.1.443 > 80.215.134.116.2651: Flags [.], ack 103, win 1029, options [nop,nop,TS val 2585626351 ecr 6288660], length 0
13:51:00.345883 IP 80.215.134.116.2651 > 10.1.1.1.443: Flags [P.], seq 103:206, ack 1, win 23176, options [nop,nop,TS val 6288910 ecr 2585626351], length 103
13:51:00.451729 IP 10.1.1.1.443 > 80.215.134.116.2651: Flags [.], ack 206, win 1029, options [nop,nop,TS val 2585627349 ecr 6288910], length 0
13:51:02.350786 IP 80.215.134.116.2651 > 10.1.1.1.443: Flags [P.], seq 206:309, ack 1, win 23176, options [nop,nop,TS val 6289411 ecr 2585627349], length 103
13:51:02.456726 IP 10.1.1.1.443 > 80.215.134.116.2651: Flags [.], ack 309, win 1029, options [nop,nop,TS val 2585629354 ecr 6289411], length 0
13:51:04.397599 IP 10.1.1.1.443 > 80.215.134.116.2651: Flags [P.], seq 1:56, ack 309, win 1029, options [nop,nop,TS val 2585631295 ecr 6289411], length 55
13:51:04.740680 IP 10.1.1.1.443 > 80.215.134.116.2651: Flags [P.], seq 1:56, ack 309, win 1029, options [nop,nop,TS val 2585631638 ecr 6289411], length 55
13:51:04.757013 IP 80.215.134.116.2651 > 10.1.1.1.443: Flags [.], ack 56, win 23176, options [nop,nop,TS val 6290003 ecr 2585631295], length 0
13:51:04.786235 IP 80.215.134.116.2651 > 10.1.1.1.443: Flags [.], ack 56, win 23176, options [nop,nop,TS val 6290022 ecr 2585631638,nop,nop,sack 1 {1:56}], length 0
13:51:06.377525 IP 80.215.134.116.2651 > 10.1.1.1.443: Flags [P.], seq 309:412, ack 56, win 23176, options [nop,nop,TS val 6290412 ecr 2585631638], length 103
13:51:06.483750 IP 10.1.1.1.443 > 80.215.134.116.2651: Flags [.], ack 412, win 1029, options [nop,nop,TS val 2585633381 ecr 6290412], length 0
^C
12 packets captured
27 packets received by filter
0 packets dropped by kernel
 
Leryan said:
Clients are not one the same network of OpenVPN: clients connects to OpenVPN with a public IP, then I want all client's traffic to be natted to this IP.
I am sorry, I read this 3 times and it is still not clear to me, and without having a clear understanding of the topology it is impossible to setup a working natting firewall.

For example, I have running an L2TP/IPsec-VPN dial-in server on my gateway machine, to which clients from the internet can connect, and then would have access to everything in the local network. With respect to my VPN-Server My topology is:
Code:
INTERNET                GATEWAY (2 NICs)         INTRANET
VPN Clients                       VPN-Server
[public IP]-------[public IP][NAT][local IP]--------[LAN]
                  |
      [WAN]-------|
The VPN-Server is listening on a local address behind the NAT, and therefore my NAT is configured for port redirection like so:
Code:
/sbin/ipfw -q nat 1 config if $WAN reset \
                                   redirect_port udp 10.0.0.1:500   500 \
                                   redirect_port udp 10.0.0.1:4500 4500
IPsec works on the udp ports 500 and 4500, and 10.0.0.1 is the local IP of the FreeBSD Gateway. In addition, I got 2 more firewall rules for allowing the VPN traffic:
Code:
# Rules for allowing dial-in calls to the IPsec VPN server
# that is listening on a LAN interface behind the NAT.
/sbin/ipfw -q add 201 skipto 10000 udp from any to any  500 via $WAN in keep-state
/sbin/ipfw -q add 202 skipto 10000 udp from any to any 4500 via $WAN in keep-state
The L2TP server allocates dynamically local IP addresses from the reserved IP pool10.0.0.150-10.0.0.199 to the clients, and in order the clients have access to the whole LAN, the L2TP server provides for proxy-arp.

If your setup is quite like this, but only having an OpenVPN server instead of a L2TP/IPsec-VPN server, then you would need to specify port redirection rules, one for each port/protocol combo on which OpenVPN is listening on, and you need similar skipto rules like mine #201 and #202.

If your setup is different in respect to where the VPN server is listening for incoming traffic, then please specify. Everything depends on that!
 
Well, if I make a schema I'm sure it will be clear :-)

Code:
VPN CLIENTS/INTERNET        GATEWAY (1 NIC)

                            [ VPN  / tun0 ]→---×
                                  ↑            |
                                  |            |
[vpn ip]→[public IP]-------→[public IP/em0]    |
                            [NAT on pub IP]←---×
                                  |          
                                  |
                                  ↓
                              [INTERNET]

I want VPN clients that send packets to be routed over the Internet, go through the VPN and then be NAT on the server's public IP. Like any ISP "box".
I don't want any incoming port translation.
 
For default setup of OpenVPN 2.x (TCP/UDP 1194 and more than 1 tun) try:

Code:
#!/bin/sh

cmd="/sbin/ipfw -q"
pif="vtnet0"

$cmd flush
$cmd nat 1 config if $pif reset

$cmd add 10 allow ip from any to any via lo0
$cmd add 20 allow ip from any to any via tun*

# NAT rule for incomming packets.
$cmd add 100 nat 1 ip from any to any via $pif in
$cmd add 101 check-state

# Allow all other outgoing connections.
$cmd add 2000 skipto 10000 tcp from any to any via $pif out setup keep-state
$cmd add 2010 skipto 10000 udp from any to any via $pif out keep-state

# Rule for incomming traffic - deny everything that is not explicitely allowed.
$cmd add 5000 allow tcp from any to me 22,80,443,1194 via $pif in setup keep-state
$cmd add 5001 allow udp from any to me 1194 via $pif in keep-state
$cmd add 6000 allow icmp from any to any via $pif

# Deny all other incomming traffic
$cmd add 9999 deny all from any to any in via $pif

# NAT rule for outgoing packets.
$cmd add 10000 nat 1 ip from any to any via $pif out

$cmd add 65534 allow ip from any to any

Note: $pif is an ambiguous identifier, since this may be the abbreviation for public interface or for private interface.

PS: I am sure that OpenVPN needs to provide Proxy-ARP for its clients, however, I have no idea, how to set this up with OpenVPN on FreeBSD.
 
Back
Top