#1117993 runit-services: add a "test for network is up" service

Package:
runit-services
Source:
runit-services
Submitter:
Lorenzo Puliti
Date:
2026-01-31 16:31:15 UTC
Severity:
normal
Tags:
#1117993#5
Date:
2025-10-13 12:11:55 UTC
From:
To:
(adding Andrew in CC since he sent the MRs that are blocked)

There are two MR on Salsa
gerbera
https://salsa.debian.org/debian/runit-services/-/merge_requests/4
xl2tpd
https://salsa.debian.org/debian/runit-services/-/merge_requests/9

that are blocked because we are not able to test for network:
the proper solution is to add oneshot capability to runit package,
but this requires some design and I'm not sure it will be ready
before forky, and blocked MR are already 1 year old.

In order to unblock MRs in a reasonable time frame I propose to
termporarily add two standard services (to be replaced by oneshots in future)
to test network in the check file and wait forever in the run file.

I think those services should meet the following conditions:

* name should not match any existing sysv script to avoid the shadowing logic
  that runs at startup and during runtime; name could be 'loopback-is-up'
  and 'network-is-up'

* script are only for testing the network, they do not bring the network
  up in stage 2.

* they shuould work in non-linux port, so if 'ip' is used but is
  not available in the platform, fallback on ifconfig


'loopback-is-up' and 'network-is-up'

I'll send some draft here as soon as I have something.

Lorenzo

#1117993#10
Date:
2025-10-15 13:36:22 UTC
From:
To:
initial version of the service is here
(loopback probably is not useful)

$ cat /etc/sv/network-is-up/run
#!/bin/sh
#Copyright: 2025 Lorenzo Puliti <plorenzo@disroot.org>
#                  2025 Andrew Bower <andrew@bower.uk>
#License: CC0-1.0

exec chpst -L /run/runit/${PWD##*/}.lock chpst -l \
   /run/runit/${PWD##*/}.lock true
-----------------------------------------------------

$ cat /etc/sv/network-is-up/check
#!/bin/sh

if [ -x /usr/sbin/ip ]; then
        for interf in $(ip addr | grep -Po '^\d+:\s+\K[^:]+') ; do
                [ "$interf" = 'Iface' ] && continue
                [ "$interf" = 'lo' ] && continue
                echo "$interf" | grep docker >/dev/null && continue
                echo "$interf" | grep incusbr >/dev/null && continue
                echo "$interf" | grep lxcbr >/dev/null && continue
                ip link show enp0s31f6 | grep 'UP' >/dev/null && exit 0
#               if [ -x /usr/sbin/iwconfig ]; then ...
        done
        exit 1
elif [ -x /usr/sbin/ifconfig ]; then
        for interf in $(ifconfig -s | cut -f1 -d' ') ; do
                [ "$interf" = 'Iface' ] && continue
                [ "$interf" = 'lo' ] && continue
                echo "$interf" | grep docker >/dev/null && continue
                echo "$interf" | grep incusbr >/dev/null && continue
                echo "$interf" | grep lxcbr >/dev/null && continue
                ifconfig "$interf" |grep 'inet' >/dev/null && exit 0
#               if [ -x /usr/sbin/iwconfig ]; then ...
        done
        exit 1
else
        #no tools to detect, fail the check
        exit 1
fi
----------------------------------

$ cat /etc/sv/network-is-up/finish
#!/bin/sh

chpst -l /run/runit/network-is-up.lock rm /run/runit/network-is-up.lock
-----------------------------

yes the check file can be improved, however this one should be fine for
testing

Lorenzo

#1117993#17
Date:
2025-10-16 21:54:59 UTC
From:
To:
Hi Lorenzo,

Thanks for adding this support to unblock my service directory
contributions!

I don't see a problem in principle with using a new service directory as
a dependency for services that require networking, but I think there's a
better way to the extent that I think we should not do a service
directory.

Let's remember that there is nothing philosophical or practical about
runit that means a service directory's dependency needs to be another
service, rather than another type of check that can be performed.

A lot of initscripts and systemd units historically claim dependence on
'networking' which are not strictly necessary, probably added as a form
of 'cargo culting' and, as the systemd documentation correctly points
out [1], there is no single right answer as to what counts as the
network being ready.

In my contributed service definitions I have entirely omitted network
checks that are not needed at all, even if $network was present in the
initscript. Instead, I have found out what the real underlying
requirement is and implemented that as minimally as possible.

If we take the examples of my two pending runit-services merge requests:

1. gerbera - requires global unicast IPv4 address.

In this case (quite wrongly in my opinion, via libupnp) the server will
not start properly without a global unicast IPv4 address. Neither a
global unicast IPv6 address nor a link-local unicast IPv4 address would
be sufficient, even though they should be. But actual conectivity is not
important.

So we can test for this with one line which checks specifically for
global unicast IPv4 addresses.

  [ -n "$(ip -f inet addr show scope global)" ] || exit 1

I don't think a runsv supervisor is necessary or beneficial for this. If
it actually caused the network to be configured when it wasn't, like
'default-syslog' does for logging, maybe it would have some merit.

2. xl2tpd - requires a non-loopback address to bind to.

We can test for this by looking for any address with wider scope than
loopback. If we do this numerically, we can catch scopes from unexpected
types of addressing family (in principle), so with (decreasing) scopes
going up numerically to 255 and loopback being 254:

  ip -oneline -Numeric addr show | grep -Evq ' scope 25[45]' || exit 1

So how can we do this generally?

First it is important not to look for things which could give us false
information, hence porcelain views of output data (like -oneline and
-Numeric help to give us) and certainly not checking interface type
names or especially not actual interface names.

We can't have one size fits all or we end up limiting services
unnecessarily. What if we needed xl2tpd to provide a tunneling service
so that gerbera could start (OK, not that exact situation because
gerbera needs IPv4, but you get the idea!)? So if you need multiple
checks you might end up with multiple fake services like network-is-up,
each with their own runsv. It doesn't really scale.

There may be other services with more stringent requirements.

I think the answer is to have a library of shell functions we can call
to check things. You could put this in invoke-run but I wonder if that
is getting a bit full. So maybe another file, either sourced from
invoke-run or directly from the run script.

Something like:

#...

. /lib/runit/network-checks

have_network_address scope=global family=inet || exit 1

#...

The implementation of have_network_address() could map scope=X onto a
scope number first, if specified, loop through supplied addresses and
discard any that are numerically higher than num(X). It can then discard
any that don't match the family=Y key if specified. Then return true if
there are any addresses left.

You could have an alternate implementation for Hurd. I wouldn't bother
with ifconfig on Linux - net-utils won't be present if iptroute2 is. On
Linux you could fall back to /proc/net/dev; I don't know if Hurd has
something equivalent.

'ip -oneline' helps you do more robust parsing.

Andrew

[1] https://systemd.io/NETWORK_ONLINE/

#1117993#22
Date:
2025-10-17 21:59:23 UTC
From:
To:
Hi Andrew,

thanks for your comprehensive reply

yeah, I agree except the advantage of using a service is that sv check
waits up to x seconds before giving up. I want to implement this for
oneshots checks, without a runit service I'll have to implement this
too.

I'm aware checks that are in the MR are more accurate, but I hoped that
a less accurate and more generic check could be used: I see that
probably this idea does not work.

ok I'm going to try this, but I have few questions:

* why map scope= to a number, if I can filter for the word? do we need
  to filter for something within each scope (and it can be done with
  the numeric mapping)?

* what to do if scope= and or family= are not passed to the command?
  just fail or have some default for both?

* what if ip (and or ifconfig) are not found in the system? is it safe
  to assume that network is not configured and fail in this case?

Best,
Lorenzo

#1117993#27
Date:
2025-10-17 22:16:23 UTC
From:
To:
Hi Lorenzo,

/usr/bin/timeout comes from coreutils - is that good enough?

sorry I've not thought about it from this angle so need to think some
more - thought I'd get a reply in to answer your questions below though.

The reason was that I was thinking the scope runs theoretically between
0 and 255 and although most aren't used now in any situations I am aware
of, they could be, and we should be agnostic to this, so you'd say you
need something of at least a certain scope, which works better with a
number. It's probably excessive.

It might be worth us trying out an example implementation and seeing if
it looks sensible or silly.

No family would mean any family; missing scope I'm not sure, I think
possibly >=link-local or >=global.

I'm not particularly wedded to those arguments, by the way, I just came
up with it as an example of the sort of thing we could have.

That's a good question. Needs further thought. Might be worth noting
we're probably doing more than the initscripts would here - they would
just wait until networkmanager/dhclient/networking or whatever services
have been started and possibly failed. So maybe we're over thinking this
and actually need to wait on default-network instead which can launch
those like default-rsyslog does. But I think it would be ugly to have a
service launch another service to do this - is there a way of having an
alias for whichever network setup service is configured?

#1117993#32
Date:
2025-10-17 23:58:35 UTC
From:
To:
Hi,

ok

will try something this weekend
I don't find attractive to play with symlinks and alternatives either..

Recently I was thinking:
* define a facility "suffix" in runit; for example '-log' for sysloggers
  (could be '-net' for network)
* rename all services that belong to the group appending the common
  '-log' suffix to the end of the service name. For example rsyslog-log,
  socklog-unix-log and so on
* in run files of services that require one service of the '-log' group
  check the service with full path and wildcards, for example
  'sv check /etc/service/*-log'
but I still have to test if it works as expected..

Lorenzo

#1117993#37
Date:
2025-10-18 12:39:14 UTC
From:
To:
On Sat, Oct 18, 2025 at 01:58:35AM +0200, Lorenzo wrote:
 On Fri, 17 Oct 2025 23:16:23 +0100
[...]

Leaving aside my talk about ip address checks for the moment, your
wildcard suggestion got me thinking.

This seems like a better idea than forcing services to be named a
certain way:

Any service that wants to provide an LSB-style 'facility' could have an
empty file in its service directory called 'provides-<facility>'. I
have mocked this up like this (and remember ifupdown is down in stage 1):

=== /usr/lib/runit/facilities ===

facility() {
  for i in $(find "$SVDIR" -iregex "[^.]*/provides-$1" -printf "%h ")
  do
    basename $i
  done
}

sv_if() {
  [ $# -gt 1 ] || return
  sv "$@"
}


=== /usr/share/runit/sv.src/network-manager/provides-network ===


=== /usr/share/runit/sv.src/dhclient/provides-network ===


=== /usr/share/runit/sv.src/gerbera/run ===

#!/usr/bin/env /lib/runit/invoke-run

exec 2>&1

. /usr/lib/runit/facilities

sv_if start $(facility network) || exit 1

exec chpst -u gerbera:gerbera ##bin## -c /etc/gerbera/config.xml

#1117993#42
Date:
2025-10-20 15:53:20 UTC
From:
To:
Hi Andrew,

I've just force pushed to git

https://salsa.debian.org/debian/runit-services/-/blob/next/debian/extra/netcheck?ref_type=heads

call it as (example)

/usr/lib/runit-services/netcheck scope=link family=inet

scope and family can be omitted (default scope = link, family = any);
accepted values are scope=global|link|host; family=inet|inet6.

There are issues with ifconfig as especially the scope does not match
with ip output; I think ifconfig only provides the scope of the inet6,
so for example my interface is of scope=link (instead of scope=global).
Wireless is not tested, and still TODO if ip is not installed.
Other than that it seems to work.

Let me know how it works if you have the chance to test it with gerbera
or xl2tpd.

I don't find using an include particularly appealing (it feels like
sysvinit scripts, I prefer executables and return codes), but anyway I
would leave the facilities discussion for another bug;
network can be up even if there is no service like network-manager or
dhclient, and network-manager or dhclient can fail to bring the network
in the desired state even when they are up and running.
So I don't think a facility (regardless how it is implemented) is the
solution for this bug.

Best,
Lorenzo

#1117993#47
Date:
2025-11-16 14:32:41 UTC
From:
To:
Hi Lorenzo,

This is just a courtesy response to apologise for being slow to evaluate
and reply. I have some thoughts on this but need to dedicate some
attention to it without getting distracted!

In some ways there are no easy answers because no one, with any
supervision system, seems to have really perfectly solved this problem!

Andrew

#1117993#52
Date:
2025-11-24 14:17:06 UTC
From:
To:
Control: tags -1 +moreinfo

Ok, let's put this on hold for a while.
I'm attaching here the last version of the git patch for reference.

Lorenzo

#1117993#59
Date:
2026-01-31 16:27:39 UTC
From:
To:
Hi Lorenzo,

Thanks for your patience!

I am uncomfortable with this script as it seems heavy and non-generic
for the purposes required.

I did wonder, considering how it is going to be used, if, going back to
a runit service, it might as well listen on the netlink socket
continuously and let the 'check' script do the work of interpreting
current state, using a combination of rtmon(8) and ip-monitor(8).
However, I tried prototyping that solution a while ago and one problem
was that you couldn't get a dump of one of the needed tables (can't
remember if it was 'link' or 'address') at start of day to avoid a
possible race. There may have been other potential issues, too.

Another approach might be to supply hook scripts for the known network
configuration managers that raise a common flag file under /run
somewhere - this is cruder than what I was trying to achieve with only
depending on the minimum service level but it's probably what most
systemd services do anyway.

Yet another approach, going back to the facilities (we really should
have another bug for that, shouldn't we? My latest idea for that was
under #1121617, addressing your preference for a process not a shell
include) idea would be that each of the services that implement a
network facility could install their own hook script and their own check
script would check the flag raised by that. We'd need one to cover
ifupdown of course, although we'd still want the actual config done in
stage 1 I think?

Some specific review comments on the script below:

Why constrain the names and not just let them match or not naturally?

I wonder if it would be nicer to scan /proc/net/dev or something to pick
up interface names.

Why do we need to check link up and does this do the right thing for
logical interfaces that might have non-obvious states?

I think that for the applications which really do depend on network
readiness, usually it's just to be able to bind to the right addresses
and interfaces, rather than being able to pass traffic, in order to
start up, although that may not be true for all, of course. In my
examples only binding was necessary.

I don't like the number of times we are invoking iproute2. As well as
starting the tool, each time we are setting up a netlink socket and
scanning a lot of the same information, discarding a different amount
each time. I would prefer to capture once and then process. (This script
might be cleaner written as perl, which is essential anyway, as that is
way better for processing inputs.)

A shame to need to have the second method but I accept you want a
non-Linux solution.

Why do we need a wireless section? This script should be agnostic to the
type of physical interface.

Sorry again for my tardiness and I hope you don't mind my doubts!

Andrew