This chapter introduces some of the basic networking concepts and the methods to utilize them on Linux systems. It provides us with a good base to build upon. We're going to start with two computers connected with a single network cable and work our way from there to configure a router to connect your network to the Internet.
Routers are devices that are configured to span multiple networks and forward packets between them as needed. They also perform Network Address Translation (NAT) in order to allow your private network to share a single public IPv4 address.
Before we start configuring the networking within Linux, we need to physically connect the systems. The simplest configuration involves connecting the two computers with a single cable, although connecting them to a switch may make more sense for additional expansion. Once physically connected, we need to confirm that they are working as expected.
On each Linux system, use the ip
command to check for a network link as shown:
# ip link set dev eth0 up # ip link show eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether 00:0c:29:6e:8f:ab brd ff:ff:ff:ff:ff:ff
Some people may choose to use ethtool
, mii-tool
, or mii-diag
to perform the same action.
Make sure to run the same command on both the systems, especially if you're connecting to a switch rather than directly connecting the two systems.
The first command brings up the network interface card (NIC). This activates the interface and allows it to start the process to check for a network link or electrical connection between the two systems.
Next, the show
command gives you a bunch of information about the link. You should see a state showing UP. If it shows DOWN, then you have a link issue of some sort. This could be a disconnected/bad cable, a bad switch, or you forgot to bring up the network interface.
Now that we've established a link between the machines, let's put some IP addresses on the systems so that we can communicate between them. For now, let's look at manually configuring IP addresses rather than auto-configuring them via DHCP.
We need to manually configure the IP addresses using the ip
command. Let's start with server 1:
# ip addr add dev eth0 10.0.0.1/24 # ip addr list eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:6e:8f:ab brd ff:ff:ff:ff:ff:ff inet 10.0.0.1/24 brd 192.168.251.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::20c:29ff:fe6e:8fab/64 scope link valid_lft forever preferred_lft forever
Now we need to perform the same action on server 2, but with 10.0.0.2/24 instead of 10.0.0.1/24.
There are a few things in play here, so it probably makes sense to go through them one at a time.
First, let's start off by looking at the IP address that we're configuring. The 10.0.0.1 and 10.0.0.2 are a part of a series of netblocks set aside for private networks by RFC1918, IP Address Allocation for Private Internets. RFC1918 sets aside three large ranges, 10.0.0.0-10.255.255.255 (10.0.0.0/8), 172.16.0.0-172.31.255.255 (172.16.0.0/12), and 192.168.0.0-192.168.255.255 (192.168.0.0/16).
For our purpose, we're configuring 10.0.0.1/24, which is an IP range that includes 10.0.0.0-10.0.0.255. This includes 256 addresses, of which 254 are usable after setting aside 10.0.0.0 as the network address and 10.0.0.255 as the broadcast address. Both our systems get one IP in that range, which should allow them to communicate between them.
Next, we use the ip
command to define an address on the eth0
device using one of the IP addresses in that range. You need to make sure that each machine in that range has a different IP address in order to prevent IP address conflicts, which would make communication between the two systems impossible and communication with different systems difficult.
Some people may be accustomed to seeing the ifconfig
command rather than the ip
command used here. While it will certainly do the job in most cases, net-tool (and its ifconfig
command) has been deprecated by most distributions since the turn of the century, in favor of iproute2
and its ip
command.
Once the commands have been run on both servers, you should be able to ping them from each other. Log in to 10.0.0.1 and run the following:
# ping –c 2 –n 10.0.0.2
If everything is configured properly, you will be able to see successful ping responses at this point.
In the previous section we configured the network interface, but this configuration is only valid while the system is up and running. A reboot will clear this configuration, unless you take steps to make sure that it is configured on each boot. This configuration will be specific to the distribution that you are running, although most distributions fall under either the Debian or Red Hat methods.
Let' see how it works in Debian/Ubuntu:
Add
eth0
configuration to/etc/network/interfaces
:auto eth0 iface eth0 inet static address 10.0.0.1 netmask 255.255.255.0
Bring up the network interface:
# ifup eth0
Let' see how it works in Red Hat/CentOS:
Add the
eth0
configuration to/etc/sysconfig/network-scripts/ifcfg-eth0
:DEVICE=eth0 BOOTPROTO=none ONBOOT=yes NETWORK=10.0.0.0 NETMASK=255.255.255.0 IPADDR=10.0.0.1 USERCTL=no
Bring up the network interface:
# ifup eth0
Linux distributions are configured through init
systems, such as Upstart, SystemD, or SysVInit. During the initialization process, the interfaces, or ifcfg-eth0
files, are used as a configuration for the networking setup scripts. These scripts then use the same ip
commands, or possibly ifconfig
commands to set up and bring up the network interface.
For our next step, we're going to add a second interface to server 1. In addition to 10.0.0.1/24 being configured on eth0
, we're going to configure 192.168.0.1/24 on eth1
. The second interface could just as easily be 10.0.1.1/24, but let's make sure that the networks are obviously different.
The systems should be configured similar to Figure 1:

Configure the network interface on
eth1
on server 1:# ip link set dev eth1 up # ip addr add dev eth1 192.168.0.1/24 # ip addr list eth1 3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:99:ff:c1 brd ff:ff:ff:ff:ff:ff inet 192.168.0.1/24 scope global eth1 valid_lft forever preferred_lft forever inet6 fe80::20c:29ff:fe99:ffc1/64 scope link valid_lft forever preferred_lft forever
Connect your third system to
eth1
on server 1.Configure
eth0
on server 3 with an IP address of 192.168.0.2:# ip link set dev eth0 up # ip addr add dev eth0 192.168.0.2/24 # ip addr list eth1 3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:99:ff:c2 brd ff:ff:ff:ff:ff:ff inet 192.168.0.2/24 scope global eth1 valid_lft forever preferred_lft forever inet6 fe80::20c:29ff:fe99:ffc1/64 scope link valid_lft forever preferred_lft forever
Add a default route on server 3:
# ip route add default via 192.168.0.1
Enable routing on server 1:
# echo net.ipv4.ip_forward=1 > /etc/sysctl.conf # sysctl -p /etc/sysctl.conf
Add a default route on server 2:
# ip route add default via 10.0.0.1
When you configure an IP address on a Linux system, you automatically have a route defined, which states that in order to access another IP address in the same subnet, you should use 0.0.0.0 as your gateway. This tells the IP stack that the system, if it exists, will be on the same layer as the two network segments, and that it should use ARP to determine the MAC address it should communicate with.
If you want to talk to a machine outside of that subnet, the system will need to know how to communicate with it. This is done by defining a route with a gateway IP address that you forward the packet to. You then depend on the gateway system to forward the packet to the correct destination.
Most commonly, you'll deal with a default route, which is the route that the system uses for any packet that is not deemed to be local. In our configuration, we tell the system that the default route is 192.168.0.1, which asks us to forward any non-local packets to an IP address configured on our server 1 box. This means that server 1 will act as our router.
You can also define more specific routes where you can explicitly define an IP address to forward packets to a specific IP address or subnet. This can be useful in a network where one router provides access to the Internet and a second router provides access to a second internal network.
At this point server 3, configured as 192.168.0.2, knows that IP addresses on 192.168.0.0/24 are local and any other packet should be sent to 192.168.0.1 in order to be forwarded. However, if you attempt to ping a system that is outside your local network (for example 10.0.0.2), it will not arrive. This is because routing on Linux systems is disabled by default and needs to be enabled on server 1 before it can forward packets. Enabling routing can be done by setting /proc/sys/net/ipv4/ip_forward
to 1, or via sysctl
, which is the manner in which we've chosen to set it.
Once routing is enabled, packets addressed from server 3 will be received by your router and forwarded to 10.0.0.2 (server 2) via eth0
on the router. 10.0.0.2 will receive the packet from your router and promptly attempt to respond. This response will fail, as server 2 does not have a defined route for accessing the 192.168.0.1/24 network. This is fixed by adding a default route on server 2 as well, but sending to the router's interface on the 10.0.0.0/24 network.
Now server 3 knows how to address server 2, server 2 knows how to address server 3, and server 1 routes packets between the two as needed. Congratulations, you have connected two networks.
Connecting two local networks is useful, but these days it's more common to want to connect a local network to the Internet. The basic concept works the same, but with the necessary addition of NAT. NAT rewrites your packet headers in order to make them appear as if they come from your router, thus effectively hiding your system's address from the destination.
Create a third NIC (eth2
) on server 1 and connect it to your cable modem or ISP's router.
Configure
eth2
to receive an IP address via DHCP:auto eth2 iface eth2 inet dhcp
Use
iptables
to enable NAT on packets heading out througheth2
:# /sbin/iptables -t nat -A POSTROUTING -o eth2 \ -j MASQUERADE # /sbin/iptables -A FORWARD -i eth2 -o eth0 -m \ state --state RELATED,ESTABLISHED -j ACCEPT # /sbin/iptables -A FORWARD -i eth0 –o eth2 -j ACCE PT
In the last section, we discussed how in order for two systems on different networks to be able to talk to each other, they need to have routes defined which will forward packets to a router that can deliver the packet to the appropriate destination. The same is true on the Internet.
If server 2 attempts to contact an IP address on the Internet, for example Google's nameserver at 8.8.8.8, your router will pass them onto the destination. Let's give that a try:
# ping -c 2 8.8.8.8 PING 8.8.8.9 (8.8.8.8) 56(84) bytes of data. --- 8.8.8.8 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 999ms
No responses were received. So what went wrong here?
You'll recall that I said the IP addresses that we were using were defined by RFC1918 as internal IP address space. Due to this, these IP addresses are not directly usable as Internet hosts. In our example, one of the two following things will happen:
Our router will send packets out of the Internet-facing interface, where it will travel across the Internet to our destination, which will respond to the packet. The response will not be able to find its way back to our system due to the un-routed nature of the destination.
Our router will send the packets out of its Internet-facing interface, where the next hop will drop the packets due to implementing egress filtering of traffic with invalid source addresses.
Iptables is a command-line tool in Linux for interfacing with the Linux kernel firewall, which is implemented as a part of the netfilter subsystem.
Let's break down the first command line:
The
-t
option specifies the packet matching table to use. In this case, we're going to use thenat
table.f-A
indicates that the specified rule should be appended to the selected chain, which in this case is POSTROUTING. The POSTROUTING chain is processed after the kernel handles packet routing.-o
specifies the output interface. In our example, theeth0
interface contains the internal IP systems andeth2
leads to the Internet.-j
specifies what to do if the packet matches the rule. In this case, we're going to masquerade the packet (modify the IP).
Put them together and we have matching packets heading out on eth2
; rewrite the source IP address and track it in the NAT table.
The second command is added in the -m
command, which matches a packet property, in this case state
. For the packets that came in on eth1
(from the Internet), and destined to eth0
(lan
), check to see if they are related to or are a part of an established connection. If so, accept the packet and assign it to the FORWARD chain. The FORWARD chain handles any packet that is being passed through your router rather than the packets originating from the system (OUTPUT chain) or packets destined to your system (INPUT chain).
Finally, any packets that come in on eth0
(lan
) and are heading out on eth2
(Internet
) are just automatically accepted.
You now have a router that provides Internet access to all systems behind it, but the systems behind it need to be manually configured with IP addresses while avoiding conflicts. You also need to configure them with DNS servers for resolving host information. To solve this, we're going to configure a DHCP server on your router to be responsible for handing out addresses.
Dynamic Host Configuration Protocol (DHCP) allows you to centralize your IP address management. Machines which are added to a network will issue a DHCP request asking any available DHCP server to provide it with configuration information including IP address, subnet mask, gateway, DNS server, and so on.
Let's set up DHCP in Debian/Ubuntu:
Install a DHCP server:
# sudo apt-get install isc-dhcp-server
Modify
/etc/default/isc-dhcp-server
to add the interface which you should serve requests on:# sudo sed –i "s/^INTERFACES.*/INTERFACES="eth0"\ /etc/default/isc-dhcp-server
Modify
/etc/dhcp3/dhcpd.conf
to configure the network information you want to serve:ddns-update-style none; option domain-name "example.org"; option domain-name-servers 8.8.8.8, 8.8.4.4; default-lease-time 600; max-lease-time 7200; authoritative; subnet 10.0.0.0 netmask 255.255.255.0 { range 10.0.0.10 10.0.0.100; option routers 10.0.0.1; }
Let's set up DHCP in Red Hat/CentOS
Install a DHCP server:
# sudo yum install dhcp
Modify
/etc/dhcp/dhcpd.conf
to configure the network information you want to serve:ddns-update-style none; option domain-name "example.org"; option domain-name-servers 8.8.8.8, 8.8.4.4; default-lease-time 600; max-lease-time 7200; authoritative; subnet 10.0.0.0 netmask 255.255.255.0 { range 10.0.0.10 10.0.0.100; option routers 10.0.0.1; }
The first thing you might notice about the difference between Debian-and Red Hat-based systems is that in Debian-based systems, you need to explicitly define the interfaces to listen on, while this is not needed on Red Hat systems. This is because Red Hat has chosen to trust ISC DHCP's built-in restriction to only listen on interfaces that have an IP address in the same subnet as DHCP leases were set up for.
Let's look at the configuration for the DHCP server itself.
The first section defines the global configuration parameters:
ddns-update-style
: This defines optional functionality to update a DNS server with hostnames for the machines in your network. We'll look at this option in detail later in the book.option domain-name
: This defines the domain name for your network. On Linux DHCP clients, this populates the search field that specifies the domain to search in for non-fully qualified domain names.option domain-name-servers
: This specifies the default DNS servers, which your clients should use for domain resolution. In this example, we've used Google's public nameserver, but you may instead want to use your ISP's nameservers or a different public service.Max-lease-time and default-lease-time
: This defines how many seconds the IP address can dedicate to the requesting machine. Clients can also request for a specific lease length. Max-lease-time puts a cap on how long they can request it for, while default-lease-time comes into play if they don't request a specific lease length. Longer leases cut down on the number of IP address changes you may experience, while shorter leases make sure that you don't run out of IP addresses if you have a lot of short-term users on the network.authoritative directive
: This tells the DHCP server that it is the authority for this particular network. Sometimes, clients that have recently had a lease on another network may attempt to re-request the same IP address. An authoritative server may send them a DHCPNAK (negative acknowledgement) to tell them that they must request a new IP address. If your DHCP server is not the only one on the network, you may set it as not authoritative in order to avoid this behavior.
The second section is the subnet declaration. Your DHCP server must know about all the subnets configured on the interface that it has been told to serve DHCP addresses on. For the subnets on which it should serve addresses, you should define the range of IPs to hand out and you most likely want to define your network gateway as well. If your machine has multiple IP addresses on the interface and you only want to serve IPs to one of them, you should still define the subnet, but leave out the range and gateway information from within the brackets. For example:
subnet 10.0.0.0 netmask 255.255.255.0 { }
Now that your DHCP server is configured, it will automatically hand out the IP addresses to all machines that connect to the network which are configured to request addresses via the DHCP protocol, which is often the default. It will keep track of these leases in a human-readable format in /var/lib/dhcpd/dhcpd.leases
, in order to avoid having multiple machines receive the same address.
We touched upon iptables
a little while discussing NAT, but now we're going to go a bit deeper into configuring a secure firewall for your network.
A properly configured firewall should be configured in a default deny configuration with specific allows (Whitelist) for what you want to accept:
# iptables -A INPUT -i lo -j ACCEPT # iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # iptables -A INPUT -p tcp --dport 22 -j ACCEPT # iptables -P INPUT DROP # iptables -P FORWARD DROP # iptables -P OUTPUT ACCEPT # iptables -A FORWARD -i eth0 -j ACCEPT # iptables -t nat -A POSTROUTING -o eth2 \ -j MASQUERADE # iptables -A FORWARD -i eth2 -o eth0 -m \ state --state RELATED,ESTABLISHED -j ACCEPT # iptables -A FORWARD -i eth0 -j ACCEPT
We start off by setting an ACCEPT policy on traffic destined to the local system on the localhost adapter (-i lo
). A lot of software expects to be able to communicate over the localhost adapter, so we want to make sure we don't inadvertently filter it.
Next, we set an ACCEPT policy for any packet that matches a state (-m
state) of ESTABLISHED, RELATED. This command accepts packets which are a part of, or related to, an established connection. ESTABLISHED should be pretty straightforward. RELATED packets are special cases that the connection tracking module needs to have a prior knowledge of. An example of this is the FTP protocol operating in active mode. The FTP client in active mode connects to the server on port 21. Data transfers however, operate on a second connection originating from the ftp server from port 20. During the operation of the connection, the port 21 packets are ESTABLISHED, while the port 20 packets are RELATED.
The third command accepts any TCP connection (-p tcp
) destined for port 22 (--dport 22
) that is destined for your system. This rule allows you to SSH into the machine.
Next, we will define the default policies. By default, we drop packets on the INPUT and FORWARD chains and accept packets on the OUTPUT chain.
The last three lines you'll recognize from the NAT section, which tells the firewall to allow any packets starting on the internal network and heading out to the Internet.
Additional ACCEPT rules based upon destination ports can be opened as needed as INPUT lines, like the port 22 one.
In the previous section, we configured iptables
to accept connections to port 22 in order to allow people to SSH into the host. Sometimes, you want to forward a port to a system behind the firewall instead of having the service run on the firewall itself. For example, you may have a web server running on port 8080 on an internal box that you want to expose to the Internet via port 80 on the firewall.
Rewrite packets addressed to port 80 to instead go to port 8080:
# iptables -t nat -A PREROUTING -p tcp -i eth2 --dport 80 \ -j DNAT --to-destination 192.168.0.200:8080
Accept any packets addressed to 192.168.0.200 port 8080:
# iptables -A FORWARD -p tcp -d 192.168.0.200 \ --dport 8080 -m state --state NEW,ESTABLISHED,RELATED \ -j ACCEPT
This example is a lot simpler since it builds upon concepts we've already learned. We just have two simple commands:
First we set up a PREROUTING rule which will be processed once the packet is received, prior to any routing rules being applied. If the packet is TCP and came in on the Internet interface (
eth2
) with a destination port, then the packet is added to the destination NAT (DNAT) chain with a final destination of 192.168.0.200 port 8080.Next, any packet destined for 192.168.0.200 port 8080 is either a new connection or an established connection; the packet is then accepted for forwarding to the destination.
Right now we have a rather simple network configuration. We have a single router with a public-facing IP address on one interface and a private IP address on the second interface. But what if we want to have multiple private networks behind the route?
Our first option in this scenario would be to add additional IP addresses to the internal interface. The ip
command allows you to assign multiple IPs to a single interface, with optional interface aliases like eth0:0
. This will allow you to assign IP addresses to systems behind the firewall within one of the few ranges and have them all route appropriately.
The downside of this approach is that all the internal IPs exist within the same collision domain of the network. This has a few implications, including the ability to move systems between those IP ranges and potentially bypassing access control rules, as well as problems assigning addresses via DHCP due to confusion about what address range to hand out.
The second option would be to put a third network card in the router and then either plug the additional card into a dedicated switch or separate out the existing switch into multiple VLANs and plugging the new network card into a port on a dedicated VLAN for that network. The downside here is the additional cost of the NIC (assuming you have space to add it) and then either the usage of an extra switch port or an extra switch.
The third option is to configure the switch into dedicated VLANs and plug the LAN side of your router into a port configured as a trunk. From there, Linux can be configured to use VLAN tagging to split your single physical interface into a pair of virtual interfaces and tag packets, as appropriate, so that the switch automatically adds them to the appropriate VLAN.
There are two steps required in order to use VLAN tagging on your Linux server:
The first is to hook it up to a switch that has VLAN enabled, connected to a port which is allowed to act as a trunk. The specifics around how to configure a switch in this mode are outside of the scope of this book, since they are specific to the switch itself. You'll want to find a managed switch which supports the 802.1Q standard and consult its documentation for configuration.
The second thing you'll need is to create virtual interfaces assigned to the desired
VLAN
. In our case, we're going to create two virtual interfaces, which are assigned tovlans
1 and 2.# ip link add link eth0 name eth0.1 type vlan id 1 # ip link add link eth0 name eth0.2 type vlan id 2
Now that they exist, you can treat them like normal network interfaces and configure them as we did in the section on adding a second network.
Note
Note that eth0.1
is a naming convention, not a requirement at this point. You could instead choose to name the interfaces names wireless and wired if you wanted to.
Making this change permanent can be rather distribution specific and may depend on the use of the vconfig
command, which is distributed through the VLAN package on Debian/Ubuntu. Debian-based distributions will automatically create VLAN interfaces if you specify an interface in /etc/network/interfaces
which is named as a physical interface, followed by a period, and then a VLAN ID, as eth0.1
is our example.
VLAN tagging, as defined by the 802.1Q standard, functions at the Ethernet layer level. A standard Ethernet frame contains 4 fields, the destination MAC address, the source MAC address, the EtherType or length field (depending on the type of frame), the data (the IP packet), and a frame check sequence (FCS). 802.1Q works by adding a VLAN tag between the source MAC address and then the EtherType/length field.
A switch that supports 802.1Q may have one or more network ports that are configured to act as a Trunk. Trunk ports will accept VLAN tagged packets and will pass them along as appropriate. They will detect the specified VLAN tag, determine the appropriate VLAN the packet is destined to, and will deliver the packet to any switch ports that are on that VLAN. Tagged packets can even pass between multiple switches as long as they are properly configured. If a packet is received without a tag on it, it will have a tag added automatically, based upon the VLAN associated with the switch port it was received on.