Running Services After the Network is up
So you have configured your service to run after
network.target but it still gets run before your network is up? And now you are wondering why that is and what you can do about it?
network.target unit is the systemd counterpart of LSB's
$network facility. However as this facility is defined only very unprecisely people tend to have different ideas what it is supposed to mean. Here are a couple of ideas people came up with so far:
- All "configured" network interfaces are up and an IP address has been assigned to each
- All discovered local hardware interfaces that have a link beat have an IP address assigned, independently whether there is actually any explicit local configuration for them
- The network has been set up precisely to the level that a DNS server is reachable
- Similar, but some specific site-specific server is reachable
- Similar, but "the Internet" is reachable
- All "configured" ethernet devices are up, but all "configured" PPP links which are supposed to also start at boot don't have to be yet
- A certain "profile" is enabled and some condition of the above holds. If another "profile" is enabled a different condition would have to be checked
- Based on the location of the system a different set of configuration should be up or checked for
- At least one global IPv4 address is configured
- At least one global IPv6 address is configured
- At least one global IPv4 or IPv6 address is configured
And so on and so on. All these are valid approaches to the question "When is the network up?", but none of them would be useful to be good as generic default.
Modern networking tends to be highly dynamic: machines are moved between networks, network configuration changes, hardware is added and removed, virtual networks are set up, reconfigured and shut down again. Network connectivity is not unconditionally and continuously available, and a machine is connected to different networks at different times. This is particularly true for mobile hardware such as handsets, tablets and laptops, but also for embedded and servers. Software that is written under the assumption that network connectivity is available continuously and never changes is hence not up-to-date with modern standards. Well-written software should be able to handle dynamic configuration changes. It should react to changing network configuration and make the best of it. If it cannot reach a server it must retry. If network configuration connectivity is lost it must react. Reacting to local network configuration changes in daemon code is not particularly hard. In fact many well-known network-facing services running on Linux have been doing this for decades. A service written like this is robust, can be started at any time, and will always do the best of the circumstances it is running in.
network.target is a mechanism that is required only to deal with software that assumes continuous network is available (i.e. of the simple non-well-written kind). Which facet of it it requires is undefined. An IMAP server might just require a certain IP to be assigned so that it can listen on it. OTOH a network file system client might need DNS up, and the service to contact up, as well. For
network.target what precisely is required is not obvious and can be different things depending on local configuration.
A robust system boots up independently of external services. More specifically, if a network DHCP server does not react this should not slow down boot on most setups, but only for those where network connectivity is strictly needed (for example, because the host actually boots from the network).
network.target by default does not have much meaning. If a service orders itself after it this will have little effect, by default. It is up to the admin to actually fill
network.target with sense, i.e. order a service before it that waits until the "network is up" based on the policy the admin chooses, based on the needs of the software that shall be ordered after it and its configuration. By defaulting to the empty meaning we hence provide a speedy boot that is not slowed down by external factors for the majority of cases, but by allowing
network.target to be defined by the administrator as he likes any policy is implementable instead.
Cut the crap! How do I make network.target work for me?
Well, that depends on your setup and the services you plan to run after it (see above). If you use NetworkManager one option is to enable
systemctl enable NetworkManager-wait-online.service
This will ensure that all configured network devices are up and have an IP address assigned before boot continues. This service will time out after 90s. Enabling this service might considerably delay your boot even if the timeout is not reached.
Alternatively, you can write your own service file wrapping any other program or script you like and simply order it before
network.target (with Before=). Your service also needs to pull in
network.target (with Wants=), to indicate that it implements this target.
What does this mean for me, a Developer?
If you are a developer, instead of wondering what to do about
network.target, please just fix your program to be friendly to dynamically changing network configuration. That way you will make your users happy because things just start to work, and you will get fewer bug reports as your stuff is just rock solid. You also make the boot faster for your users, as they don't have to delay arbitrary services for the network anymore (which is particularly annoying for folks with slow address assignment replies from a DHCP server).
Here are a couple of possible approaches:
- Watch rtnetlink and react properly to network configuration changes as they happen. This is usually the nicest solution, but not always the easiest.
- If you write a server: listen on 0.0.0.0 and 127.0.0.1 only. These pseudo-addresses are unconditionally available. If you always bind to these addresses you will have code that doesn't have to react to network changes, as all you listen on is catch-all and private addresses.
- If you write a server: if you want to listen on other, explicitly configured addresses, consider using the IP_FREEBIND sockopt functionality of the Linux kernel. This allows your code to bind to an address even if it is not actually (yet or ever) configured locally. This also makes your code robust towards network configuration changes.