Merge branch 'master' of github.com:tailscale/tailscale into HEAD

This commit is contained in:
David Crawshaw 2020-02-13 10:12:06 -05:00
commit cbfef0c8b7
20 changed files with 557 additions and 42 deletions

View File

@ -28,7 +28,22 @@ jobs:
- name: Basic build - name: Basic build
run: go build ./cmd/... run: go build ./cmd/...
- name: Test build - name: macOS build
env:
GOOS: darwin
GOARCH: amd64
run: go build ./cmd/...
- name: Windows build
env:
GOOS: windows
GOARCH: amd64
run: go build ./cmd/...
- name: Cross-compile tests for other geese
run: ./test.sh
- name: Run tests on linux
run: go test ./... run: go test ./...
- uses: k0kubun/action-slack@v2.0.0 - uses: k0kubun/action-slack@v2.0.0
@ -45,3 +60,4 @@ jobs:
env: env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push' if: failure() && github.event_name == 'push'

View File

@ -20,11 +20,14 @@
"tailscale.com/ipn/ipnserver" "tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/wgengine" "tailscale.com/wgengine"
"tailscale.com/wgengine/magicsock"
) )
func main() { func main() {
fake := getopt.BoolLong("fake", 0, "fake tunnel+routing instead of tuntap") fake := getopt.BoolLong("fake", 0, "fake tunnel+routing instead of tuntap")
debug := getopt.StringLong("debug", 0, "", "Address of debug server") debug := getopt.StringLong("debug", 0, "", "Address of debug server")
tunname := getopt.StringLong("tun", 0, "ts0", "tunnel interface name")
listenport := getopt.Uint16Long("port", 'p', magicsock.DefaultPort, "WireGuard port (0=autoselect)")
logf := wgengine.RusagePrefixLog(log.Printf) logf := wgengine.RusagePrefixLog(log.Printf)
@ -47,7 +50,7 @@ func main() {
if *fake { if *fake {
e, err = wgengine.NewFakeUserspaceEngine(logf, 0, false) e, err = wgengine.NewFakeUserspaceEngine(logf, 0, false)
} else { } else {
e, err = wgengine.NewUserspaceEngine(logf, "ts0", 0, false) e, err = wgengine.NewUserspaceEngine(logf, *tunname, *listenport, false)
} }
if err != nil { if err != nil {
log.Fatalf("wgengine.New: %v\n", err) log.Fatalf("wgengine.New: %v\n", err)

205
cmd/tsshd/tsshd.go Normal file
View File

@ -0,0 +1,205 @@
// 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 !windows
// The tsshd binary is an SSH server that accepts connections
// from anybody on the same Tailscale network.
//
// It does not use passwords or SSH public key.
//
// Any user name is accepted; users are logged in as whoever is
// running this daemon.
//
// Warning: use at your own risk. This code has had very few eyeballs
// on it.
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"strings"
"syscall"
"time"
"unsafe"
"github.com/gliderlabs/ssh"
"github.com/kr/pty"
gossh "golang.org/x/crypto/ssh"
)
var (
port = flag.Int("port", 2200, "port to listen on")
hostKey = flag.String("hostkey", "", "SSH host key")
)
func main() {
flag.Parse()
if *hostKey == "" {
log.Fatalf("missing required --hostkey")
}
hostKey, err := ioutil.ReadFile(*hostKey)
if err != nil {
log.Fatal(err)
}
signer, err := gossh.ParsePrivateKey(hostKey)
if err != nil {
log.Printf("failed to parse SSH host key: %v", err)
return
}
warned := false
for {
addr, iface, err := tailscaleInterface()
if err != nil {
log.Fatalf("listing interfaces: %v", err)
}
if addr == nil {
if !warned {
log.Printf("no tailscale interface found; polling until one is available")
warned = true
}
// TODO: use netlink or other OS-specific mechanism to efficiently
// wait for change in interfaces. Polling every N seconds is good enough
// for now.
time.Sleep(5 * time.Second)
continue
}
warned = false
listen := net.JoinHostPort(addr.String(), fmt.Sprint(*port))
log.Printf("tailscale ssh server listening on %v, %v", iface.Name, listen)
s := &ssh.Server{
Addr: listen,
Handler: handleSSH,
}
s.AddHostKey(signer)
err = s.ListenAndServe()
log.Fatalf("tailscale sshd failed: %v", err)
}
}
// 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()
ta, ok := addr.(*net.TCPAddr)
if !ok {
log.Printf("tsshd: rejecting non-TCP addr %T %v", addr, addr)
s.Exit(1)
return
}
if !isTailscaleIP(ta.IP) {
log.Printf("tsshd: rejecting non-Tailscale addr %v", ta.IP)
s.Exit(1)
return
}
log.Printf("new session for %q from %v", user, ta)
defer log.Printf("closing session for %q from %v", user, ta)
ptyReq, winCh, isPty := s.Pty()
if !isPty {
fmt.Fprintf(s, "TODO scp etc")
s.Exit(1)
return
}
userWantsShell := len(s.Command()) == 0
if userWantsShell {
shell, err := shellOfUser(s.User())
if err != nil {
fmt.Fprintf(s, "failed to find shell: %v\n", err)
s.Exit(1)
return
}
cmd := exec.Command(shell)
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
f, err := pty.Start(cmd)
if err != nil {
log.Printf("running shell: %v", err)
s.Exit(1)
return
}
defer f.Close()
go func() {
for win := range winCh {
setWinsize(f, win.Width, win.Height)
}
}()
go func() {
io.Copy(f, s) // stdin
}()
io.Copy(s, f) // stdout
cmd.Process.Kill()
if err := cmd.Wait(); err != nil {
s.Exit(1)
}
s.Exit(0)
return
}
fmt.Fprintf(s, "TODO: args\n")
s.Exit(1)
}
func shellOfUser(user string) (string, error) {
// TODO
return "/bin/bash", nil
}
func setWinsize(f *os.File, w, h int) {
syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), uintptr(syscall.TIOCSWINSZ),
uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(h), uint16(w), 0, 0})))
}

7
go.mod
View File

@ -3,18 +3,23 @@ module tailscale.com
go 1.13 go 1.13
require ( require (
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gliderlabs/ssh v0.2.2
github.com/go-ole/go-ole v1.2.4 github.com/go-ole/go-ole v1.2.4
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/google/go-cmp v0.4.0 github.com/google/go-cmp v0.4.0
github.com/klauspost/compress v1.9.8 github.com/klauspost/compress v1.9.8
github.com/kr/pty v1.1.1
github.com/mdlayher/netlink v1.1.0 github.com/mdlayher/netlink v1.1.0
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3 github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3
github.com/tailscale/winipcfg-go v0.0.0-20200213045944-185b07f8233f
github.com/tailscale/wireguard-go v0.0.0-20200211020303-f39bc8eeee1b github.com/tailscale/wireguard-go v0.0.0-20200211020303-f39bc8eeee1b
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678 golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4
gortc.io/stun v1.22.1 gortc.io/stun v1.22.1
honnef.co/go/tools v0.0.1-2019.2.3 // indirect honnef.co/go/tools v0.0.1-2019.2.3 // indirect
) )

13
go.sum
View File

@ -1,10 +1,16 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4= github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29/go.mod h1:JYWahgHer+Z2xbsgHPtaDYVWzeHDminu+YIBWkxpCAY= github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29/go.mod h1:JYWahgHer+Z2xbsgHPtaDYVWzeHDminu+YIBWkxpCAY=
github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab h1:CMGzRRCjnD50RjUFSArBLuCxiDvdp7b8YPAcikBEQ+k= github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab h1:CMGzRRCjnD50RjUFSArBLuCxiDvdp7b8YPAcikBEQ+k=
github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab/go.mod h1:nfFtvHn2Hgs9G1u0/J6LHQv//EksNC+7G8vXmd1VTJ8= github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab/go.mod h1:nfFtvHn2Hgs9G1u0/J6LHQv//EksNC+7G8vXmd1VTJ8=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
@ -23,6 +29,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA= github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA=
github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
@ -34,6 +41,8 @@ github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwp
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3 h1:rdtXEo9yffOjh4vZQJw3heaY+ggXKp+zvMX5fihh6lI= github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3 h1:rdtXEo9yffOjh4vZQJw3heaY+ggXKp+zvMX5fihh6lI=
github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3/go.mod h1:STqf+YV0ADdzk4ejtXFsGqDpATP9JoL0OB+hiFQbkdE= github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3/go.mod h1:STqf+YV0ADdzk4ejtXFsGqDpATP9JoL0OB+hiFQbkdE=
github.com/tailscale/winipcfg-go v0.0.0-20200213045944-185b07f8233f h1:q2ynfOHxHaaMnkZ1YHswWeO6wEk7IyOnkFozytZ1ztc=
github.com/tailscale/winipcfg-go v0.0.0-20200213045944-185b07f8233f/go.mod h1:x880GWw5fvrl2DVTQ04ttXQD4DuppTt1Yz6wLibbjNE=
github.com/tailscale/wireguard-go v0.0.0-20191108062213-b93cdd0582db h1:oP0crfwOb3WZSVrMVm/o51NXN2JirDlcdlNEIPTmgI0= github.com/tailscale/wireguard-go v0.0.0-20191108062213-b93cdd0582db h1:oP0crfwOb3WZSVrMVm/o51NXN2JirDlcdlNEIPTmgI0=
github.com/tailscale/wireguard-go v0.0.0-20200207221558-a158079b156a h1:5TWA3nl2QUfL9OiE3tlBpqJd4GYd4hbGtDNkWQQ2fyc= github.com/tailscale/wireguard-go v0.0.0-20200207221558-a158079b156a h1:5TWA3nl2QUfL9OiE3tlBpqJd4GYd4hbGtDNkWQQ2fyc=
github.com/tailscale/wireguard-go v0.0.0-20200207221558-a158079b156a/go.mod h1:QPS8HjBzzAXoQNndUNx2efJaQbCCz8nI2Cv1ksTUHyY= github.com/tailscale/wireguard-go v0.0.0-20200207221558-a158079b156a/go.mod h1:QPS8HjBzzAXoQNndUNx2efJaQbCCz8nI2Cv1ksTUHyY=
@ -66,8 +75,10 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BG
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190310054646-10058d7d4faa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -77,6 +88,8 @@ golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

38
test.sh Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
function remove_test_files {
rm -f ./*test{,.exe}
}
function fail {
printf '%s\n' "$1" >&2
# If we fail, clean up after ourselves
remove_test_files
exit 1
}
function main {
test_dirs=()
while IFS= read -r -d '' file
do
dir=$(dirname "$file")
if [[ ! " ${test_dirs[*]} " =~ ${dir} ]]; then
test_dirs+=("$dir")
fi
done < <(find . -type f -iname '*_test.go' -print0)
for goos in openbsd darwin windows
do
for dir in "${test_dirs[@]}"; do
echo "Testing GOOS=$goos in dir $dir"
GOOS="$goos" go test -c "./$dir" || fail "Test failed using $goos and $dir"
done
done
# If all goes well, we should still clean up the test files
echo "Test complete"
remove_test_files
}
main "$@"

View File

@ -5,9 +5,10 @@
package wgengine package wgengine
import ( import (
"github.com/tailscale/wireguard-go/tun"
"io" "io"
"os" "os"
"github.com/tailscale/wireguard-go/tun"
) )
type fakeTun struct { type fakeTun struct {
@ -16,6 +17,9 @@ type fakeTun struct {
closechan chan struct{} closechan chan struct{}
} }
// NewFakeTun returns a fake TUN device that does not depend on the
// operating system or any special permissions.
// It primarily exists for testing.
func NewFakeTun() tun.Device { func NewFakeTun() tun.Device {
return &fakeTun{ return &fakeTun{
datachan: make(chan []byte), datachan: make(chan []byte),

View File

@ -16,13 +16,13 @@
"time" "time"
"unsafe" "unsafe"
"github.com/go-ole/go-ole" ole "github.com/go-ole/go-ole"
winipcfg "github.com/tailscale/winipcfg-go"
"github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"github.com/tailscale/wireguard-go/wgcfg" "github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
"golang.zx2c4.com/winipcfg"
"tailscale.com/wgengine/winnet" "tailscale.com/wgengine/winnet"
) )

View File

@ -32,5 +32,6 @@ func (r *darwinRouter) SetRoutes(rs RouteSettings) error {
return nil return nil
} }
func (r *darwinRouter) Close() { func (r *darwinRouter) Close() error {
return nil
} }

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !windows,!linux,!darwin // +build !windows,!linux,!darwin,!openbsd
package wgengine package wgengine
@ -13,5 +13,5 @@
) )
func NewUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router { func NewUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router {
return NewFakeRouter(logf, tunname, dev, tuntap) return NewFakeRouter(logf, tunname, dev, tuntap, netChanged)
} }

View File

@ -16,11 +16,10 @@ type fakeRouter struct {
} }
func NewFakeRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router { func NewFakeRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router {
r := fakeRouter{ return &fakeRouter{
logf: logf, logf: logf,
tunname: tunname, tunname: tunname,
} }
return &r
} }
func (r *fakeRouter) Up() error { func (r *fakeRouter) Up() error {
@ -33,6 +32,7 @@ func (r *fakeRouter) SetRoutes(rs RouteSettings) error {
return nil return nil
} }
func (r *fakeRouter) Close() { func (r *fakeRouter) Close() error {
r.logf("Warning: fakeRouter.Close: not implemented.\n") r.logf("Warning: fakeRouter.Close: not implemented.\n")
return nil
} }

View File

@ -57,6 +57,8 @@ func cmd(args ...string) *exec.Cmd {
func (r *linuxRouter) Up() error { func (r *linuxRouter) Up() error {
out, err := cmd("ip", "link", "set", r.tunname, "up").CombinedOutput() out, err := cmd("ip", "link", "set", r.tunname, "up").CombinedOutput()
if err != nil { if err != nil {
// TODO: this should return an error; why is it calling log.Fatalf?
// Audit callers to make sure they're handling errors.
log.Fatalf("running ip link failed: %v\n%s", err, out) log.Fatalf("running ip link failed: %v\n%s", err, out)
} }
@ -154,6 +156,7 @@ func (r *linuxRouter) SetRoutes(rs RouteSettings) error {
r.local = rs.LocalAddr r.local = rs.LocalAddr
r.routes = newRoutes r.routes = newRoutes
// TODO: this:
if false { if false {
if err := r.replaceResolvConf(rs.DNS, rs.DNSDomains); err != nil { if err := r.replaceResolvConf(rs.DNS, rs.DNSDomains); err != nil {
errq = fmt.Errorf("replacing resolv.conf failed: %v", err) errq = fmt.Errorf("replacing resolv.conf failed: %v", err)
@ -162,12 +165,17 @@ func (r *linuxRouter) SetRoutes(rs RouteSettings) error {
return errq return errq
} }
func (r *linuxRouter) Close() { func (r *linuxRouter) Close() error {
var ret error
r.mon.Close() r.mon.Close()
if err := r.restoreResolvConf(); err != nil { if err := r.restoreResolvConf(); err != nil {
r.logf("failed to restore system resolv.conf: %v", err) r.logf("failed to restore system resolv.conf: %v", err)
if ret == nil {
ret = err
}
} }
// TODO(apenwarr): clean up iptables etc. // TODO(apenwarr): clean up iptables etc.
return ret
} }
const ( const (

176
wgengine/router_openbsd.go Normal file
View File

@ -0,0 +1,176 @@
// 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 wgengine
import (
"fmt"
"log"
"net"
"os/exec"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/logger"
)
// For now this router only supports the userspace WireGuard implementations.
//
// There is an experimental kernel version in the works:
// https://git.zx2c4.com/wireguard-openbsd.
//
// TODO(mbaillie): netlink-style monitoring might be possible through
// `ifstated(8)`/`devd(8)`, or become possible with the OpenBSD kernel
// implementation. This merits further investigation.
type openbsdRouter struct {
logf logger.Logf
tunname string
local wgcfg.CIDR
routes map[wgcfg.CIDR]struct{}
}
func NewUserspaceRouter(logf logger.Logf, tunname string, _ *device.Device, tuntap tun.Device, _ func()) Router {
r := openbsdRouter{
logf: logf,
tunname: tunname,
}
return &r
}
// TODO(mbaillie): extract as identical to linux version
func cmd(args ...string) *exec.Cmd {
if len(args) == 0 {
log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]\n", args)
}
return exec.Command(args[0], args[1:]...)
}
func (r *openbsdRouter) Up() error {
ifup := []string{"ifconfig", r.tunname, "up"}
if out, err := cmd(ifup...).CombinedOutput(); err != nil {
r.logf("running ifconfig failed: %v\n%s", err, out)
return err
}
return nil
}
func (r *openbsdRouter) SetRoutes(rs RouteSettings) error {
var errq error
if rs.LocalAddr != r.local {
if r.local != (wgcfg.CIDR{}) {
addrdel := []string{"ifconfig", r.tunname,
"inet", r.local.String(), "-alias"}
out, err := cmd(addrdel...).CombinedOutput()
if err != nil {
r.logf("addr del failed: %v: %v\n%s", addrdel, err, out)
if errq == nil {
errq = err
}
}
routedel := []string{"route", "-q", "-n",
"del", "-inet", r.local.String(),
"-iface", r.local.IP.String()}
if out, err := cmd(routedel...).CombinedOutput(); err != nil {
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
if errq == nil {
errq = err
}
}
}
addradd := []string{"ifconfig", r.tunname,
"inet", rs.LocalAddr.String(), "alias"}
out, err := cmd(addradd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", addradd, err, out)
if errq == nil {
errq = err
}
}
routeadd := []string{"route", "-q", "-n",
"add", "-inet", rs.LocalAddr.String(),
"-iface", rs.LocalAddr.IP.String()}
if out, err := cmd(routeadd...).CombinedOutput(); err != nil {
r.logf("route add failed: %v: %v\n%s", routeadd, err, out)
if errq == nil {
errq = err
}
}
}
newRoutes := make(map[wgcfg.CIDR]struct{})
for _, peer := range rs.Cfg.Peers {
for _, route := range peer.AllowedIPs {
newRoutes[route] = struct{}{}
}
}
for route := range r.routes {
if _, keep := newRoutes[route]; !keep {
net := route.IPNet()
nip := net.IP.Mask(net.Mask)
nstr := fmt.Sprintf("%v/%d", nip, route.Mask)
routedel := []string{"route", "-q", "-n",
"del", "-inet", nstr,
"-iface", rs.LocalAddr.IP.String()}
out, err := cmd(routedel...).CombinedOutput()
if err != nil {
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
if errq == nil {
errq = err
}
}
}
}
for route := range newRoutes {
if _, exists := r.routes[route]; !exists {
net := route.IPNet()
nip := net.IP.Mask(net.Mask)
nstr := fmt.Sprintf("%v/%d", nip, route.Mask)
routeadd := []string{"route", "-q", "-n",
"add", "-inet", nstr,
"-iface", rs.LocalAddr.IP.String()}
out, err := cmd(routeadd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", routeadd, err, out)
if errq == nil {
errq = err
}
}
}
}
r.local = rs.LocalAddr
r.routes = newRoutes
if err := r.replaceResolvConf(rs.DNS, rs.DNSDomains); err != nil {
errq = fmt.Errorf("replacing resolv.conf failed: %v", err)
}
return errq
}
func (r *openbsdRouter) Close() error {
out, err := cmd("ifconfig", r.tunname, "down").CombinedOutput()
if err != nil {
r.logf("running ifconfig failed: %v\n%s", err, out)
}
if err := r.restoreResolvConf(); err != nil {
r.logf("failed to restore system resolv.conf: %v", err)
}
// TODO(mbaillie): wipe routes
return nil
}
// TODO(mbaillie): these are no-ops for now. They could re-use the Linux funcs
// (sans systemd parts), but I note Linux DNS is disabled(?) so leaving for now.
func (r *openbsdRouter) replaceResolvConf(_ []net.IP, _ []string) error { return nil }
func (r *openbsdRouter) restoreResolvConf() error { return nil }

View File

@ -7,9 +7,9 @@
import ( import (
"log" "log"
winipcfg "github.com/tailscale/winipcfg-go"
"github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"golang.zx2c4.com/winipcfg"
"tailscale.com/logger" "tailscale.com/logger"
) )
@ -43,7 +43,7 @@ func (r *winRouter) Up() error {
} }
func (r *winRouter) SetRoutes(rs RouteSettings) error { func (r *winRouter) SetRoutes(rs RouteSettings) error {
err := ConfigureInterface(&rs.Cfg, r.nativeTun, rs.DNS, rs.DNSDomains) err := ConfigureInterface(rs.Cfg, r.nativeTun, rs.DNS, rs.DNSDomains)
if err != nil { if err != nil {
r.logf("ConfigureInterface: %v\n", err) r.logf("ConfigureInterface: %v\n", err)
return err return err
@ -51,8 +51,9 @@ func (r *winRouter) SetRoutes(rs RouteSettings) error {
return nil return nil
} }
func (r *winRouter) Close() { func (r *winRouter) Close() error {
if r.routeChangeCallback != nil { if r.routeChangeCallback != nil {
r.routeChangeCallback.Unregister() r.routeChangeCallback.Unregister()
} }
return nil
} }

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !windows
// Package rtnlmon watches for "interesting" changes to the network // Package rtnlmon watches for "interesting" changes to the network
// stack and fires a callback. // stack and fires a callback.
package rtnlmon package rtnlmon

View File

@ -11,7 +11,10 @@
"tailscale.com/logger" "tailscale.com/logger"
) )
func RusagePrefixLog(logf logger.Logf) func(f string, argv ...interface{}) { // RusagePrefixLog returns a Logf func wrapping the provided logf func that adds
// a prefixed log message to each line with the current binary memory usage
// and max RSS.
func RusagePrefixLog(logf logger.Logf) logger.Logf {
return func(f string, argv ...interface{}) { return func(f string, argv ...interface{}) {
var m runtime.MemStats var m runtime.MemStats
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)

View File

@ -23,7 +23,7 @@ func rusageMaxRSS() float64 {
rss /= 1 << 20 // ru_maxrss is bytes on darwin rss /= 1 << 20 // ru_maxrss is bytes on darwin
} else { } else {
// ru_maxrss is kilobytes elsewhere (linux, openbsd, etc) // ru_maxrss is kilobytes elsewhere (linux, openbsd, etc)
rss /= 1024 rss /= 1 << 10
} }
return rss return rss
} }

View File

@ -67,14 +67,14 @@ func NewUserspaceEngine(logf logger.Logf, tunname string, listenPort uint16, der
tuntap, err := tun.CreateTUN(tunname, device.DefaultMTU) tuntap, err := tun.CreateTUN(tunname, device.DefaultMTU)
if err != nil { if err != nil {
log.Printf("CreateTUN: %v\n", err) logf("CreateTUN: %v\n", err)
return nil, err return nil, err
} }
log.Printf("CreateTUN ok.\n") logf("CreateTUN ok.\n")
e, err := NewUserspaceEngineAdvanced(logf, tuntap, NewUserspaceRouter, listenPort, derp) e, err := NewUserspaceEngineAdvanced(logf, tuntap, NewUserspaceRouter, listenPort, derp)
if err != nil { if err != nil {
log.Printf("NewUserspaceEngineAdv: %v\n", err) logf("NewUserspaceEngineAdv: %v\n", err)
return nil, err return nil, err
} }
return e, err return e, err
@ -205,7 +205,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
e.peerSequence[i] = p.PublicKey e.peerSequence[i] = p.PublicKey
} }
// TODO(apenwarr): get rid of silly uapi stuff for in-process comms // TODO(apenwarr): get rid of uapi stuff for in-process comms
uapi, err := cfg.ToUAPI() uapi, err := cfg.ToUAPI()
if err != nil { if err != nil {
return err return err
@ -239,7 +239,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
rs := RouteSettings{ rs := RouteSettings{
LocalAddr: cidr, LocalAddr: cidr,
Cfg: *cfg, Cfg: cfg,
DNS: cfg.Interface.Dns, DNS: cfg.Interface.Dns,
DNSDomains: dnsDomains, DNSDomains: dnsDomains,
} }

View File

@ -5,9 +5,9 @@
package wgengine package wgengine
import ( import (
"bytes"
"log" "log"
"runtime/pprof" "runtime/pprof"
"strings"
"time" "time"
"github.com/tailscale/wireguard-go/wgcfg" "github.com/tailscale/wireguard-go/wgcfg"
@ -45,7 +45,7 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error {
t.Stop() t.Stop()
return err return err
case <-t.C: case <-t.C:
buf := new(bytes.Buffer) buf := new(strings.Builder)
pprof.Lookup("goroutine").WriteTo(buf, 1) pprof.Lookup("goroutine").WriteTo(buf, 1)
e.logf("wgengine watchdog stacks:\n%s", buf.String()) e.logf("wgengine watchdog stacks:\n%s", buf.String())
e.fatalf("wgengine: watchdog timeout on %s", name) e.fatalf("wgengine: watchdog timeout on %s", name)

View File

@ -14,6 +14,10 @@
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
) )
// ByteCount is the number of bytes that have been sent or received.
//
// TODO: why is this a type? remove?
// TODO: document whether it's payload bytes only or if it includes framing overhead.
type ByteCount int64 type ByteCount int64
type PeerStatus struct { type PeerStatus struct {
@ -22,20 +26,29 @@ type PeerStatus struct {
NodeKey tailcfg.NodeKey NodeKey tailcfg.NodeKey
} }
// Status is the Engine status.
type Status struct { type Status struct {
Peers []PeerStatus Peers []PeerStatus
LocalAddrs []string // TODO(crawshaw): []wgcfg.Endpoint? LocalAddrs []string // TODO(crawshaw): []wgcfg.Endpoint?
} }
type StatusCallback func(s *Status, err error) // StatusCallback is the type of status callbacks used by
// Engine.SetStatusCallback.
//
// Exactly one of Status or error is non-nil.
type StatusCallback func(*Status, error)
// RouteSettings is the full WireGuard config data (set of peers keys,
// IP, etc in wgcfg.Config) plus the things that WireGuard doesn't do
// itself, like DNS stuff.
type RouteSettings struct { type RouteSettings struct {
LocalAddr wgcfg.CIDR LocalAddr wgcfg.CIDR // TODO: why is this here? how does it differ from wgcfg.Config's info?
DNS []net.IP DNS []net.IP
DNSDomains []string DNSDomains []string
Cfg wgcfg.Config Cfg *wgcfg.Config
} }
// OnlyRelevantParts returns a string minimally describing the route settings.
func (rs *RouteSettings) OnlyRelevantParts() string { func (rs *RouteSettings) OnlyRelevantParts() string {
var peers [][]wgcfg.CIDR var peers [][]wgcfg.CIDR
for _, p := range rs.Cfg.Peers { for _, p := range rs.Cfg.Peers {
@ -45,31 +58,58 @@ func (rs *RouteSettings) OnlyRelevantParts() string {
rs.LocalAddr, rs.DNS, rs.DNSDomains, peers) rs.LocalAddr, rs.DNS, rs.DNSDomains, peers)
} }
// Router is responsible for managing the system route table.
//
// There's only one instance, and one per-OS implementation.
type Router interface { type Router interface {
// Up brings the router up.
Up() error Up() error
SetRoutes(rs RouteSettings) error
Close() // SetRoutes is called regularly on network map updates.
// It's how you kernel route table entries are populated for
// each peer.
SetRoutes(RouteSettings) error
// Close closes the router.
Close() error
} }
// Engine is the Tailscale WireGuard engine interface.
type Engine interface { type Engine interface {
// Reconfigure wireguard and make sure it's running. // Reconfig reconfigures WireGuard and makes sure it's running.
// This also handles setting up any kernel routes. // This also handles setting up any kernel routes.
//
// The provided DNS domains are not part of wgcfg.Config, as
// WireGuard itself doesn't care about such things.
//
// This is called whenever the tailcontrol (control plane)
// sends an updated network map.
Reconfig(cfg *wgcfg.Config, dnsDomains []string) error Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
// Update the packet filter.
SetFilter(filt *filter.Filter) // SetFilter updates the packet filter.
// Set the function to call when wireguard status changes. SetFilter(*filter.Filter)
SetStatusCallback(cb StatusCallback)
// Request a wireguard status update right away, sent to the callback. // SetStatusCallback sets the function to call when the
// WireGuard status changes.
SetStatusCallback(StatusCallback)
// RequestStatus requests a WireGuard status update right
// away, sent to the callback registered via SetStatusCallback.
RequestStatus() RequestStatus()
// Shut down this wireguard instance, remove any routes it added, etc.
// To bring it up again later, you'll need a new Engine. // Close shuts down this wireguard instance, remove any routes
// it added, etc. To bring it up again later, you'll need a
// new Engine.
Close() Close()
// Wait until the Engine is .Close()ed or aborts with an error.
// You don't have to call this. // Wait waits until the Engine's Close method is called or the
// engine aborts with an error. You don't have to call this.
// TODO: return an error?
Wait() Wait()
// LinkChange informs the engine that the system network // LinkChange informs the engine that the system network
// link has changed. The isExpensive parameter is set on links // link has changed. The isExpensive parameter is set on links
// where sending packets uses substantial power or dollars // where sending packets uses substantial power or money,
// (such as LTE on a phone). // such as mobile data on a phone.
LinkChange(isExpensive bool) LinkChange(isExpensive bool)
} }