The netctl@ service now yields before obtaining an IP address. This
prevents holding up network.target unnecessarily. Waiting for profiles
to obtain an IP address is possible through
1) The netctl-wait-online service
This service waits until all enabled profiles have obtained an address.
It is ordered before network-online.target so that this target is now
correctly implemented in netctl.
2) The wait-online <PROFILE> subcommand to netctl
This command waits for a started profile to obtain an address.
---
contrib/bash-completion | 4 ++--
contrib/zsh-completion | 3 ++-
docs/netctl.1.txt | 4 ++++
docs/netctl.special.7.txt | 16 +++++++++++---
services/netctl-ifplugd@.service | 1 +
services/netctl-wait-online.service | 13 +++++++++++
services/netctl@.service | 3 ++-
src/lib/connections/mobile_ppp | 3 +++
src/lib/connections/pppoe | 1 +
src/lib/globals | 6 ++++++
src/lib/ip | 2 ++
src/lib/network | 40 ++++++++++++++++++++++++++++------
src/netctl.in | 43 ++++++++++++++++++++++++-------------
13 files changed, 111 insertions(+), 28 deletions(-)
create mode 100644 services/netctl-wait-online.service
diff --git a/contrib/bash-completion b/contrib/bash-completion
index 2ab6acb..5f78355 100644
--- a/contrib/bash-completion
+++ b/contrib/bash-completion
@@ -23,10 +23,10 @@ _netctl()
case $COMP_CWORD in
1)
- COMPREPLY=( $(compgen -W "--help --version list store restore stop-all start stop restart switch-to is-active status enable disable reenable is-enabled edit" -- "$cur") )
+ COMPREPLY=( $(compgen -W "--help --version list store restore stop-all start stop restart switch-to is-active status enable disable reenable is-enabled edit wait-online" -- "$cur") )
;;
2)
- [[ ${COMP_WORDS[COMP_CWORD-1]} = @(start|stop|restart|switch-to|is-active|status|enable|disable|reenable|is-enabled|edit) ]] &&
+ [[ ${COMP_WORDS[COMP_CWORD-1]} = @(start|stop|restart|switch-to|is-active|status|enable|disable|reenable|is-enabled|edit|wait-online) ]] &&
mapfile -t COMPREPLY < <(IFS=$'\n'; compgen -W "$(_netctl_profiles)" -- "$cur")
;;
esac
diff --git a/contrib/zsh-completion b/contrib/zsh-completion
index f583526..00cef81 100644
--- a/contrib/zsh-completion
+++ b/contrib/zsh-completion
@@ -11,7 +11,7 @@ _wireless_interfaces() {
(( $+function[_netctl_command] )) ||
_netctl_command() {
- [[ $words[1] = (start|stop|restart|switch-to|is-active|status|enable|disable|reenable|is-enabled|edit) ]] &&
+ [[ $words[1] = (start|stop|restart|switch-to|is-active|status|enable|disable|reenable|is-enabled|edit|wait-online) ]] &&
compadd "${(f)$(find -L /etc/netctl -maxdepth 1 -type f -not -name '.*' -not -name '*~' -not -name '*.conf' -not -name '*.service' -printf "%f\n")}"
}
@@ -34,6 +34,7 @@ _netctl_commands() {
'reenable:Reenable the systemd unit for a profile'
'is-enabled:Check whether a profile is enabled'
'edit:Edit a profile'
+ 'wait-online:Wait for a profile to finish connecting'
)
_describe "netctl commands" _commands
}
diff --git a/docs/netctl.1.txt b/docs/netctl.1.txt
index 4fe87ca..0f63de4 100644
--- a/docs/netctl.1.txt
+++ b/docs/netctl.1.txt
@@ -84,6 +84,10 @@ The following commands are understood:
Open the file of the specified profile in an editor. This does not
reenable or restart any profiles.
+*wait-online [+PROFILE+]*::
+ Wait until the interface of the profile has a routable IP address of
+ some kind.
+
EXIT STATUS
-----------
diff --git a/docs/netctl.special.7.txt b/docs/netctl.special.7.txt
index a4e0911..da1c2ec 100644
--- a/docs/netctl.special.7.txt
+++ b/docs/netctl.special.7.txt
@@ -8,7 +8,8 @@ netctl.special - Special netctl systemd units
SYNOPSIS
--------
-netctl.service, netctl-auto.service, netctl-ifplugd.service
+netctl.service, netctl-auto.service, netctl-ifplugd.service,
+netctl-wait-online.service
DESCRIPTION
@@ -23,7 +24,7 @@ SPECIAL UNITS
netctl.service::
When started, this unit tries to start the profiles that were
running when the unit was last stopped. In some cases, the interface
- a profile binds to might not be available yet, when netctl.service
+ a profile binds to might not be available yet, when 'netctl.service'
tries to bring a profile up. A simple, hackish, solution is to do:
--------------------------------------------------------------------
echo "[[ -t 0 ]] || sleep 3" > /etc/netctl/interfaces/<interface>
@@ -56,7 +57,16 @@ netctl-ifplugd@<interface>.service::
This unit starts ifplugd on the interface it is used for. It will
try to start a netctl profile whenever a cable is plugged into the
interface and stop the profile when the cable is unplugged. Note
- that this unit does not provide network.target.
+ that this unit does not provide 'network.target'.
+
+netctl-wait-online.service::
+ When activated, this unit waits for all enabled netctl profiles to
+ come online. Enabling this unit causes 'network-online.target' to
+ only be reached once all enabled netctl profiles are fully
+ connected. The maximum time, in seconds, to wait for profiles can be
+ passed to this unit via 'TIMEOUT_ONLINE='. The default value is
+ +120+, but the service itself may time out sooner. If a timeout
+ occurs, the service enters a failed state.
SEE ALSO
diff --git a/services/netctl-ifplugd@.service b/services/netctl-ifplugd@.service
index 2cea97b..76993b9 100644
--- a/services/netctl-ifplugd@.service
+++ b/services/netctl-ifplugd@.service
@@ -5,6 +5,7 @@ BindsTo=sys-subsystem-net-devices-%i.device
After=sys-subsystem-net-devices-%i.device network-pre.target
[Service]
+NotifyAccess=all
ExecStart=/usr/bin/ifplugd -i %I -r /etc/ifplugd/netctl.action -bfIns
[Install]
diff --git a/services/netctl-wait-online.service b/services/netctl-wait-online.service
new file mode 100644
index 0000000..64ca06d
--- /dev/null
+++ b/services/netctl-wait-online.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Wait for the enabled netctl profiles to come online
+Documentation=man:netctl.special(7)
+After=network.target
+Before=network-online.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/usr/lib/network/network wait-online
+
+[Install]
+WantedBy=network-online.target
diff --git a/services/netctl@.service b/services/netctl@.service
index b8a19c2..d9c637d 100644
--- a/services/netctl@.service
+++ b/services/netctl@.service
@@ -6,7 +6,8 @@ Before=network.target netctl.service
Wants=network.target
[Service]
-Type=oneshot
+Type=notify
+NotifyAccess=all
RemainAfterExit=yes
ExecStart=/usr/lib/network/network start %I
ExecStop=/usr/lib/network/network stop %I
diff --git a/src/lib/connections/mobile_ppp b/src/lib/connections/mobile_ppp
index 78851f3..0890373 100644
--- a/src/lib/connections/mobile_ppp
+++ b/src/lib/connections/mobile_ppp
@@ -12,6 +12,9 @@ quote_word() {
mobile_ppp_up() {
local options_dir="$STATE_DIR/mobile_ppp.$Interface.$Profile"
+
+ network_ready
+
mkdir -p "$options_dir"
if [[ -z $ChatScript ]]; then
ChatScript="$options_dir/modem.chat"
diff --git a/src/lib/connections/pppoe b/src/lib/connections/pppoe
index 36746a8..15e4db5 100644
--- a/src/lib/connections/pppoe
+++ b/src/lib/connections/pppoe
@@ -18,6 +18,7 @@ pppoe_up() {
report_error "Failed to bring interface '$Interface' up"
return 1
fi
+ network_ready
mkdir -p "$(dirname "$options")"
cat >> "$options" << EOF
diff --git a/src/lib/globals b/src/lib/globals
index a9e9751..f4e64a3 100644
--- a/src/lib/globals
+++ b/src/lib/globals
@@ -141,6 +141,12 @@ sd_call() {
systemctl $command $(systemd-escape --template=netctl@.service "$@")
}
+## Retrieves the status string from the unit for a profile
+# $1: profile name
+sd_status_text() {
+ sd_call status "$1" | sed -n 's/^ *Status: "\(.*\)"$/\1/p'
+}
+
# Set a restrictive umask
do_readable :
diff --git a/src/lib/ip b/src/lib/ip
index 2dc67fc..71c7fa2 100644
--- a/src/lib/ip
+++ b/src/lib/ip
@@ -38,6 +38,8 @@ resolvconf_add() {
ip_set() {
local addr line route interface_sysctl=${Interface/.//}
+ network_ready
+
if [[ -z $IP && -z $IP6 ]]; then
report_error "Neither IP, nor IP6 was specified"
return 1
diff --git a/src/lib/network b/src/lib/network
index adea2a2..8a43ce2 100755
--- a/src/lib/network
+++ b/src/lib/network
@@ -56,17 +56,33 @@ bring_interface_down() {
timeout_wait "${TimeoutUp:-5}" '! interface_is_up "$interface"'
}
+## Indicate that the network stack for the profile is up
+network_ready() {
+ if ! is_yes "${NETWORK_READY:-no}"; then
+ do_debug systemd-notify --pid --ready
+ NETWORK_READY=yes
+ fi
+}
+
+## Describe the status of the service for the profile
+# $1: status string, should be "online" when the profile gets connected
+network_status() {
+ do_debug systemd-notify --pid --status="$1"
+}
+
-if [[ $# -ne 2 || $1 != @(start|stop) ]]; then
- exit_error "Usage: $0 {start|stop} <profile>"
-fi
ensure_root netctl
# Ensure we are not in a transient directory
cd /
-# Expose the profile name
-Profile=$2
-load_profile "$Profile"
+if [[ $# -eq 2 && $1 == @(start|stop) ]]; then
+ # Expose the profile name
+ Profile=$2
+ load_profile "$Profile"
+elif [[ $# -ne 1 || $1 != "wait-online" ]]; then
+ exit_error "Usage: $0 {start|stop|wait-online} [profile]"
+fi
+
case $1 in
start)
report_notice "Starting network profile '$Profile'..."
@@ -79,6 +95,7 @@ case $1 in
report_error "Failed to bring the network up for profile '$Profile'"
exit 1
fi
+ network_ready
# Sandbox the eval
if ! ( eval $ExecUpPost ); then
report_error "ExecUpPost failed for network profile '$Profile'"
@@ -86,6 +103,7 @@ case $1 in
"${Connection}_down"
exit 1
fi
+ network_status "online"
report_notice "Started network profile '$Profile'"
;;
stop)
@@ -100,6 +118,7 @@ case $1 in
report_error "Failed to bring the network down for profile '$Profile'"
exit 1
fi
+ network_status ""
if is_interface "$Interface" && interface_is_up "$Interface" && \
! is_yes "${ForceConnect:-no}"; then
report_error "The interface of network profile '$Profile' did not go down"
@@ -107,6 +126,15 @@ case $1 in
fi
report_notice "Stopped network profile '$Profile'"
;;
+ wait-online)
+ mapfile -t Profiles < <(list_profiles)
+ i=0
+ ## Wait for all enabled profiles to come online within a single timeout
+ timeout_wait "${TIMEOUT_ONLINE:-120}" \
+ '! until [[ $(sd_call is-enabled "${Profiles[i]}") == "enabled" &&
+ $(sd_status_text "${Profiles[i]}") != "online" ]]; do
+ (( ++i < ${#Profiles[@]} )) || return 0; done'
+ ;;
esac
diff --git a/src/netctl.in b/src/netctl.in
index 0df3759..fcb40f4 100644
--- a/src/netctl.in
+++ b/src/netctl.in
@@ -9,21 +9,22 @@ Usage: netctl {COMMAND} [PROFILE]
[--help|--version]
Commands:
- list List available profiles
- store Save which profiles are active
- restore Load saved profiles
- stop-all Stops all profiles
- start [PROFILE] Start a profile
- stop [PROFILE] Stop a profile
- restart [PROFILE] Restart a profile
- switch-to [PROFILE] Switch to a profile
- is-active [PROFILE] Check whether a profile is active
- status [PROFILE] Show runtime status of a profile
- enable [PROFILE] Enable the systemd unit for a profile
- disable [PROFILE] Disable the systemd unit for a profile
- reenable [PROFILE] Reenable the systemd unit for a profile
- is-enabled [PROFILE] Check whether a profile is enabled
- edit [PROFILE] Edit a profile
+ list List available profiles
+ store Save which profiles are active
+ restore Load saved profiles
+ stop-all Stops all profiles
+ start [PROFILE] Start a profile
+ stop [PROFILE] Stop a profile
+ restart [PROFILE] Restart a profile
+ switch-to [PROFILE] Switch to a profile
+ is-active [PROFILE] Check whether a profile is active
+ status [PROFILE] Show runtime status of a profile
+ enable [PROFILE] Enable the systemd unit for a profile
+ disable [PROFILE] Disable the systemd unit for a profile
+ reenable [PROFILE] Reenable the systemd unit for a profile
+ is-enabled [PROFILE] Check whether a profile is enabled
+ edit [PROFILE] Edit a profile
+ wait-online [PROFILE] Wait for a profile to finish connecting
END
}
@@ -122,6 +123,16 @@ unit_disable() {
do_debug rm "$unit"
}
+wait_online() {
+ local profile="$1"
+ if sd_call "is-active --quiet" "$profile"; then
+ timeout_wait "${TIMEOUT_ONLINE:-120}" \
+ '[[ $(sd_status_text "$profile") == "online" ]]'
+ else
+ return 1
+ fi
+}
+
case $# in
1)
@@ -162,6 +173,8 @@ case $# in
fi;;
edit)
exec ${EDITOR:-nano} "$PROFILE_DIR/$2";;
+ wait-online)
+ wait_online "$2";;
*)
exit_error "$(usage)";;
esac;;
--
2.10.0