From 29bf329f6acb16bd772eb30ae961c1f1f93bb0b9 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 2 May 2024 23:19:05 +0700 Subject: [PATCH] cmd/cli: fix systemd-networkd-wait-online blocks ctrld starts The systemd-networkd-wait-online is only required if systemd-networkd is managing any interfaces. Otherwise, it will hang and block ctrld from starting. See: https://github.com/systemd/systemd/issues/23304 --- cmd/cli/prog_linux.go | 29 ++++++++++++++++++++++- cmd/cli/prog_linux_test.go | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 cmd/cli/prog_linux_test.go diff --git a/cmd/cli/prog_linux.go b/cmd/cli/prog_linux.go index cdb3c0e..0af906d 100644 --- a/cmd/cli/prog_linux.go +++ b/cmd/cli/prog_linux.go @@ -1,7 +1,12 @@ package cli import ( + "bufio" + "bytes" + "io" "os" + "os/exec" + "strings" "github.com/kardianos/service" @@ -24,12 +29,34 @@ func setDependencies(svc *service.Config) { "After=network-online.target", "Wants=NetworkManager-wait-online.service", "After=NetworkManager-wait-online.service", - "Wants=systemd-networkd-wait-online.service", "Wants=nss-lookup.target", "After=nss-lookup.target", } + if out, _ := exec.Command("networkctl", "--no-pager").CombinedOutput(); len(out) > 0 { + if wantsSystemDNetworkdWaitOnline(bytes.NewReader(out)) { + svc.Dependencies = append(svc.Dependencies, "Wants=systemd-networkd-wait-online.service") + } + } } func setWorkingDirectory(svc *service.Config, dir string) { svc.WorkingDirectory = dir } + +// wantsSystemDNetworkdWaitOnline reports whether "systemd-networkd-wait-online" service +// is required to be added to ctrld dependencies services. +// The input reader r is the output of "networkctl --no-pager" command. +func wantsSystemDNetworkdWaitOnline(r io.Reader) bool { + scanner := bufio.NewScanner(r) + // Skip header + scanner.Scan() + configured := false + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) > 0 && fields[len(fields)-1] == "configured" { + configured = true + break + } + } + return configured +} diff --git a/cmd/cli/prog_linux_test.go b/cmd/cli/prog_linux_test.go new file mode 100644 index 0000000..ecc4cd5 --- /dev/null +++ b/cmd/cli/prog_linux_test.go @@ -0,0 +1,48 @@ +package cli + +import ( + "io" + "strings" + "testing" +) + +const ( + networkctlUnmanagedOutput = `IDX LINK TYPE OPERATIONAL SETUP + 1 lo loopback carrier unmanaged + 2 wlp0s20f3 wlan routable unmanaged + 3 tailscale0 none routable unmanaged + 4 br-9ac33145e060 bridge no-carrier unmanaged + 5 docker0 bridge no-carrier unmanaged + +5 links listed. +` + networkctlManagedOutput = `IDX LINK TYPE OPERATIONAL SETUP + 1 lo loopback carrier unmanaged + 2 wlp0s20f3 wlan routable configured + 3 tailscale0 none routable unmanaged + 4 br-9ac33145e060 bridge no-carrier unmanaged + 5 docker0 bridge no-carrier unmanaged + +5 links listed. +` +) + +func Test_wantsSystemDNetworkdWaitOnline(t *testing.T) { + tests := []struct { + name string + r io.Reader + required bool + }{ + {"unmanaged", strings.NewReader(networkctlUnmanagedOutput), false}, + {"managed", strings.NewReader(networkctlManagedOutput), true}, + {"empty", strings.NewReader(""), false}, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + if required := wantsSystemDNetworkdWaitOnline(tc.r); required != tc.required { + t.Errorf("wants %v got %v", tc.required, required) + } + }) + } +}