Zurück

How to Setup a Linux Firewall with PPPoE/NAT/iptables


PPPoE for Linux

Many Internet service providers are using the Point-to-Point Protocol over Ethernet (PPPoE) to provide residential Digital Subscriber Link (DSL) broadband Internet access. Most ISP’s do not support Linux and supply PPPoE clients only for Windows and Mac OS.

The PPPoE Protocol

PPPoE is a protocol for encapsulating PPP frames in Ethernet frames. PPP is a data-link-level protocol typically used to encapsulate network-level packets over an asynchronous serial line. This mode of usage is called asynchronous.

While PPP is a peer-to-peer protocol, PPPoE is initially a client-server protocol. The client (usually a personal computer) searches for a PPPoE server (called an access concentrator) and obtains the access concentrator’s MAC address and a session number. The process of setting up a PPPoE session is called discovery.

The Pseudo-TTY

The pppd program and the Linux kernel expect to transmit PPP frames over a TTY device. Luckily, UNIX (and Linux) support the concept of a pseudo-tty. This is a device which “looks” like a TTY, but instead of being connected to a physical terminal, it is connected to a UNIX process. Whenever something writes to the pseudo-tty, the data appears on the standard input of the back-end process. Whenever the back-end process writes to its standard output, the data may be read from the pseudo-tty.

Even more luckily, recent versions of pppd (2.3.7 and newer) support a pty option. This option automatically starts the back-end process and performs all the mundane operations required to connect it to a pseudo-tty. So to start the PPPoE link, you start pppd with the appropriate
pty option, which runs the pppoe executable connected to the pseudo-tty.

The MTU Problem

PPPoE introduces a real and annoying problem. The maximum Ethernet frame is 1518 bytes long. 14 bytes are consumed by the header, and 4 by the frame-check sequence, leaving 1500 bytes for the payload. For this reason, the Maximum Transmission Unit (MTU) of an Ethernet interface is usually 1500 bytes.

This is the largest IP datagram which can be transmitted over the interface without fragmentation. PPPoE adds another six bytes of overhead, and the PPP protocol field consumes two bytes, leaving 1492 bytes for the IP datagram. The MTU of PPPoE interfaces is therefore 1492 bytes.

When a TCP connection is initiated, each side can optionally specify the Maximum Segment Size (MSS). TCP chops a stream of data into segments, and MSS specifies the largest segment each side will accept. By default, the MSS is chosen as the MTU of the outgoing interface minus the usual size of the TCP and IP headers (40 bytes), which results in an MSS of 1460 bytes for an Ethernet interface.

TCP stacks try to avoid fragmentation, so they use an MSS which will not cause fragmentation on their outgoing interface. Unfortunately, there may be intermediate links with lower MTU’s which will cause fragmentation. Good TCP stacks perform path MTU discovery. In path MTU discovery, a TCP stack sets a special Don’t Fragment (DF) bit in the IP datagrams. Routers which cannot forward the datagram without fragmenting it are supposed to drop it and send an ICMP “Fragmentation-Required” datagram to the originating host.

The originating host then tries a lower MTU value. Unfortunately, many routers are anti-social and do not generate the fragmentation-required datagrams. Many firewalls are equally anti-social and drop all ICMP datagrams.

Now consider a client workstation on an Ethernet LAN connected to a PPPoE gateway. It opens a TCP connection to a web server. Because the Ethernet MTU is 1500, it suggests an MSS of 1460. The web server is also on an Ethernet and also suggests an MSS of 1460. The client then requests a web page. This request is typically small and reaches the web server. The server responds with many TCP segments, most of which are 1460 bytes long.

The maximum-sized segments result in 1500-byte IP datagrams and make their way to the DSL provider. The DSL provider cannot transmit a 1500-byte IP datagram over a PPPoE link, so it drops it (assume for now that the DF bit is set.) Furthermore, being anti-social, the DSL provider does not send an ICMP message to the web server.

The net result is that packets are silently dropped. The web client hangs waiting for data, and the web server keeps retransmitting until it finally gives up, or the connection is closed by the user aborting the web client.

One way around this is to artificially set an MSS for the default route on all LAN hosts behind the PPPoE gateway. This is annoying, as it requires changes on each host. Instead, rp-pppoe “listens in” on the MSS negotiation and modifies the MSS if it is too big. Adjusting the MSS is a hack. It breaks the concept of the transport-layer being end-to-end. It will not work with IPSec, because IPSec will not let you damage IP packets (they will fail to authenticate.) Nevertheless, it is a fairly effective solution to an ugly real-world problem, and is used by default in rp-pppoe.

Installation and Configuration of PPP / PPPoE

We setup a GENTOO 2005.0 Linux machine with RP-PPPoE, PPP and the Firewall Software iptables. The internal network 192.168.138.0 is mapped to the dynamically asigned IP address using NAT / IP-Masquerading.

  1. Download PPP from Sambas Site, on GENTOO Linux use:

    emerge ppp

    This creates the directory/etc/ppp
     

  2. Download RP-PPPoE from RoaringPenguin

    tar xzvf rp-pppoe-xxx.tar.gz
    cd src
    ./configure
    make
    make install

    cd /etc/ppp
    ls -l
    -rw-------  1 root root   37 Jul 25 09:59 chap-secrets
    -rw-------  1 root root    0 Jul 25 09:59 chap-secrets-bak
    -rw-------  1 root root   78 Jul 25 09:42 chap-secrets.example
    -rw-r--r--  1 root root  353 Jul 25 09:42 chat-default
    -rw-r--r--  1 root root  938 Jul 25 09:53 firewall-masq
    -rw-r--r--  1 root root  836 Jul 25 09:53 firewall-standalone
    -rwxr-xr-x  1 root root  931 Jul 25 09:42 ip-down*
    -rwxr-xr-x  1 root root 1081 Jul 25 09:42 ip-up*
    -rw-r--r--  1 root root    5 Jul 25 09:42 options
    -rw-r--r--  1 root root   53 Jul 25 09:42 options-pppoe
    -rw-r--r--  1 root root  238 Jul 25 09:42 options-pptp
    -rw-------  1 root root   37 Jul 25 09:59 pap-secrets
    -rw-------  1 root root    0 Jul 25 09:59 pap-secrets-bak
    -rw-------  1 root root   77 Jul 25 09:42 pap-secrets.example
    drwxr-xr-x  2 root root 4096 Jul 25 09:42 peers/
    drwxr-xr-x  2 root root 4096 Jul 25 09:53 plugins/
    -rw-r--r--  1 root root 4592 Jul 25 09:59 pppoe.conf
    -rw-------  1 root root 4562 Jul 25 09:59 pppoe.conf-bak
    -rw-r--r--  1 root root  104 Jul 25 09:53 pppoe-server-options

How to Connect

  1. Set up your Ethernet hardware

    First, make sure the Ethernet card you intend to use with the modem is visible to the Linux kernel.

    ifconfig eth0

    should display something like this:

    eth0 Link encap:Ethernet HWaddr 00:60:67:62:31:D4

    plust some more lines. Your HWaddr will be different. DO NOT assign an IP address to the Ethernet card. DO NOT configure the card to come up at boot time.
    Note, that the PPP-Interface (ppp0) is automatically setup by PPPoE/PPP, it cannot be setup manually.
     

  2. Configure various files

    Several files need editing. The easiest way to do this is to run the following command as root:

    adsl-setup

    Answer the questions and you should be all set. If you want to know what goes on behind, continue reading.
     

  3. Edit pap-secrets

    "xx.xyz@domain.ch" * "xxxxxxx"

    Edit the "pap-secrets" file, inserting your proper user-ID and password. Install the file (or copy the relevant lines) to /etc/ppp/pap-secrets. Your ISP may use CHAP authentication. In this case, add the line to /etc/ppp/chap-secrets.
     

  4. Edit pppoe.conf

    The file /etc/ppp/pppoe.conf contains configuration information for the ADSL connection.

    #***********************************************************************
    #
    # pppoe.conf
    #
    # Configuration file for rp-pppoe.  Edit as appropriate and install in
    # /etc/ppp/pppoe.conf
    #
    # NOTE: This file is used by the adsl-start, adsl-stop, adsl-connect and
    #       adsl-status shell scripts.  It is *not* used in any way by the
    #       "pppoe" executable.
    #
    # Copyright (C) 2000 Roaring Penguin Software Inc.
    #
    # This file may be distributed under the terms of the GNU General
    # Public License.
    #
    # LIC: GPL
    # $Id: pppoe.conf,v 1.10 2002/04/09 17:28:38 dfs Exp $
    #***********************************************************************

    # When you configure a variable, DO NOT leave spaces around the "=" sign.
    # Ethernet card connected to ADSL modem
    ETH='eth0'

    # ADSL user name.  You may have to supply "@provider.com"  Sympatico
    # users in Canada do need to include "@sympatico.ca"
    # Sympatico uses PAP authentication.  Make sure /etc/ppp/pap-secrets
    # contains the right username/password combination.
    # For Magma, use xxyyzz@magma.ca

    USER='xx.xyz@domain.ch'

    # Bring link up on demand?  Default is to leave link up all the time.
    # If you want the link to come up on demand, set DEMAND to a number indicating
    # the idle time after which the link is brought down.

    DEMAND=no
    #DEMAND=300

    # DNS type: SERVER=obtain from server; SPECIFY=use DNS1 and DNS2;
    # NOCHANGE=do not adjust.
    DNSTYPE=SPECIFY

    # Obtain DNS server addresses from the peer (recent versions of pppd only)
    # In old config files, this used to be called USEPEERDNS.  Changed to
    # PEERDNS for better Red Hat compatibility
    PEERDNS=no

    DNS1=195.186.4.111
    DNS2=195.186.1.111

    # Make the PPPoE connection your default route.  Set to
    # DEFAULTROUTE=no if you don't want this.

    DEFAULTROUTE=yes

    ### ONLY TOUCH THE FOLLOWING SETTINGS IF YOU'RE AN EXPERT

    # How long adsl-start waits for a new PPP interface to appear before
    # concluding something went wrong.  If you use 0, then adsl-start
    # exits immediately with a successful status and does not wait for the
    # link to come up.  Time is in seconds.
    #
    # WARNING WARNING WARNING:
    #
    # If you are using rp-pppoe on a physically-inaccessible host, set
    # CONNECT_TIMEOUT to 0.  This makes SURE that the machine keeps trying
    # to connect forever after adsl-start is called.  Otherwise, it will
    # give out after CONNECT_TIMEOUT seconds and will not attempt to
    # connect again, making it impossible to reach.

    CONNECT_TIMEOUT=30

    # How often in seconds adsl-start polls to check if link is up
    CONNECT_POLL=2

    # Specific desired AC Name
    ACNAME=

    # Specific desired service name
    SERVICENAME=

    # Character to echo at each poll.  Use PING="" if you don't want
    # anything echoed

    PING="."

    # File where the adsl-connect script writes its process-ID.
    # Three files are actually used:
    #   $PIDFILE       contains PID of adsl-connect script
    #   $PIDFILE.pppoe contains PID of pppoe process
    #   $PIDFILE.pppd  contains PID of pppd process

    CF_BASE=`basename $CONFIG`
    PIDFILE="/var/run/$CF_BASE-adsl.pid"

    # Do you want to use synchronous PPP?  "yes" or "no".  "yes" is much
    # easier on CPU usage, but may not work for you.  It is safer to use
    # "no", but you may want to experiment with "yes".  "yes" is generally
    # safe on Linux machines with the n_hdlc line discipline; unsafe on others.

    SYNCHRONOUS=no

    # Do you want to clamp the MSS?  Here's how to decide:
    # - If you have only a SINGLE computer connected to the ADSL modem, choose
    #   "no".
    # - If you have a computer acting as a gateway for a LAN, choose "1412".
    #   The setting of 1412 is safe for either setup, but uses slightly more
    #   CPU power.
    CLAMPMSS=1412
    #CLAMPMSS=no

    # LCP echo interval and failure count.
    LCP_INTERVAL=20
    LCP_FAILURE=3

    # PPPOE_TIMEOUT should be about 4*LCP_INTERVAL
    PPPOE_TIMEOUT=80

    # Firewalling: One of NONE, STANDALONE or MASQUERADE
    FIREWALL=NONE

    # Linux kernel-mode plugin for pppd.  If you want to try the kernel-mode
    # plugin, use LINUX_PLUGIN=/etc/ppp/plugins/rp-pppoe.so

    LINUX_PLUGIN=

    # Any extra arguments to pass to pppoe.  Normally, use a blank string
    # like this:

    PPPOE_EXTRA=""

    # Rumour has it that "Citizen's Communications" with a 3Com
    # HomeConnect ADSL Modem DualLink requires these extra options:

    # PPPOE_EXTRA="-f 3c12:3c13 -S ISP"

    # Any extra arguments to pass to pppd.  Normally, use a blank string
    # like this:
    PPPD_EXTRA=""

    ########## DON'T CHANGE BELOW UNLESS YOU KNOW WHAT YOU ARE DOING
    # If you wish to COMPLETELY overrride the pppd invocation:
    # Example:
    # OVERRIDE_PPPD_COMMAND="pppd call dsl"

    # If you want adsl-connect to exit when connection drops:
    # RETRY_ON_FAILURE=no
     

  5.  Set up DNS

    If you are using DNS servers supplied by your ISP, edit the file /etc/resolv.conf
     

  6. Bring up the connection at boot time (For GENTOO)

    #!/sbin/runscript

    depend() {
    need net
    use logger dns
    }

    # Paths to programs
    START=/usr/sbin/adsl-start
    STOP=/usr/sbin/adsl-stop
    STATUS=/usr/sbin/adsl-status

    start() {
    ebegin "Bringing up ADSL link"
    $START &>/dev/null
    touch /var/lock/subsys/adsl
    eend $?
    }

    stop() {
    ebegin "Shutting down ADSL link"
    $STOP &>/dev/null
    rm -f /var/lock/subsys/adsl
    eend $?
    }
     

  7. Commands to control the ADSL link

    As root, bring up the link by typing: adsl-start
    As root, bring down the link by typing: adsl-stop

    ps -ef

    /usr/sbin/adsl-connect
    /usr/sbin/pppd pty /usr/sbin/pppoe -p /var/run/pppoe.conf-adsl.pid.pppoe \
       -I eth0 -T 80 -U -m 1412 noipdefa
    /usr/sbin/pppoe -p /var/run/pppoe.conf-adsl.pid.pppoe \
       -I eth0 -T 80 -U -m 1412

    ifconfig -a

    eth0      Link encap:Ethernet  HWaddr 00:30:48:28:AA:4A
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:23529 errors:0 dropped:0 overruns:0 frame:0
              TX packets:18759 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:22942360 (21.8 Mb)  TX bytes:1973186 (1.8 Mb)
              Base address:0x3000 Memory:fc200000-fc220000

    eth1      Link encap:Ethernet  HWaddr 00:30:48:28:AA:4B
              inet addr:192.168.138.1  Bcast:192.168.138.255  Mask:255.255.255.0
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:18130 errors:0 dropped:0 overruns:0 frame:0
              TX packets:25373 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:1819853 (1.7 Mb)  TX bytes:21836499 (20.8 Mb)
              Base address:0x3040 Memory:fc220000-fc240000

    lo        Link encap:Local Loopback
              inet addr:127.0.0.1  Mask:255.0.0.0
              UP LOOPBACK RUNNING  MTU:16436  Metric:1
              RX packets:16 errors:0 dropped:0 overruns:0 frame:0
              TX packets:16 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0
              RX bytes:1590 (1.5 Kb)  TX bytes:1590 (1.5 Kb)

    ppp0      Link encap:Point-to-Point Protocol
              inet addr:213.3.5.17  P-t-P:195.186.253.131  Mask:255.255.255.255
              UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1492  Metric:1
              RX packets:21685 errors:0 dropped:0 overruns:0 frame:0
              TX packets:14272 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:3
              RX bytes:22260398 (21.2 Mb)  TX bytes:1314858 (1.2 Mb)

    netstat -nr

    Kernel IP routing table
    Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
    195.186.253.131 0.0.0.0         255.255.255.255 UH        0 0          0 ppp0
    192.168.138.0   0.0.0.0         255.255.255.0   U         0 0          0 eth1
    127.0.0.0       127.0.0.1       255.0.0.0       UG        0 0          0 lo
    0.0.0.0         195.186.253.131 0.0.0.0         UG        0 0          0 ppp0

    adsl-status

    adsl-status: Link is up and running on interface ppp0
    ppp0      Link encap:Point-to-Point Protocol
              inet addr:213.3.5.17  P-t-P:195.186.253.131  Mask:255.255.255.255
              UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1492  Metric:1
              RX packets:2432 errors:0 dropped:0 overruns:0 frame:0
              TX packets:2145 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:3
              RX bytes:628102 (613.3 Kb)  TX bytes:241917 (236.2 Kb)

Using A Zyxel Router as a Modem (Bridged Mode)

Connect to your Zyxel Router using TELNET. In Menu 1, change Route IP = No, Bridge = Yes.

In Menu 4, change Encapsulation to RFC 1483. Make sure VPI and VCI are setup correct for your Provider.

In Menu 11.2, change Route = None, Bridge = Yes.

 

Now, reboot the Modem ... it's now a Modem no more a Router. All settings are ignored, the Modem can no longer talk PPPoE!

Linux Routing

The first task to undertake when configuring the firewall ruleset is to turn on all the options you would like the kernel to use when processing IP packets. The very first thing is that you turn ip_forwarding off - it will be turned on after everything is done. We have set the following kernel options in the /etc/conf.d/local.start.

# /etc/conf.d/local.start:
# Setup Kernel Options for iptables/Firewall"

# If your firewall has a dynamic IP address, use this setting
echo 2 > /proc/sys/net/ipv4/ip_dynaddr

# Hardening settings:
if [ -e /proc/sys/net/ipv4/conf/all/accept_source_route ]; then
  for f in /proc/sys/net/ipv4/conf/*/accept_source_route
  do
   echo 0 > $f
  done
fi


# Do not respond to 'redirected' packets
if [ -e /proc/sys/net/ipv4/conf/all/send_redirects ]; then
  for f in /proc/sys/net/ipv4/conf/*/send_redirects
  do
   echo 0 > $f
  done
fi


# Do not reply to 'proxyarp' packets
if [ -e /proc/sys/net/ipv4/conf/all/proxy_arp ]; then
  for f in /proc/sys/net/ipv4/conf/*/proxy_arp
  do
   echo 0 > $f
  done
fi


# Detecting and stopping spoofed packets
echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
if [ -e /proc/sys/net/ipv4/conf/all/rp_filter ]; then
  for f in /proc/sys/net/ipv4/conf/*/rp_filter
  do
   echo 1 > $f
  done
fi


# Ignore source routing (dictating what route the traffic will take)
# from the origin of the packet as dictated by the client

echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route
if [ -e /proc/sys/net/ipv4/conf/all/accept_source_route ]; then
  for f in /proc/sys/net/ipv4/conf/*/accept_source_route
  do
   echo 0 > $f
  done
fi


# Suppress ICMP redirects
if [ -e /proc/sys/net/ipv4/conf/all/accept_redirects ]; then
  for f in /proc/sys/net/ipv4/conf/*/accept_redirects
  do
   echo 0 > $f
  done
fi
if [ -e /proc/sys/net/ipv4/conf/all/send_redirects ]; then
  for f in /proc/sys/net/ipv4/conf/*/send_redirects
  do
   echo 0 > $f
  done
fi
if [ -e /proc/sys/net/ipv4/conf/all/secure_redirects ]; then
  for f in /proc/sys/net/ipv4/conf/*/secure_redirects
  do
   echo 0 > $f
  done
fi


echo 1 > /proc/sys/net/ipv4/conf/all/log_martians
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all
echo 1 > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses
echo 1 > /proc/sys/net/ipv4/ip_forward

NAT (Network Address Translation)

Normally, packets on a network travel from their source to their destination through many different links. None of these links really alter your packet: they just send it onward.

If one of these links were to do NAT, then they would alter the source or destinations of the packet as it passes through. As you can imagine, this is not how the system was designed to work, and hence NAT is always something of a crock. Usually the link doing NAT will remember how it mangled a packet, and when a reply packet passes through the other way, it will do the reverse mangling on that reply packet, so everything works.

Why Would I Want To Do NAT?

In a perfect world, you wouldn't. Meanwhile, the main reasons are:

  • Modem Connections To The Internet

Most ISPs give you a single IP address when you dial up to them. You can send out packets with any source address you want, but only replies to packets with this source IP address will return to you. If you want to use multiple different machines (such as a home network) to connect to the Internet through this one link, you'll need NAT.

This is by far the most common use of NAT today, commonly known as «masquerading» in the Linux world. I call this SNAT, because you change the source address of the first packet.

  • Multiple Servers

Sometimes you want to change where packets heading into your network will go. Frequently this is because (as above), you have only one IP address, but you want people to be able to get into the boxes behind the one with the real IP address. If you rewrite the destination of incoming packets, you can manage this. This type of NAT was called port-forwarding under previous versions of Linux.

  • Transparent Proxying

Sometimes you want to pretend that each packet which passes through your Linux box is destined for a program on the Linux box itself. This is used to make transparent proxies: a proxy is a program which stands between your network and the outside world, shuffling communication between the two. The transparent part is because your network won't even know it's talking to a proxy, unless of course, the proxy doesn't work. Squid can be configured to work this way, and it is called redirection or transparent proxying under previous Linux versions.

The Two Types of NAT

NAT is divided into two different types: Source NAT (SNAT) and Destination NAT (DNAT).

  • SNAT

Source NAT is when you alter the source address of the first packet: i.e. you are changing where the connection is coming from. Source NAT is always done post-routing, just before the packet goes out onto the wire. Masquerading is a specialized form of SNAT.

  • DNAT

Destination NAT is when you alter the destination address of the first packet: i.e. you are changing where the connection is going to. Destination NAT is always done before routing, when the packet first comes off the wire. Port forwarding, load sharing, and transparent proxying are all forms of DNAT.

Examples

Here are our NAT rules as examples.

# echo "Static NAT: Masquerade our Traffic"
# $IPTABLES -t nat -A POSTROUTING -o ppp0 -j MASQUERADE

echo "Static NAT to IP: 213.3.5.17"
$IPTABLES -t nat -A POSTROUTING -o ppp0 -j SNAT --to 213.3.5.17


echo "DNAT Portforwarding: 25 --> 192.168.138.28:25"
$IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 25 -j DNAT --to 192.168.138.28:25

echo "DNAT Portforwarding: 53 --> 192.168.138.20:25"
$IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 53 -j DNAT --to 192.168.138.20:53
$IPTABLES -t nat -A PREROUTING -p udp -i ppp0 --dport 53 -j DNAT --to 192.168.138.20:53

echo "DNAT Portforwarding: 80 --> 192.168.138.21:80"
$IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 80 -j DNAT --to 192.168.138.21:80

Firewall with netfilter/iptables

netfilter and iptables are building blocks of a framework inside the Linux 2.4.x and 2.6.x kernel. This framework enables packet filtering, network address translation and other packet mangling. It is the re-designed and heavily improved successor of the previous Linux 2.2.x ipchains and Linux 2.0.x ipfwadm systems.

netfilter is a set of hooks inside the Linux kernel that allows kernel modules to register callback functions with the network stack. A registered callback function is then called back for every packet that traverses the respective hook within the network stack.

iptables is a generic table structure for the definition of rulesets. Each rule within an IP table consists of a number of classifiers (iptables matches) and one connected action (iptables target).

Main Features

  • stateless packet filtering (IPv4 and IPv6)
  • stateful packet filtering (IPv4)
  • all kinds of network address and port translation (NAT/NAPT)
  • flexible and extensible infrastructure
  • multiple layers of API's for 3rd party extensions
  • large number of plugins/modules kept in 'patch-o-matic' repository

What can I do with netfilter/iptables?

  • build internet firewalls based on stateless and stateful packet filtering
  • use NAT and masquerading for sharing internet access if you don't have enough public IP addresses
  • use NAT to implement transparent proxies
  • aid the tc and iproute2 systems used to build sophisticated QoS and policy routers
  • do further packet manipulation (mangling) like altering the TOS/DSCP/ECN bits of the IP header

An iptable tutortial can be found here.

Stateful Inspection and Connection Tracking

Stateful packet inspection uses the same fundamental packet screening technique that packet filtering does. In addition, it examines the packet header information from the network layer of the OSI model to the application layer to verify that the packet is part of a legitimate connection and the protocols are behaving as expected.

The stateful packet inspection process is accomplished in the following manner. As packets pass through the firewall, packet header information is examined and fed into a dynamic state table where it is stored. The packets are compared to pre-configured rules or filters and allow or deny decisions are made based on the results of the comparison. The data in the state table is then used to evaluate subsequent packets to verify that they are part of the same connection. In short, stateful packet inspection uses a two step process to determine whether or not packets will be allowed or denied. This method can make decisions based on one or more of the following:

  • Source IP address
  • Destination IP address
  • Protocol type (TCP/UDP)
  • Source port
  • Destination port
  • Connection state

The connection state is derived from information gathered in previous packets. It is an essential factor in making the decision for new communication attempts. Stateful packet inspection compares the packets against the rules or filters and then checks the dynamic state table to verify that the packets are part of a valid, established connection. By having the ability to "remember" the status of a connection, this method of packet screening is better equipped to guard against attacks than standard packet filtering.

Stateful packet inspection solutions offer sophisticated decision-making capabilities, yet they operate faster than other packet screening methods because they require little processing overhead. Allow and deny decisions are made at the lower levels of the OSI model.

Some newer stateful packet inspection firewalls maintain more advanced connection state information. Some are able to reassemble the packets as they pass through the firewall and perform additional processing such as content filtering.

Connection States

  • NEW

    This packet is trying to create a new connection. Unless you're running a server you shouldn't allow these on the input side.
     
  • RELATED

    This packet is related to the existing connection, and is passing in the original direction
    .
     
  • INVALID

    his packet doesn't match any connection
     
  • ESTABLISHED

    This packet is part of an existing connection

As a simple example, to forward across the firewall interfaces packets that are part of a pre-existing connection might look like this:

iptables -A FORWARD -m state -state ESTABLISHED,RELATED -j ACCEPT

Installation and Configuration

  1. Download iptables from netfilter.org, on GENTOO use

    emerge iptables
     

  2. Prepare the Kernel

    cd /usr/src/linux
    make menuconfig
    (Enable Network packet filtering in Networking options)
     
  3. Download Firewall Builder from fwbuilder.org

    With this tool, you can build the basic iptables rules. Here are our basic rules.

    #!/bin/sh

    # Akadia AG, Fichtenweg 10, 3672 Oberdiessbach
    # --------------------------------------------------------------------------
    # File:        firewall.fw
    #
    # Autor:       Martin Zahn, 28.07.2005
    #
    # Purpose:     Configuration file IPTABLES Firewall
    #
    # Location:    /home/zahn/iptables
    #
    # Load Rules:  ./firewall.fw
    # Save Rules:  /etc/init.d/iptables save
    #
    # --------------------------------------------------------------------------
    #

    PATH="/sbin:/usr/sbin:/bin:/usr/bin:${PATH}"
    export PATH

    log() {
      echo "$1"
      test -x "$LOGGER" && $LOGGER -p info "$1"
    }

    va_num=1
    add_addr() {
      addr=$1
      nm=$2
      dev=$3

      type=""
      aadd=""

      L=`$IP -4 link ls $dev | head -n1`
      if test -n "$L"; then
        OIFS=$IFS
        IFS=" /:,<"
        set $L
        type=$4
        IFS=$OIFS

        L=`$IP -4 addr ls $dev to $addr | grep inet | grep -v :`
        if test -n "$L"; then
          OIFS=$IFS
          IFS=" /"
          set $L
          aadd=$2
          IFS=$OIFS
        fi
      fi
      if test -z "$aadd"; then
        if test "$type" = "POINTOPOINT"; then
          $IP -4 addr add $addr dev $dev scope global label $dev:FWB${va_num}
          va_num=`expr $va_num + 1`
        fi
        if test "$type" = "BROADCAST"; then
          $IP -4 addr add $addr/$nm dev $dev brd + scope global label $dev:FWB${va_num}
          va_num=`expr $va_num + 1`
        fi
      fi
    }

    getInterfaceVarName() {
      echo $1 | sed 's/\./_/'
    }

    getaddr() {
      dev=$1
      name=$2
      L=`$IP -4 addr show dev $dev | grep inet | grep -v :`
      test -z "$L" && {
        eval "$name=''"
        return
      }
      OIFS=$IFS
      IFS=" /"
      set $L
      eval "$name=$2"
      IFS=$OIFS
    }

    getinterfaces() {
      NAME=$1
      $IP link show | grep ": $NAME" | while read L; do
        OIFS=$IFS
        IFS=" :"
        set $L
        IFS=$OIFS
        echo $2
      done
    }

    LSMOD="lsmod"
    MODPROBE="modprobe"
    IPTABLES="iptables"
    IPTABLES_RESTORE="iptables-restore"
    IP="ip"
    LOGGER="logger"

    getaddr ppp0  i_ppp0

    log 'Activating firewall script'

    echo "Cleanup iptables Rules"
    $IPTABLES -P OUTPUT  DROP
    $IPTABLES -P INPUT   DROP
    $IPTABLES -P FORWARD DROP
    # $IPTABLES -F
    # $IPTABLES -X

    cat /proc/net/ip_tables_names | while read table; do
      test "X$table" = "Xmangle" && continue
      $IPTABLES -t $table -L -n | while read c chain rest; do
          if test "X$c" = "XChain" ; then
            $IPTABLES -t $table -F $chain
          fi
      done
      $IPTABLES -t $table -X
    done

    # echo "Mainly for PPPoE, VPN and DSL (MTU Fix, activate it if you have)"
    # echo "Problems with large Downloads over PPPoE"
    # $IPTABLES -A OUTPUT -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
    # $IPTABLES -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
    #
    echo "Connection Tracking Rules"
    $IPTABLES -A INPUT   -m state --state ESTABLISHED,RELATED -j ACCEPT
    $IPTABLES -A OUTPUT  -m state --state ESTABLISHED,RELATED -j ACCEPT
    $IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
    #
    # echo "NAT: Masquerade our Traffic"
    # $IPTABLES -t nat -A POSTROUTING -o ppp0  -j MASQUERADE

    echo "Static NAT to IP: 213.3.5.17"
    $IPTABLES -t nat -A POSTROUTING -o ppp0 -j SNAT --to 213.3.5.17


    # echo "Portforwarding: 25 --> 192.168.138.28:25"
    $IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 25 -j DNAT --to 192.168.138.28:25

    # echo "Portforwarding: 53 --> 192.168.138.28:25"
    $IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 53 -j DNAT --to 192.168.138.28:53
    $IPTABLES -t nat -A PREROUTING -p udp -i ppp0 --dport 53 -j DNAT --to 192.168.138.28:53


    # echo "Portforwarding: 80,8080,8081 --> 192.168.138.28:80,8080,8081"
    $IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 80 -j DNAT --to 192.168.138.28:80
    $IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 8080 -j DNAT --to 192.168.138.28:8080
    $IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 8081 -j DNAT --to 192.168.138.28:8081


    # echo "Portforwarding: 143 --> 192.168.138.28:143"
    $IPTABLES -t nat -A PREROUTING -p tcp -i ppp0 --dport 143 -j DNAT --to 192.168.138.28:143
    #
    echo "Anti Spoofing Rule"
    $IPTABLES -N ppp0_In_RULE_0
    test -n "$i_ppp0" && $IPTABLES -A INPUT  -i ppp0  -s $i_ppp0  -j ppp0_In_RULE_0
    $IPTABLES -A INPUT  -i ppp0  -s 192.168.138.1  -j ppp0_In_RULE_0
    $IPTABLES -A INPUT  -i ppp0  -s 192.168.138.0/24  -j ppp0_In_RULE_0
    test -n "$i_ppp0" && $IPTABLES -A FORWARD  -i ppp0  -s $i_ppp0  -j ppp0_In_RULE_0
    $IPTABLES -A FORWARD  -i ppp0  -s 192.168.138.1  -j ppp0_In_RULE_0
    $IPTABLES -A FORWARD  -i ppp0  -s 192.168.138.0/24  -j ppp0_In_RULE_0
    $IPTABLES -A ppp0_In_RULE_0   -j LOG  --log-level info --log-prefix "RULE 0 -- DENY "
    $IPTABLES -A ppp0_In_RULE_0   -j DROP

    #
    echo "Loopback (lo) Rules"
    $IPTABLES -A INPUT  -i lo  -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT  -o lo  -m state --state NEW  -j ACCEPT

    #
    echo "Allow the following TCP Ports from Aynwhere"

    $IPTABLES -A OUTPUT  -p tcp -m tcp -m multiport --dports 22,80,443,25,143,8080,8081 \
      -m state --state NEW -j ACCEPT
    $IPTABLES -A INPUT   -p tcp -m tcp -m multiport --dports 22,80,443,25,143,8080,8081 \
      -m state --state NEW -j ACCEPT
    $IPTABLES -A FORWARD -p tcp -m tcp -m multiport --dports 22,80,443,25,143,8080,8081 \
      -m state --state NEW -j ACCEPT

    #
    echo "Allow DNS Zone Transfer only from 62.2.210.211"
    $IPTABLES -A OUTPUT  -p tcp -m tcp -d 62.2.210.211 --dport 53 -m state --state NEW -j ACCEPT
    $IPTABLES -A INPUT   -p tcp -m tcp -d 62.2.210.211 --dport 53 -m state --state NEW -j ACCEPT
    $IPTABLES -A FORWARD -p tcp -m tcp -d 62.2.210.211 --dport 53 -m state --state NEW -j ACCEPT
    #
    echo "Allow DNS Queries"
    $IPTABLES -A OUTPUT  -p udp -m udp  --dport 53 -m state --state NEW -j ACCEPT
    $IPTABLES -A INPUT   -p udp -m udp  --dport 53 -m state --state NEW -j ACCEPT
    $IPTABLES -A FORWARD -p udp -m udp  --dport 53 -m state --state NEW -j ACCEPT

    #
    echo "Allow NTP Time to setup the Date/Time from NTP Server"
    $IPTABLES -A OUTPUT  -p udp -m udp --dport 123 -m state --state NEW -j ACCEPT
    $IPTABLES -A INPUT   -p udp -m udp --dport 123 -m state --state NEW -j ACCEPT
    $IPTABLES -A FORWARD -p udp -m udp --dport 123 -m state --state NEW -j ACCEPT

    #
    echo "HSZ Rules"

    $IPTABLES -A INPUT  -s 192.168.138.0/24  -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT  -s 192.168.138.0/24  -m state --state NEW  -j ACCEPT
    $IPTABLES -A FORWARD  -s 192.168.138.0/24  -m state --state NEW  -j ACCEPT

    #
    echo "Logging Rules"
    $IPTABLES -N RULE_2
    $IPTABLES -A OUTPUT  -j RULE_2
    $IPTABLES -A INPUT  -j RULE_2
    $IPTABLES -A FORWARD  -j RULE_2
    $IPTABLES -A RULE_2  -j LOG  --log-level info --log-prefix "RULE 2 -- DENY "
    $IPTABLES -A RULE_2  -j DROP
    #
    echo "Activate Routing"
    echo 1 > /proc/sys/net/ipv4/ip_forward
     
  4. Load and Save the Rules

    ./firewall.fw
    /etc/init.d/iptables save

    The iptables rules are saved and automatically loaded when the machine is booting the next time. The location of the saved rules are defined in /etc/conf.d/iptables (/var/lib/iptables/rules-save) for GENTOO linux.

Useful iptables Commands

iptables -L          (List Rules)
iptables -t nat -L   (List NAT Rules)

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DNAT       tcp  --  anywhere             anywhere            tcp dpt:smtp to:192.168.138.28:25
DNAT       tcp  --  anywhere             anywhere            tcp dpt:domain to:192.168.138.20:53
DNAT       udp  --  anywhere             anywhere            udp dpt:domain to:192.168.138.20:53
DNAT       tcp  --  anywhere             anywhere            tcp dpt:www to:192.168.138.21:80

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
SNAT       all  --  anywhere             anywhere            to:213.3.5.17

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

iptables -F    (Delete all rules)
iptables -X    (Delete all userdefined chains)