Dynamic network configuration on Linux ...

... or how to support dynamic IPv4 network configuration on a Linux host with multiple interfaces (w/o using NetworkManager).

Introduction

Your laptop (obviously running Linux) has many interfaces that you can use to get IPv4 connectivity: ethernet, wifi, 3G, bluetooth, USB, firewire ... Nonetheless, even if you would like things to work out of the box when pluging your ethernet cable, docking your laptop or when your are in a location with a WiFi network you know, this is usually not the case. This page discusses why (i.e. the problems) and provides some tools and configurations options (i.e. the solutions).

Multiples default routes

Linux does not allow multiple default IPv4 routes (with the same weight) to be configured simultaneously. Usually (i.e. dynamic configurations), a process (ppp daemon, dhcp client, ...) ends up installing a default route for the interface it configures. If another process tries and do the same (e.g. DHCP daemon) for another interface, it usually fails to install the new route. Some other application may also have some logic to replace (or not) the existing default route (pppd).

In all cases, when multiple IPv4 automatically-configured interfaces are up at the same time on a Linux host, only a single default route remains active. By default, this results in an undefined network configuration.

Reaction to carrier loss

Based on the distribution you use, the tools you have selected, your habits and specific configurations, things may vary a lot regarding the IPv4 autoconfiguration of your interfaces. By default, most DHCP clients are started manually (via a CLI or a GUI) or at boot to work on a given interface. They grab parameters from a DHCP server, configure an address and a default route. From time to time, they renew their lease.

Usually, when the link disappears (cable unplugged, carrier loss, reboot of an AP, ...) on the associated interface, nothing happens, i.e. the configured address and the route are not invalidated. Then, if one connect the interface to a different network (change of L2 subnet), previously configured route and address are used. This requires manual intervention (ifup eth0; ifdown eth0). Another solution is to rely on a global configuration interface and associated tools to define some policy for such events: many distributions for client devices ship with NetworkManager.

If you do not fancy graphical utilities, it's possible to make things more dynamic using e.g. netplug and some very simple scripts. This is discussed later in this page.

Weak host model

Linux implements a weak host model: even if an address is configured on a given interface, this does not guarantee that packets generated on the host with that address as source will leave the host by that interface. Simply put, the destination IP address of the packet is used as the main key to select the best available route (gateway, interface) from the routing table. In the strong host model, the destination IP address and source IP address would be used to to select the best route.

If you are interested by the details, there are more information available in RFC1122 and this Cable Guy article. For IPv6, this is different and the reference document on the topic is RFC3484.

Configuration

In this section, we discuss the tools and configuration required to support the dynamic operation of a mobile node:

The target Linux distribution is Debian but this also probably applies directly to Ubuntu. The tools we use are:

To be clear, the specific use cases we have are the following:

In simple words, when a 802.11 network gets available, if 802.11 interface is administratively up, attachment and IPv4 address autoconfiguration should be automatic. If a cable is connected to our ethernet interface (administratively up and w/ link available), we want automatic address configuration to happen. When connected to multiple networks, we want all default routes to be available with the route via the best interface having the highest priority (ethernet, wifi and 3G, in that order).

Configuring dhcpcd

DHCPCD is a DHCP client. It supports per interface configuration and has the ability to install routes with different metrics. At least on Debian, there are 4 files involved in the configuration of DHCPCD:

The content of my /etc/default/dhcpcd is provided below. Note that its content prevents the configuration of DNS server. This is on purpose in my case but you may probably want a different behavior.

SET_DNS='no'
SET_DOMAIN='no'
SET_HOSTNAME='no'
SET_NTP='no'
SET_YP='no'

case ${INTERFACE} in
eth0) # Ethernet
OPTIONS="-h spooky -i stealth --metric 1 --noipv4ll"
;;

wlan0) # 802.11
OPTIONS="-h spooky -i stealth --metric 2 --noipv4ll"
;;

wwan0) # 3G
OPTIONS="-h spooky -i stealth --metric 3 --noipv4ll"
;;

*)
;;

esac

All options passed by SET_* variables are common to all interfaces. The only difference in OPTIONS values for the various interface is the metric of the installed route: eth0 is preferred over wlan0 which is preferred over wwan0

.

I suggest you take a look at man 8 dhcpcd-bin for more details on what the daemon support. Also note that (at the time of writing) current version of /sbin/dhcpcd script is buggy in the way it deals with the set of options (bad use of double quotes). As usual, don't trust your configuration and check what the daemon does in practice.

As we will see in a few seconds, unlike what is usually done on Debian systems, our calls to dhcpcd will never be done manually or by marking an interface with "inet dhcp" in /etc/network/interfacesfor instance. All our calls to dhcpcd will be done via netplug either to start the service when the link becomes up or to stop the service when the carrier is lost or the interface is administratively set down.

Configuring netplug

netplug is a network link monitoring daemon. It processes link events information provided by Linux kernel via Netlink and allows users to configure actions associated with those events. Simply put, this allows dynamic configuration (via simple scripts) when a cable is plugged/unplugged, connection to a WiFi network occurs.

On Debian, netplug configuration is performed via /etc/netplug/netplugd.conf configuration file and by tweaking /etc/netplug/netplug script. The former is simply filled with the list of interfaces to consider. The latter is a simple shell script called each time an interesting event (associated to previous interfaces) occurs.

On my laptop, I want netplug to monitor my three main interfaces: eth0 (Ethernet interface), wlan0 (802.11 interface), wwan0 (3G interface). netplug configuration file is filled with this list:

$ cat /etc/netplug/netplugd.conf
eth0
wlan0
wwan0

Now that we have configured netplug to monitor previous interfaces, we need to configure some reactions when events occur on those interfaces (link up/down). This is done by tweaking /etc/netplu/netplu script. Below is the modified version I use.

PATH=/usr/bin:/bin:/usr/sbin:/sbin
export PATH

dev="$1"
action="$2"

case "$action" in
in)
    FILE="/var/run/dhcpcd-${dev}.pid"
    if ! [ -r $FILE ]; then
        exec dhcpcd ${dev} 2>&1
    fi
    ;;
out)
    FILE="/var/run/dhcpcd-${dev}.pid"
    if [ -r $FILE ]; then
        exec kill -INT `cat $FILE` 2>&1
    fi
    ;;
*)
    exit 0
    ;;
esac

For the specific device (eth0, wlan0 and wwan0 interfaces all support DHCP), it simply spawns an instance of dhcpcd (if one is not already running) upon in event (i.e. cable plugged or link up) or stops the running dhcpcd instance on out event (cable unplugged or link down). Note that I don't use the probe event in my script (so that you know it exist).

When receiving the INT signal sent by kill, dhcpcd removes configured address and route.

For more details, I suggest you take a look at man 8 netplugd.

Configuring wpa_supplicant

wpa_supplicant is the Linux 802.1X supplicant (it supports all kinds of EAP) for WiFi (WPA/WPA2) and wired networks. It has a lot of features and can in fact be used to automatically manage all kind of 802.11 networks. For instance, once configured and started, it can associate to the preferred available network automatically.

The first thing to do is to write a wpa_supplicant configuration file providing information on the various WiFi networks you use. Here is an excerpt of my /etc/wpa_supplicant/wpa_supplicant_wifi.conf file (I have a separate equivalent for my ethernet networks (/etc/wpa_supplicant/wpa_supplicant_wifi.conf)

...

network={
      ssid="open"
      key_mgmt=NONE
      id_str="open"
      priority=3 
}

network={
        ssid="secure"
        key_mgmt=WPA-EAP
        proto=WPA2
        group=CCMP
        pairwise=CCMP
        eap=TLS
        ca_cert="/etc/certs/cacert.pem"
        client_cert="/etc/certs/client.pem"
        private_key="/etc/certs/client.key"
        private_key_passwd="somepwd"
        identity="me"
        priority=5
}

network={
        ssid="AndroidAP"
        key_mgmt=WPA-PSK
        proto=WPA2
        pairwise=CCMP
        group=CCMP
        psk="SomeP4ssw0rd"
        priority=4
}


network={
        ssid="Spooky"
        key_mgmt=NONE
        group=WEP104
        psk="A4ABC2FC27412D4D23CAEBCA23"
        priority=2
}

network={
        ssid="another"
        key_mgmt=WPA-PSK
        proto=WPA2
        pairwise=CCMP
        group=CCMP
        psk="A very long and secret passphrase here"
        priority=1
}

The important elements for what we want to do are colored in red. Those are the priority and id_str configuration parameters:

Below is an example of /etc/network/interfaces associated with previous /etc/wpa_supplicant/wpa_supplicant_wifi.conf:

auto wlan0
iface wlan0 inet manual
      pre-up ip link set dev wlan0 up || true
      wpa-roam /etc/wpa_supplicant/wpa_supplicant_wifi.conf
      wpa-driver wext
      pre-down /etc/netplug/netplug wlan0 out >/dev/null 2>&1 || true
      post-down ip link set dev wlan0 down || true

# open is specific (IPv6 only) so I just stop dhcp via netplug
iface open inet manual
      up   /etc/netplug/netplug wlan0 out >/dev/null 2>&1 || true

iface default inet manual
      up   /etc/netplug/netplug wlan0 in  >/dev/null 2>&1 || true
      down /etc/netplug/netplug wlan0 out >/dev/null 2>&1 || true

All the 802.11 networks I use are mapped to the "default" id_str (parameter no set in the configuration) except the network with SSID "open" which is mapped to "open" virtual interface configuration entry. Because that network is IPv6 only, there is no need to start a dhcp server when connected to it.

We discuss other elements of the /etc/network/interfaces in more details in next section.

What to put in /etc/network/interfaces

Here is the content of my /etc/network/interfaces

auto eth0
iface eth0 inet6 manual
    pre-up ip link set dev eth0 up || true
    wpa-iface eth0
    wpa-driver wired
    wpa-conf /etc/wpa_supplicant/wpa_supplicant_wired.conf
    pre-down /etc/netplug/netplug eth0 out >/dev/null 2>&1 || true
    post-down ip link set dev eth0 down || true

auto wlan0
iface wlan0 inet manual
      pre-up ip link set dev wlan0 up || true
      wpa-roam /etc/wpa_supplicant/wpa_supplicant_wifi.conf
      wpa-driver wext
      pre-down /etc/netplug/netplug wlan0 out >/dev/null 2>&1 || true
      post-down ip link set dev wlan0 down || true

# open is specific (IPv6 only) so I just stop dhcp via netplug
iface open inet manual
      up   /etc/netplug/netplug wlan0 out >/dev/null 2>&1 || true

iface default inet manual
      up   /etc/netplug/netplug wlan0 in  >/dev/null 2>&1 || true
      down /etc/netplug/netplug wlan0 out >/dev/null 2>&1 || true

# 3G interface
iface wwan0 inet manual
      pre-up ip link set dev wwan0 up || true
      up /etc/netplug/netplug wwan0 in >/dev/null 2>&1 || true
      down /etc/netplug/netplug wwan0 out >/dev/null 2>&1 || true
      post-down ip link set dev wwan0 down || true

Ethernet and 802.11 interfaces are marked auto. I prefer starting 3G interface by hand after having typed some AT commands. Ethernet configuration is pretty straightforward


Remaining issues

The whole setup works great for me. But here is the list of remaining issues that I have not found time to address yet:

Now, I spoke a lot about addresses and routes in relation with DHCP but there is no word so far on DNS configuration i.e. updates of /etc/resolv.conf. The reason is (simply) that I have a static DNS configuration: my laptop is always acting as a MIPv6 Mobile Node, my DNS server is IPv6-reachable and IPv4 is only used by m6t in order to provide UMIP access to my Home Agent from IPv4 only networks. So, handling of /etc/resolv.conf is left as an exercise ...

A translation of this article to Serbo-Croatian language has been performed by Jovana Milutinovich. Thanks to her!.