diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 7b8705ba8..1bd1bef11 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -14,6 +14,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+ + L github.com/mdlayher/sdnotify from tailscale.com/util/systemd github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli 💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+ @@ -72,6 +73,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/structs from tailscale.com/control/controlclient+ W tailscale.com/util/endian from tailscale.com/net/netns tailscale.com/util/lineread from tailscale.com/control/controlclient+ + tailscale.com/util/systemd from tailscale.com/control/controlclient+ tailscale.com/version from tailscale.com/cmd/tailscale/cli+ tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+ tailscale.com/wgengine from tailscale.com/ipn diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index fa7ab7899..2e2a56c9a 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -19,6 +19,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+ + L github.com/mdlayher/sdnotify from tailscale.com/util/systemd 💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+ 💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+ github.com/tailscale/wireguard-go/device/tokenbucket from github.com/tailscale/wireguard-go/device @@ -82,6 +83,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/lineread from tailscale.com/control/controlclient+ tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver tailscale.com/util/racebuild from tailscale.com/logpolicy + tailscale.com/util/systemd from tailscale.com/control/controlclient+ tailscale.com/version from tailscale.com/cmd/tailscaled+ tailscale.com/version/distro from tailscale.com/control/controlclient+ tailscale.com/wgengine from tailscale.com/cmd/tailscaled+ diff --git a/cmd/tailscaled/tailscaled.service b/cmd/tailscaled/tailscaled.service index a6680fdf1..71dc89f1b 100644 --- a/cmd/tailscaled/tailscaled.service +++ b/cmd/tailscaled/tailscaled.service @@ -18,6 +18,7 @@ StateDirectory=tailscale StateDirectoryMode=0750 CacheDirectory=tailscale CacheDirectoryMode=0750 +Type=notify [Install] WantedBy=multi-user.target diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 36f5e45c9..4d8d6d577 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -44,6 +44,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/types/opt" "tailscale.com/types/structs" + "tailscale.com/util/systemd" "tailscale.com/version" "tailscale.com/wgengine/filter" ) @@ -309,6 +310,7 @@ func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags Logi if mustregen { _, url, err = c.doLogin(ctx, t, flags, true, url) } + return url, err } @@ -329,6 +331,7 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags, if expired { c.logf("Old key expired -> regen=true") + systemd.Status("key expired; run 'tailscale up' to authenticate") regen = true } if (flags & LoginInteractive) != 0 { diff --git a/go.mod b/go.mod index ea7caa61a..1d3deb67c 100644 --- a/go.mod +++ b/go.mod @@ -13,12 +13,13 @@ require ( github.com/go-ole/go-ole v1.2.4 github.com/godbus/dbus/v5 v5.0.3 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e - github.com/google/go-cmp v0.4.0 + github.com/google/go-cmp v0.5.0 github.com/goreleaser/nfpm v1.1.10 github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 github.com/klauspost/compress v1.10.10 github.com/kr/pty v1.1.1 github.com/mdlayher/netlink v1.1.0 + github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a github.com/miekg/dns v1.1.30 github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/peterbourgon/ff/v2 v2.0.0 diff --git a/go.sum b/go.sum index c62104f23..47ef6c628 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0 h1:BW6OvS3kpT5UEPbCZ+KyX/OB4Ks9/MNMhWjqPPkZxsE= github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= @@ -68,6 +70,8 @@ github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a h1:wMv2mvcHRH4jqIxaVL5t6gSq1hjPiaWH7TOcA0Z+uNo= +github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4= github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo= github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= @@ -93,6 +97,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be h1:ZKe3kVGbu/goUVxXcaCPbQ4b0STQ5NsCpG90CG6mw/c= github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI= +github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f h1:KMx58dbn2YCutzOvjNHgmvbwQH7nGE8H+J42Nenjl/M= +github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4= github.com/tailscale/depaware v0.0.0-20201210233412-71b54857b5d9 h1:IquU2Mhy4Q+xUYoRLHMiEamd80OShhYXhTNywEbQu3g= github.com/tailscale/depaware v0.0.0-20201210233412-71b54857b5d9/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI= github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw= @@ -134,7 +140,7 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -161,6 +167,7 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 130ebb375..2cd4bf92c 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -34,6 +34,7 @@ import ( "tailscale.com/smallzstd" "tailscale.com/types/logger" "tailscale.com/util/pidowner" + "tailscale.com/util/systemd" "tailscale.com/version" "tailscale.com/wgengine" ) @@ -589,6 +590,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( }) } + systemd.Ready() for i := 1; ctx.Err() == nil; i++ { var c net.Conn var err error diff --git a/ipn/local.go b/ipn/local.go index 68de79616..52d24e67f 100644 --- a/ipn/local.go +++ b/ipn/local.go @@ -29,6 +29,7 @@ import ( "tailscale.com/types/empty" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/util/systemd" "tailscale.com/version" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" @@ -1322,6 +1323,8 @@ func (b *LocalBackend) enterState(newState State) { notify := b.notify bc := b.c networkUp := b.prevIfState.AnyInterfaceUp() + activeLogin := b.activeLogin + authURL := b.authURL b.mu.Unlock() if state == newState { @@ -1339,6 +1342,7 @@ func (b *LocalBackend) enterState(newState State) { switch newState { case NeedsLogin: + systemd.Status("Needs login: %s", authURL) b.blockEngineUpdates(true) fallthrough case Stopped: @@ -1346,12 +1350,20 @@ func (b *LocalBackend) enterState(newState State) { if err != nil { b.logf("Reconfig(down): %v", err) } + + if authURL == "" { + systemd.Status("Stopped; run 'tailscale up' to log in") + } case Starting, NeedsMachineAuth: b.authReconfig() // Needed so that UpdateEndpoints can run b.e.RequestStatus() case Running: - break + var addrs []string + for _, addr := range b.netMap.Addresses { + addrs = append(addrs, addr.IP.String()) + } + systemd.Status("Connected; %s; %s", activeLogin, strings.Join(addrs, " ")) default: b.logf("[unexpected] unknown newState %#v", newState) } diff --git a/util/systemd/doc.go b/util/systemd/doc.go new file mode 100644 index 000000000..9830685bd --- /dev/null +++ b/util/systemd/doc.go @@ -0,0 +1,14 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package systemd contains a minimal wrapper around systemd-notify to enable +applications to signal readiness and status to systemd. + +This package will only have effect on Linux systems running Tailscale in a +systemd unit with the Type=notify flag set. On other operating systems (or +when running in a Linux distro without being run from inside systemd) this +package will become a no-op. +*/ +package systemd diff --git a/util/systemd/systemd_linux.go b/util/systemd/systemd_linux.go new file mode 100644 index 000000000..133bf520f --- /dev/null +++ b/util/systemd/systemd_linux.go @@ -0,0 +1,75 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package systemd + +import ( + "log" + "sync" + + "github.com/mdlayher/sdnotify" +) + +var getNotifyOnce struct { + sync.Once + v *sdnotify.Notifier +} + +type logOnce struct { + sync.Once +} + +func (l *logOnce) logf(format string, args ...interface{}) { + l.Once.Do(func() { + log.Printf(format, args...) + }) +} + +var ( + readyOnce = &logOnce{} + statusOnce = &logOnce{} +) + +func notifier() *sdnotify.Notifier { + getNotifyOnce.Do(func() { + var err error + getNotifyOnce.v, err = sdnotify.New() + if err != nil { + log.Printf("systemd: systemd-notifier error: %v", err) + } + }) + return getNotifyOnce.v +} + +// Ready signals readiness to systemd. This will unblock service dependents from starting. +func Ready() { + err := notifier().Notify(sdnotify.Ready) + if err != nil { + readyOnce.logf("systemd: error notifying: %v", err) + } +} + +// Status sends a single line status update to systemd so that information shows up +// in systemctl output. For example: +// +// $ systemctl status tailscale +// ● tailscale.service - Tailscale client daemon +// Loaded: loaded (/nix/store/qc312qcy907wz80fqrgbbm8a9djafmlg-unit-tailscale.service/tailscale.service; enabled; vendor preset: enabled) +// Active: active (running) since Tue 2020-11-24 17:54:07 EST; 13h ago +// Main PID: 26741 (.tailscaled-wra) +// Status: "Connected; user@host.domain.tld; 100.101.102.103" +// IP: 0B in, 0B out +// Tasks: 22 (limit: 4915) +// Memory: 30.9M +// CPU: 2min 38.469s +// CGroup: /system.slice/tailscale.service +// └─26741 /nix/store/sv6cj4mw2jajm9xkbwj07k29dj30lh0n-tailscale-date.20200727/bin/tailscaled --port 41641 +func Status(format string, args ...interface{}) { + err := notifier().Notify(sdnotify.Statusf(format, args...)) + if err != nil { + statusOnce.logf("systemd: error notifying: %v", err) + } +} diff --git a/util/systemd/systemd_nonlinux.go b/util/systemd/systemd_nonlinux.go new file mode 100644 index 000000000..b6dcd9dad --- /dev/null +++ b/util/systemd/systemd_nonlinux.go @@ -0,0 +1,10 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !linux + +package systemd + +func Ready() {} +func Status(string, ...interface{}) {}