The Linux Home Network

by Preston F. Crow

I have a fairly complicated home network that uses many of the different features of Linux. While my network setup is probably more complicated than what you'll ever need in your home, you may find that you want to use some of the same features. In this article, I'll describe how my computers are physically connected, discuss how I want my network to work, and then explain how I used various aspects of Linux to accomplish those goals.

The Physical Layer

I have three desktop computers and a laptop, with Internet access provided by a cable modem and an ISDN line. One of the desktop computers runs Linux and handles communication between the Internet and the internal network.

The cable modem is an external device that converts the cable signal into an Ethernet signal. I also use an external ISDN modem that works the same way. Each of these is connected to a separate Ethernet card in my central Linux system.

The three desktop computers are connected to each other using Ethernet with a hub. The laptop, however, doesn't have Ethernet, so it connects to my central Linux system using a cable between the parallel ports.

So, the central computer has three Ethernet cards: one for the cable modem, one for the ISDN modem, and one for the hub connecting to the other machines.

How It Should Work

First, let me explain how my Internet connections work. The cable modem provides a single dynamic IP address and a fast connection to the general Internet. The ISDN line provided by my company connects to its internal network, and provides a 16 IP address subnet for the ISDN line.

I want all my computers to communicate with each other, my company's intranet and the general Internet. I want to use the IP addresses that come with the ISDN line for my internal network.

Subnets and Routing

The first thing I did was partition my network into subnets. Forget anything you've heard about different classes of IP addresses; they're not relevant for home networking. The issue here is subnetting. The idea is that you have a block of contiguous IP addresses, and you want to split them into separate smaller blocks. The only restriction is that the number of addresses on each block must be a power of two.

When subnetting, the first address of each block is called the “network” address. Don't use it for anything. The last address of the block is the “broadcast” address. This, also, is not used for any particular machine. The “netmask” is 255.255.255.x where x is 256 minus the number of addresses in the subnet.

IP routing is handled as either a network or on an address-by-address basis. For a network, you specify the network address and the netmask instead of just a single IP number. In either case, you have to tell it how to get to that address. If the machines are on a directly connected network, you can just specify a network interface; otherwise, you have to give it the address of another machine that will do forwarding for you.

For my network, I assigned the 16 IP addresses that came with my ISDN modem as follows: 4 for a subnet connecting the ISDN modem and my main Linux system, 4 for the PLIP subnet connecting my laptop and 8 for the Ethernet connecting my desktop systems. The cable modem provides a single IP address for the Ethernet interface it connects to, while the ISDN modem acts like a router with its own IP address.

How It All Works

I completely rewrote my startup scripts for initializing my network. Probably the best way to understand my network is to step through the initialization script for my main Linux system and explain what it does. Like most systems, my startup scripts are written for /bin/sh (which is actually bash, but I don't use any fancy bash features).

Since the tools for Linux 2.2 are different from those in Linux 2.0, the scripts check which version I'm running, allowing me to use whichever kernel I choose to boot.

# Determine kernel version
KVERSION=`/usr/bin/awk '{printf("%3.3s\n,$3)}'\
/proc/version`

Now I go through and set up each of the network interfaces. The loopback device is there for making network connections to my own machine. Every Linux system should already have this configured.

# Attach the loopback device.
/sbin/ifconfig lo 127.0.0.1
/sbin/route add -net 127.0.0.0 lo
I built all the Ethernet drivers as modules. This allows me easy control over which interface is eth0, as opposed to eth1. You will notice I use options to specify the I/O address of my NE2000 card. This is because I have the unusual combination of an ISA and a PCI NE2000 card, and they use separate drivers.
# Set up the cable modem,
:echo setting up eth0
modprobe ne io=0x300,1,1,1
The cable modem uses DHCP to determine its IP address, and there's a different version of the DHCP client daemon for 2.2.
echo -n "starting dhcpcd"
[ $KVERSION = "2.0" ] && {
        /sbin/dhcpcd.old -r eth0
} || {
        /sbin/dhcpcd eth0
}
echo "
The second Ethernet card is connected to the ISDN modem. The ISDN modem acts like a router, so it has its own IP address (.209). I add a route for the subnet to use eth1, and I add another route for my employer's network to be gatewayed through the ISDN line.
# Set up the ISDN line
echo setting up eth1
modprobe ne2k-pci
ifconfig eth1 inet 172.25.5.210 netmask 255.255.255.252 \ broadcast 172.25.5.211
route add -net 172.25.5.208 netmask 255.255.255.252 eth1
route add -net 168.159.0.0 netmask 255.255.0.0\
gw 172.25.5.209
The third Ethernet interface is for my internal network.
# Set up the LAN
echo setting up eth2
modprobe tulip options=11
ifconfig eth2 inet 172.25.5.217 netmask 255.255.255.248 broadcast 172.25.5.223
route add -net 172.25.5.216 netmask\
255.255.255.248 eth2
For my laptop, setting up PLIP is just like setting up another Ethernet port. Because I have a separate parallel port with a printer attached, I use modules for both PLIP and LP and specify options to modprobe so it gets the right port for each one.
# Set up PLIP
# see
# http://metalab.unc.edu/mdw/HOWTO/mini/PLIP.html
echo setting up plip0
[ $KVERSION = "2.2" ] && {
        modprobe lp parport=1
        echo 7 > /proc/parport/0/irq
        modprobe plip parport=0
} || {
        modprobe lp io=0x378
        modprobe plip io=0x3bc irq=7
}
/sbin/ifconfig plip0 172.25.5.213 netmask \
255.255.255.252 pointopoint 172.25.5.214 up
/sbin/route  add -net 172.25.5.212 netmask \
255.255.255.252 dev plip0
We will use IP Masquerade to allow us to share the single IP address provided by the cable modem, so we want to load the modules to provide support for masquerading protocols needing special help.
# Load IP Masquerade modules
# Note that some of these only exist for 2.2
# kernels
modprobe ip_masq_ftp
modprobe ip_masq_irc
modprobe ip_masq_raudio
modprobe ip_masq_cuseeme
[ $KVERSION = "2.2" ] && {
        modprobe ip_masq_vdolive.o
        modprobe ip_masq_quake.o
}
Now for the masquerading, firewalling and such. All of this changed in the 2.2 kernel, but the ideas are the same. First, we set up our forwarding rules. These tell Linux what to do when it receives an IP packet from one machine that is destined for another. Anything from the internal machines destined for the Internet should be masqueraded. We also need to allow forwarding of packets between the different interfaces. What we don't want is to allow connections from the general Internet to be forwarded or masqueraded—coming in from the cable modem should be addressed to the IP number assigned for that interface. Hence, my default policy is to forward, and I have special rules to masquerade packets destined to go out the cable modem or come in from the cable modem.

Note that the order of the forwarding rules is important. The kernel must see the rules for masquerading before the rule for denying forwarding on the interface.

# Set up IP Masquerading
# This has to be done carefully to only masquerade
# packets that originate from my network. Otherwise,
# someone on the outside could route through my
# system to hide their identity
echo Setting up IP Masquerade
[ $KVERSION = "2.2" ] && {
        echo 1 > /proc/sys/net/ipv4/ip_forward
        ipchains -P forward ACCEPT
        ipchains -A forward -i eth0 -s \
172.25.5.216/255.255.255.248 -j
        MASQ
        ipchains -A forward -i eth0 -s \
172.25.5.212/255.255.255.252 -j
        MASQ
        ipchains -A forward -i eth0 -j DENY
} || {
        /sbin/ipfwadm -F -p accept
        /sbin/ipfwadm -F -a m -S \
172.25.5.216/255.255.255.248 -D 0.0.0.0/0 -W
eth0
        /sbin/ipfwadm -F -a m -S \
172.25.5.212/255.255.255.252 -D 0.0.0.0/0 -W \
eth0
        /sbin/ipfwadm -F -a deny -W eth0
}

Now, I like to run X on several machines, connecting to them from accounts on remote Internet hosts. The X server accepts connections on port 6000+n, where n is the number following the colon in the DISPLAY variable. Hence, I want to forward connections to port 6001 to port 6000 on an internal machine, doing a sort of reverse IP masquerade. With 2.2, I use port forwarding. With 2.0, I use a user-space daemon, since port forwarding isn't part of the standard 2.0 kernel. Note this is insecure, since the internal machine thinks all the forwarded connections are coming from my other Linux box.

Listing 1

This gets a bit complicated, because the normal way of doing port forwarding requires you to specify the IP address the packet was addressed to. Since my IP address is dynamically assigned, I wanted to keep my rules independent of my IP address. Hence, I do a two-step process using “firewall marking”. I create a marking rule that assigns a numbered mark to any incoming packet for port 6001, and then I do port forwarding based on that mark. (See Listing 1.)

Listing 2

The last step is adding some firewall rules (see Listing 2) to block out the general Internet connections I don't want. For example, I run Sendmail, but only for sending mail, as I receive mail elsewhere. Instead of keeping up on all the security issues concerning Sendmail, I just block any attempts to connect to it from outside. You can use DENY or REJECT. The difference is that REJECT will send back a packet saying “connection refused”, while DENY will totally ignore the connection attempt.

Now we have a problem with the ISDN modem, because when it sends packets out for my various machines, it thinks they're all on one big subnet. It doesn't realize it needs to send everything to my central Linux system, which will then route things. Since I didn't want to bother the people who configured the modem, I instead used bridging to solve the problem. Bridging is somewhat like routing, but at a different level. Routing uses IP addresses; bridging uses Ethernet addresses. When bridging is turned on, Linux figures out the Ethernet addresses of all the systems on each interface, and if it sees a packet addressed to a machine on a different interface from that machine, it retransmits the packet on the right interface. What is important is that a bridge transparently connects Ethernet segments. It works at the Ethernet level, so any IP-level stuff (such as firewalling, masquerading and subnetting) is completely bypassed. You don't even have to assign IP addresses to the interfaces when bridging. In order for it to work, you have to put the interfaces into “promiscuous” mode, so they will listen for Ethernet packets which aren't addressed to them.

# Set up bridging so that incoming ISDN packets
# that don't know that
# I'm acting as a router will be intercepted and
# passed on
ifconfig eth1 promisc
ifconfig eth2 promisc
brcfg -ena

Listing 3

Bridging works at the Ethernet level, so PLIP, which is an IP-level interface, can't be a part of a bridge. But, I still want to do the same thing for my laptop. The solution is to use proxy ARP (see Listing 3). The idea is to use the “address resolution protocol”, which maps between IP addresses and Ethernet addresses, to tell the ISDN modem my Linux gateway is supposed to receive packets for the IP number I've assigned to my laptop. Think of ARP as the glue that connects Ethernet to IP networking.

For the client systems, I configure their network interfaces the same way I configured the corresponding interface above, only changing the IP address. I then add a route for the subnet and set the default route to be my Linux box. It also works fine if the client is running MacOS or Windows. Here's an example:

# Configure network interface and routes
ifconfig eth0 inet 172.25.5.218 netmask \<\n>
255.255.255.248 broadcast 172.25.5.223
route add -net 172.25.5.216 netmask \<\n>
255.255.255.248 eth0
route add default gw 172.25.5.217

Of course, I also had to recompile my kernel, explicitly enabling every feature I used. Some of them are flagged as “experimental”, but they have been completely stable in my experience (although you do need to tell the kernel to allow “experimental” features during configuration). The only networking kernel patch I've used is an update of the version of the Tulip card driver, because my card is rather strange.

Be careful, though: even people who really know what they're doing can make mistakes. If you find any security holes in my network, please let Linux Journal know, so others won't make the same mistake.

Preston F. Crow (Preston.F.Crow@Dartmouth.edu) lives with his wonderful wife in his new house in Ashland, MA. He works for EMC, writing software to run multi-terabyte storage systems.

Load Disqus comments