From 433b917977bb9e925cba89c6da37213e0fe52110 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 21 Feb 2020 08:13:21 -0800 Subject: [PATCH] interfaces, cmd/tsshd: move interface lookup from tsshd to its own package For reuse by derper, etc. Signed-off-by: Brad Fitzpatrick --- cmd/tsshd/tsshd.go | 50 ++---------------------------- interfaces/interfaces.go | 58 +++++++++++++++++++++++++++++++++++ interfaces/interfaces_test.go | 31 +++++++++++++++++++ 3 files changed, 92 insertions(+), 47 deletions(-) create mode 100644 interfaces/interfaces.go create mode 100644 interfaces/interfaces_test.go diff --git a/cmd/tsshd/tsshd.go b/cmd/tsshd/tsshd.go index b96df803a..d405472a2 100644 --- a/cmd/tsshd/tsshd.go +++ b/cmd/tsshd/tsshd.go @@ -25,7 +25,6 @@ import ( "net" "os" "os/exec" - "strings" "syscall" "time" "unsafe" @@ -33,6 +32,7 @@ import ( "github.com/gliderlabs/ssh" "github.com/kr/pty" gossh "golang.org/x/crypto/ssh" + "tailscale.com/interfaces" ) var ( @@ -57,7 +57,7 @@ func main() { warned := false for { - addr, iface, err := tailscaleInterface() + addr, iface, err := interfaces.Tailscale() if err != nil { log.Fatalf("listing interfaces: %v", err) } @@ -87,50 +87,6 @@ func main() { } -// tailscaleInterface returns an err on a fatal problem, and all zero values -// if no suitable inteface is found. -func tailscaleInterface() (net.IP, *net.Interface, error) { - ifs, err := net.Interfaces() - if err != nil { - return nil, nil, err - } - for _, iface := range ifs { - if !maybeTailscaleInterfaceName(iface.Name) { - continue - } - addrs, err := iface.Addrs() - if err != nil { - continue - } - for _, a := range addrs { - if ipnet, ok := a.(*net.IPNet); ok && isTailscaleIP(ipnet.IP) { - return ipnet.IP, &iface, nil - } - } - } - return nil, nil, nil -} - -// maybeTailscaleInterfaceName reports whether s is an interface -// name that might be used by Tailscale. -func maybeTailscaleInterfaceName(s string) bool { - return strings.HasPrefix(s, "wg") || - strings.HasPrefix(s, "ts") || - strings.HasPrefix(s, "tailscale") -} - -func isTailscaleIP(ip net.IP) bool { - return cgNAT.Contains(ip) -} - -var cgNAT = func() *net.IPNet { - _, ipNet, err := net.ParseCIDR("100.64.0.0/10") - if err != nil { - panic(err) - } - return ipNet -}() - func handleSSH(s ssh.Session) { user := s.User() addr := s.RemoteAddr() @@ -140,7 +96,7 @@ func handleSSH(s ssh.Session) { s.Exit(1) return } - if !isTailscaleIP(ta.IP) { + if !interfaces.IsTailscaleIP(ta.IP) { log.Printf("tsshd: rejecting non-Tailscale addr %v", ta.IP) s.Exit(1) return diff --git a/interfaces/interfaces.go b/interfaces/interfaces.go new file mode 100644 index 000000000..2493b3155 --- /dev/null +++ b/interfaces/interfaces.go @@ -0,0 +1,58 @@ +// 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 interfaces contains helpers for looking up system network interfaces. +package interfaces + +import ( + "net" + "strings" +) + +// Tailscale returns the current machine's Tailscale interface, if any. +// If none is found, all zero values are returned. +// A non-nil error is only returned on a problem listing the system interfaces. +func Tailscale() (net.IP, *net.Interface, error) { + ifs, err := net.Interfaces() + if err != nil { + return nil, nil, err + } + for _, iface := range ifs { + if !maybeTailscaleInterfaceName(iface.Name) { + continue + } + addrs, err := iface.Addrs() + if err != nil { + continue + } + for _, a := range addrs { + if ipnet, ok := a.(*net.IPNet); ok && IsTailscaleIP(ipnet.IP) { + return ipnet.IP, &iface, nil + } + } + } + return nil, nil, nil +} + +// maybeTailscaleInterfaceName reports whether s is an interface +// name that might be used by Tailscale. +func maybeTailscaleInterfaceName(s string) bool { + return strings.HasPrefix(s, "wg") || + strings.HasPrefix(s, "ts") || + strings.HasPrefix(s, "tailscale") +} + +// IsTailscaleIP reports whether ip is an IP in a range used by +// Tailscale virtual network interfaces. +func IsTailscaleIP(ip net.IP) bool { + return cgNAT.Contains(ip) +} + +var cgNAT = func() *net.IPNet { + _, ipNet, err := net.ParseCIDR("100.64.0.0/10") + if err != nil { + panic(err) + } + return ipNet +}() diff --git a/interfaces/interfaces_test.go b/interfaces/interfaces_test.go new file mode 100644 index 000000000..ad36c437c --- /dev/null +++ b/interfaces/interfaces_test.go @@ -0,0 +1,31 @@ +// 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 interfaces + +import ( + "net" + "testing" +) + +func TestIsTailscaleIP(t *testing.T) { + tests := []struct { + ip string + want bool + }{ + {"100.81.251.94", true}, + {"8.8.8.8", false}, + } + for _, tt := range tests { + ip := net.ParseIP(tt.ip) + if ip == nil { + t.Fatalf("failed to parse IP %q", tt.ip) + } + got := IsTailscaleIP(ip) + if got != tt.want { + t.Errorf("F(%q) = %v; want %v", tt.ip, got, tt.want) + } + } + +}