Iptables and Virtual Networks

I’ve been messing around with Networks lately. DHCP servers, Iptables, DNS, FTP, SSH, HTTP, and on. The best way to do it that I’ve found is through virtual machines. That way you can monitor everything all from one computer. Personally I’ve gone with a lightweight Fedora 8 install on VMWare. This post will be about Iptables, so for the network configuration I used two custom networks, both host-only.

I started by making three machines. One represented the private network (192.168.0.0/24), one represented the gateway/firewall, and one represented the public network (10.0.0.0/8). I then configured the machines so that the private network could access the public via only http, ping, and ftp. The public could access the private only via http and ssh, which were configured to map to a specific node (or web server). The gateway itself could access both public and private networks with ping and ssh, but could only be accessed via ssh by the private network. The forwarding was handled with NAT, or Network Address Translation.

Network Setup

Here’s what my network looks like. The words in all caps are variables that I use in my Iptables configuration file. I’ll include that at the end.

Software Packages

  • -Wireshark-gnome
  • -Vsftpd
  • -And the other common packages such as Apache

Quick Overview of Iptables

I’m not going to talk so much about what Iptables as I am about how to implement them. Because of that I’ll only give a quick, high level view of how they work. If this is your first time looking at Iptables I recommend reading something else to give you a little more background information… but this might be enough if you’re a learn by doing kind of person.

When packets arrive at the firewall, they’re processed and checked to see if the NAT (network address translation) table needs to translate them. This is because sometimes a server sits behind a firewall on a private network but needs a presence in the public internet. It uses the gateway’s public address, but the gateway has to know to forward those on. NAT does that.

It’s important that happen first because then the packets are processed. The packets being forwarded are added to the FORWARD chain. The packets meant for the gateway enter the INPUT chain. The packets ready to leave the gateway (not forwarded from the gateway, but originating from the gateway) go into the OUTPUT chain.

Each chain can have rules added to it (-A) and they are all processed sequentially. A packet will continue through the rules until it is accepted (ACCEPT) or dropped (DROP). Along the way it is common to see jump commands (-j) sending types of packets to rules meant to process together.

If the packet is accepted it reaches the firewall lets it through to its destination. DROP makes the packet go *poof, and REJECT sends a nice message to the sender of the packet that their communication has not been accepted so they don’t wait with the little spinner on their browser going until it times out.

Ok enough of the high level, lets get to the implementation.

Description

I used the whitelist approach, which is the most secure. This means that by default I drop all packets, only accepting those on my whitelist. (Blacklist is accepting all by default and marking packets to drop – not much of a firewall). During this description I’ll type relevant lines from my Iptables file and then include it in completed form at the end.

As I mentioned before I set up the network with two host-only networks (simulated switches). I set the private network to have the static IP 192.168.0.1 with the subnet mask 255.255.255.0 and the GW 192.168.0.254. The public network’s IP was 10.0.0.1 with a subnet mask 255.0.0.0 and a GW of 10.0.0.254 (in Fedora 8 setting this up is as simple as typing neat& in the command line and then adding a network adapter with the IP as static). The gateway had two ports (one to connect to the public network and one to connect to the private), which I matched to their respective pairs (public side to the public server, etc) so they’d be on the same switch. One was 192.168.0.254 with the same subnet mask as its pair (255.255.255.0) and itself as the gateway. The other followed the same pattern with its IP as 10.0.0.254.

After the network was ready I started working on my firewall. I set up the default drops like so:

$IPTABLES -P INPUT DROP
$IPTABLES -P OUTPUT DROP
$IPTABLES -P FORWARD DROP

FORWARDING and NAT

Once that was done I got to work on my first task. This was all about forwarding traffic correctly, and to save myself time later I set up the NAT (Network address translation. This setting makes it so my server changes my address so that whichever computer I’m talking to on the internet knows to send messages to it, and then it sends them to me). My settings were:

$IPTABLES -t nat -A PREROUTING -p tcp -d $LAN_0_IP --dport 80 -j DNAT --to $WEB_IP_ADDRESS:80
$IPTABLES -t nat -A POSTROUTING -s $INTERNAL_PRIVATE_SUBNET -o $LAN_0_IFACE -j SNAT --to $LAN_0_IP

For now making sense of this should be easy just referring to the network image above (again the variables are at the end of this post). The important point is that for pre-routing, when the destination was the public side of the gateway and was HTTP (port 80), it was forwarded to a node in the private network. Post routing took as a source the internal network’s IP address range and converted it as it left the public interface and gave it the gateway’s public address.

-A adds the chain, -p is the protocol, -d the destination –dport the destination port, –sport would be source port, -j is where to jump in the chain (this case jumps to DNAT – destination network address translation), -o is the output interface, -s the source, -t the table. This isn’t an exhaustive list, a good reference for a more in-depth look at the commands can be found here: http://www.dd-wrt.com/wiki/index.php/Iptables_command.

There will be screenshots showing this in motion soon. For now it lay the foundation so I could do task 1 without worrying about having to change anything later.

My next step was to enable HTTP, FTP, and ping from private to public. I did this by adding a new set of rules (-N) for TCP packets and adding rules (-A) allowing them:

$IPTABLES -N tcp_packets
$IPTABLES -N allowed
$IPTABLES -A tcp_packets -p TCP -s $INTERNAL_PRIVATE_SUBNET -m multiport --dport 80,20,21 -j allowed
$IPTABLES -A allowed -p TCP --syn -j ACCEPT
$IPTABLES -A allowed -p TCP -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A allowed -p TCP -j DROP

These rules take TCP packets that originate from the private subnet with FTP (20 & 21) and HTTP (80) ports and allow them to be forwarded. They also allow communications that have been established or are created by established communications (-m state –state ESTABLISHED,RELATED >side note make sure there’s no space between ESTABLISHED,RELATED). All that’s left to do is attach the rules to the forwarding chain (since they’re not for the gateway) like so:

$IPTABLES -A FORWARD -p TCP -j tcp_packets
$IPTABLES -A FORWARD -p TCP -m state --state ESTABLISHED,RELATED -j ACCEPT

The second line allowed return traffic (for both ftp and http) once it had been initiated.

In order to get ping working I created another chain for ICMP packets:

$IPTABLES -N icmp_packets
$IPTABLES -A icmp_packets -p ICMP --icmp-type 0 -j ACCEPT
$IPTABLES -A icmp_packets -p ICMP --icmp-type 8 -j ACCEPT
$IPTABLES -A FORWARD -p ICMP -j icmp_packets

These accept all 8 (ping) and 0 (response) ICMP requests. With that I had FTP, HTTP, and ping ready to go so I started my firewall by typing “service iptables start”, and then I executed my script (in /etc/rc.d/) named rc.firewall by typing ./rc.firewall. That got the first bit working, below are screen shots for each scenario:

FTP Example

Here the private address 192.168.0.1 successfully gets a login to 10.0.0.1’s FTP server.

HTTP Example

Here the ifconfig shows that our private network (192.168.0.1) successfully pulls up an HTML page from 10.0.0.1 via HTTP.

Ping Example

Again ifconfig shows a private address able to ping a public one.

FORWARDING – Disable Ping and FTP from Public

For my next task I disabled ping and FTP from the public network to the private.

I did ping by simply denying anyone outside from sending an 8 (ping) message into the network (this is a blacklist technique):

$IPTABLES -A icmp_packets -p icmp -d $INTERNAL_PRIVATE_SUBNET --icmp-type 8 -j REJECT

FTP was already denied by default (b/c of my blacklist policy). Below I have screen shots of both of these scenarios working. In the second I get the information about the packet source, destination, and content by using wireshark. So check it out if you haven’t yet.

Ping Disabled

Here the ifconfig command shows we are at 10.0.0.1, and its ping command returns that the destination port is unreachable. This is because it is not allowed to communicate.

FTP Disabled

As you can see above, the source is my public IP - 10.0.0.1, with the destination FTP never getting or responding to the SYN message.

If you’re not familiar with TCP protocols like FTP/HTTP etc, there’s usually a three way handshake where one party initiates by sending a [SYN] message, another acknowledges by sending [SYN,ACK], and then it is finalized with an [ACK] message. I don’t cover those here either but if you want to know more about the contents of these headers, I recommend looking at them with wireshark (a program that lets you inspect packets), and reading the information at these links: TCP header , and IP header (for both section 3 gives the specifications of the header).

NAT – HTTP Traffic

HTTP traffic needed to be enabled from public to private and it needed to map to an internal node. As I mentioned previously, I already implemented NAT which does this mapping for us. With the settings above it mapped to the internal node 192.168.0.1.

In order to enable the HTTP traffic I added another line to tcp_packets:

1
$IPTABLES -A tcp_packets -p TCP -d $WEB_IP_ADDRESS --dport 80 -j allowed

This allowed HTTP packets destined for my internal node (which $WEB_IP_ADDRESS was set to).

In order to demonstrate this working, first I turned off iptables and accessed my Gateway’s HTTP server. The address was 10.0.0.254 and the html page (from the apache server) said “I am the GW”:

The Gateway for 10.0.0.254

Here the gateway's html is showing for 10.0.0.254


Next I turn my iptables back on, and attempt to access the same address from the same computer. Now the packet is forwarded on and I get my private server’s web page:
Private network now

10.0.0.254 is now shows the private network


Even though the private server’s actual IP address is 192.168.0.1, it displays because it has been correctly mapped with NAT.

INPUT – Enable SSH

Next I wanted to enable ssh connections to the firewall from both public and private networks, but have the access from the public side go to the inner node again.

These rules need to attach to the INPUT chain as well as the FORWARD, because in one case the SSH is destined for the Gateway. I used the following command in order to allow this communication:

1
2
$IPTABLES -A INPUT -p TCP -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A INPUT -p TCP -i $LAN_1_IFACE -s $LAN_1_IP_RANGE --dport 22 -j ACCEPT

The first command allows all established communication to continue. The second takes the private network’s range and allows it to connect to the private side of the gateway.

In order to get the public side to connect to the private network again, I simply added another entry to the forwarding chain:

1
$IPTABLES -A tcp_packets -p TCP -d $WEB_IP_ADDRESS --dport 22 -j allowed

This connection is shown below:

SSH to Gateway

SSH to gateway from the public side


As you can see the communication was initiated by 10.0.0.1, the public side, and its destination was 10.0.0.254, its side of the gateway. But after logging in with the ssh, typing ifconfig shows that the address logged into is in fact 192.168.0.1, the private server.
SSH from Private

SSH from private to gateway


Here the SSH is initiated by the private network (192.168.0.1) and it connects to 192.168.0.254. Both addresses of the gateway came up after typing ifconfig, but in the picture only 10.0.0.254 is evident. Also notice that wireshark has SSH as the protocol.

OUTPUT – From the Firewall

Next I wanted to enable both SSH and ping from the firewall to public networks. Because they originate with the gateway, we add their entries to the output chain:

1
2
3
4
5
$IPTABLES -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A OUTPUT -p ICMP -j ACCEPT
$IPTABLES -A OUTPUT -p TCP -j tcp_packets
$IPTABLES -A tcp_packets -p TCP -s $LAN_1_IP --dport 22 -j allowed
$IPTABLES -A tcp_packets -p TCP -s $LAN_0_IP --dport 22 -j allowed

The first line again allows established communication to continue, while the second allows pings to be sent from the Gateway. The last three lines say that when originating from the gateway, TCP and SSH packets are allowed.

NAT in Action

This last bit is just to show a little bit of Wireshark and a little bit of NAT in action.

Inbound packets shown with wireshark

Inbound Packets Changed


Here you see the public address 10.0.0.1 accessing 10.0.0.254, the Public side of the Gateway. That address is then changed as we move over to the interface on the Private side of the Gateway, and it becomes 192.168.0.1 (the correct destination of the internal client).

The opposite also works:

Outbound packets shown with wireshark

Outbound Packets Changed

Last Words and Appendix

This is a very simple implementation, but Iptables can be much more sophisticated. Maybe at some point in the future I’ll do a part 2 with some of those features. Here’s the configuration file I promised:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# Static IP address of public network
#
LAN_0_IP="10.0.0.254"
LAN_0_IP_RANGE="10.0.0.0/8"
LAN_0_BCAST_ADRESS="10.255.255.255"
LAN_0_IFACE="eth5"
#
# 1.2 Local area network configuration.
#
#
#
# Internal firewall private side
#
LAN_1_IP="192.168.0.254"
LAN_1_IP_RANGE="192.168.0.0/24"
LAN_1_BCAST_ADRESS="192.168.0.255"
LAN_1_IFACE="eth6"
#
# IP aliases
#
LO_IFACE="lo"
LO_IP="127.0.0.1"
WEB_IP_ADDRESS="192.168.0.1"
INTERNAL_PRIVATE_SUBNET="192.168.0.0/24"
#
# 1.3 IPTables Configuration.
#
IPTABLES="/sbin/iptables"
#
###########################################################################
#
# 2. Module loading.
#
#
# Needed to initially load modules
#
/sbin/depmod -a	 
#
# flush iptables
#
/sbin/iptables -F 
/sbin/iptables -F -t nat
#
# 2.1 Required modules
#
/sbin/modprobe ip_tables
/sbin/modprobe ip_conntrack
/sbin/modprobe iptable_filter
/sbin/modprobe iptable_mangle
/sbin/modprobe iptable_nat
/sbin/modprobe ipt_LOG
/sbin/modprobe ipt_limit
/sbin/modprobe ipt_state
#
# 2.2 Non-Required modules
#
#/sbin/modprobe ipt_owner
#/sbin/modprobe ipt_REJECT
#/sbin/modprobe ipt_MASQUERADE
#/sbin/modprobe ip_conntrack_ftp
#/sbin/modprobe ip_conntrack_irc
#/sbin/modprobe ip_nat_ftp
#
###########################################################################
#
# 3. /proc set up.
#
#
# 3.1 Required proc configuration
#
#
# Enable ip_forward, this is critical since it is turned off as defaul in 
# Linux.
#
echo "1" > /proc/sys/net/ipv4/ip_forward
#
# 3.2 Non-Required proc configuration
#
#
# Dynamic IP users:
#
#echo "1" > /proc/sys/net/ipv4/ip_dynaddr
#
###########################################################################
#
# 4. rules set up.
#
#
# The kernel starts with three lists of rules; these lists are called firewall
# chains or just chains. The three chains are called INPUT, OUTPUT and FORWARD.
#
# The chains are arranged like so:
#
#                     _____
#                    /     \
#  -->[Routing ]--->|FORWARD|------->
#     [Decision]     \_____/        ^
#          |                        |
#          v                       ____
#         ___                     /    \
#        /   \                   |OUTPUT|
#       |INPUT|                   \____/
#        \___/                      ^
#          |                        |
#           ----> Local Process ----
#
# 1. When a packet comes in (say, through the Ethernet card) the kernel first 
#    looks at the destination of the packet: this is called `routing'.
# 2. If it's destined for this box, the packet passes downwards in the diagram, 
#    to the INPUT chain. If it passes this, any processes waiting for that 
#    packet will receive it. 
# 3. Otherwise, if the kernel does not have forwarding enabled, or it doesn't 
#    know how to forward the packet, the packet is dropped. If forwarding is 
#    enabled, and the packet is destined for another network interface (if you 
#    have another one), then the packet goes rightwards on our diagram to the 
#    FORWARD chain. If it is ACCEPTed, it will be sent out. 
# 4. Finally, a program running on the box can send network packets. These 
#    packets pass through the OUTPUT chain immediately: if it says ACCEPT, then 
#    the packet continues out to whatever interface it is destined for. 
#
######
# 4.1 Filter table
#
#
# 4.1.1 Set policies
#
#
# Set default policies for the INPUT, FORWARD and OUTPUT chains
#
$IPTABLES -P INPUT DROP
$IPTABLES -P OUTPUT DROP
$IPTABLES -P FORWARD DROP
# 
##################################################
# Create custom chains
##################################################
#
$IPTABLES -N allowed
$IPTABLES -N tcp_packets
$IPTABLES -N icmp_packets
#
#
# allowed rules
# new connections, established ones
$IPTABLES -A allowed -p TCP --syn -j ACCEPT
$IPTABLES -A allowed -p TCP -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A allowed -p TCP -j DROP
#
# TCP rules
# 
$IPTABLES -A tcp_packets -p TCP -s $INTERNAL_PRIVATE_SUBNET -m multiport --dport 20,21,80 -j allowed
$IPTABLES -A tcp_packets -p TCP -d $WEB_IP_ADDRESS -m multiport --dport 22,80 -j allowed
$IPTABLES -A tcp_packets -p TCP -s $INTERNAL_PRIVATE_SUBNET -d $LAN_1_IP --dport 22 -j allowed
$IPTABLES -A tcp_packets -p TCP -s $LAN_1_IP --dport 22 -j allowed
$IPTABLES -A tcp_packets -p TCP -s $LAN_0_IP --dport 22 -j allowed
#
# ICMP rules
#
$IPTABLES -A icmp_packets -p icmp -d $INTERNAL_PRIVATE_SUBNET --icmp-type 8 -j REJECT
$IPTABLES -A icmp_packets -p icmp --icmp-type 0 -j ACCEPT
$IPTABLES -A icmp_packets -p icmp --icmp-type 8 -j ACCEPT
#
##############################################
# INPUT chain
###############################################
# 
$IPTABLES -A INPUT -p TCP -m state --state ESTABLISHED,RELATED --sport 22 -j ACCEPT
$IPTABLES -A INPUT -p TCP -i $LAN_1_IFACE -s $LAN_1_IP_RANGE --dport 22 -j ACCEPT
$IPTABLES -A INPUT -p ICMP --icmp-type 0 -j ACCEPT
#
##############################################
# FORWARD chain
#############################################
#
$IPTABLES -A FORWARD -p TCP -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A FORWARD -p TCP -j tcp_packets
$IPTABLES -A FORWARD -p ICMP -j icmp_packets
#
#############################################
# OUTPUT chain
#############################################
#
$IPTABLES -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A OUTPUT -p TCP -j tcp_packets
$IPTABLES -A OUTPUT -p ICMP -s $LO_IP -j ACCEPT
$IPTABLES -A OUTPUT -p ICMP -j ACCEPT
#
############################################
# NAT routing
############################################
#
# Prerouting
#
$IPTABLES -t nat -A PREROUTING -p tcp -d $LAN_0_IP --dport 80 -j DNAT --to $WEB_IP_ADDRESS:80
$IPTABLES -t nat -A PREROUTING -p tcp -d $LAN_0_IP --dport 22 -j DNAT --to $WEB_IP_ADDRESS:22
#
# POSTROUTING chain.
#
$IPTABLES -t nat -A POSTROUTING -s $INTERNAL_PRIVATE_SUBNET -o $LAN_0_IFACE -j SNAT --to $LAN_0_IP
#
#

Leave a Reply

Spam protection by WP Captcha-Free