2

I'm running Alpine Linux, which is based on Busybox and musl, on Raspberry Pi.

When I run ifup eth0, it also starts udhcpc in the background for that interface. That means that if the ethernet cable is connected when I run ifup, it immediatelly obtains an IP address and if it's not connected, it will spin in the background until I connect it and then it receives an IP address. However this is a one time thing.

If it obtains an IP address and then I unplug the cable, it retains the IP address and if I try to make some network request, like ping google.com, it happily tries to do it via this interface until it times out. This is particularly a problem if I have more than one network interface, in which case it will keep trying to use the first one and ignore the other one, which might have internet connection.

What would be a proper lightweight solution to detect that cable was disconnected or that connection was lost and deconfigure the interface and then reconfigure it once it's connected again?

Just in case, here's also my /etc/network/interfaces file

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp

auto eth1
iface eth1 inet dhcp
0

1 Answer 1

3

On usual systems the command ip monitor link dev ethX could be used for an event-driven method reacting to changes to the carrier state (eg: NO-CARRIER).

As it's not available with busybox's minimal implementation, as described in an answer I made to the Q/A The difference between ip link down and physical link absence, the carrier state of an interface called ethX is also available at:

/sys/class/net/ethX/carrier
  • 0 means no carrier ie the NO-CARRIER flag on the interface
  • 1 means carrier

This value is available only once the interface is up (using ip link set ethX up) else an error is returned when reading it. The carrier state and the operstate are equivalent in almost all setups (this case included, see previous link for details) so it's just easier to use operstate at /sys/class/net/ethX/operstate which can return:

  • lowerlayerdown for no-carrier (with interface up),
  • up for carrier,
  • down when interface is administratively down.

To replace ip monitor link dev ethX one can thus periodically poll /sys/class/net/ethX/operstate to retrieve the same same information, like in the shell function below which polls about every 2 seconds the interface given as parameter and will output only changes:

stateevent () {
        local fileoperstate=/sys/class/net/"$1"/operstate
        local operstate oldoperstate=$(cat "$fileoperstate")

        while sleep 2; do
                operstate=$(cat "$fileoperstate")
                if [ "$oldoperstate" = "$operstate" ]; then
                        continue
                fi
                oldoperstate="$operstate"
                echo "$operstate"
        done
}

OP's wish is in the end to lose the routes of the disconnected interface so there's no timeout, and no clash with the dual-homed RPi (there are solutions to have two default gateways handled each in its own routing table, but it's complex especially with integration with DHCP, and would require its own approach and still require anyway the handling of carrier loss).

From a few tests, it's easier to lose the address (which will also lose the routes) than just the routes, or those routes (specifically the kernel's automatic LAN route) might not recover correctly later. That means that in the rare cases the RPi is client of itself, it should not connect to its own addresses that are set by DHCP but stick for example with addresses assigned on the lo interface like 127.0.0.1 or any additional address added there.

Busybox' built-in udhcpc has an useful feature to help with all this easily:

Signals:

USR1 Renew lease
USR2 Release lease

So just ask the running udhcpc to do the work itself with a simple signal: release will remove the address (the DHCP server can't be informed since the interface was disconnected, but that's not an issue), renew will add it (and the routes) back.

Here's an event loop function that will do this, with events coming from the output of the previous one:

eventloop () {
        local intf=$1
        local state

        while read -r state; do
                case "$state" in
                        lowerlayerdown)
                                        pkill -USR2 -f "(^|/)udhcpc( | .* )-i $intf( |$)"
                                ;;
                        up)
                                        pkill -USR1 -f "(^|/)udhcpc( | .* )-i $intf( |$)"
                                ;;
                        down)
                                ;;
                esac
        done
}

There's nothing to do if the interface was brought administratively down (even with just ip link set eth0 down): the routes are already removed by the kernel and the LAN routes will be added back by itself later, completed by the default route with the signal sent to udhcpc once the interface is back up.

You can have a shell script called manageudhcpc.sh (starting with the usual #!/bin/sh,) including the two shell functions above and ending with:

stateevent "$1" | eventloop "$1"

and run it twice (it will never return, so fork it):

./manageudhcpc.sh eth0 &
./manageudhcpc.sh eth1 &

I leave it up to OP to:

  • integrate this script for boot
  • see what to do at initial startup of the interfaces, since udhcpc could behave differently when it was just started with a non-connected interface, waiting for its initial lease.

Notes:

  • tested successfully on an Alpine 3.12 and also on an Alpine edge LXC containers with two interfaces set for DHCP on two networks,
  • the very conservative extended regex used to find the correct udhcpc might have to be changed depending on the exact Alpine version (I had to change it between 3.12 and edge, so that it works for both). Its pidfile could be used instead.
2
  • Wow, very comperhensive answer. Thank you, I used most of what you listed and I came up with a working solution. Works like a charm! Dec 1, 2020 at 12:39
  • Worked great for me, though I had to modify the script slightly since unplugging the ethernet on my system changed operstate to "down", not "lowerlayerdown". Mar 30 at 21:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.