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.