Merge branch 'master' of github.com:tailscale/tailscale into HEAD
This commit is contained in:
commit
cbfef0c8b7
|
@ -28,7 +28,22 @@ jobs:
|
|||
- name: Basic build
|
||||
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 ./...
|
||||
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
|
@ -45,3 +60,4 @@ jobs:
|
|||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: failure() && github.event_name == 'push'
|
||||
|
||||
|
|
|
@ -20,11 +20,14 @@ import (
|
|||
"tailscale.com/ipn/ipnserver"
|
||||
"tailscale.com/logpolicy"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fake := getopt.BoolLong("fake", 0, "fake tunnel+routing instead of tuntap")
|
||||
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)
|
||||
|
||||
|
@ -47,7 +50,7 @@ func main() {
|
|||
if *fake {
|
||||
e, err = wgengine.NewFakeUserspaceEngine(logf, 0, false)
|
||||
} else {
|
||||
e, err = wgengine.NewUserspaceEngine(logf, "ts0", 0, false)
|
||||
e, err = wgengine.NewUserspaceEngine(logf, *tunname, *listenport, false)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("wgengine.New: %v\n", err)
|
||||
|
|
|
@ -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
7
go.mod
|
@ -3,18 +3,23 @@ module tailscale.com
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
|
||||
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/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
|
||||
github.com/google/go-cmp v0.4.0
|
||||
github.com/klauspost/compress v1.9.8
|
||||
github.com/kr/pty v1.1.1
|
||||
github.com/mdlayher/netlink v1.1.0
|
||||
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
|
||||
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
|
||||
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678
|
||||
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
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 // indirect
|
||||
)
|
||||
|
|
13
go.sum
13
go.sum
|
@ -1,10 +1,16 @@
|
|||
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/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/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/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/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
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/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
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/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=
|
||||
|
@ -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/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/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-20200207221558-a158079b156a h1:5TWA3nl2QUfL9OiE3tlBpqJd4GYd4hbGtDNkWQQ2fyc=
|
||||
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/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-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
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-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-20190405154228-4b34438f7a67/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-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
|
|
@ -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 "$@"
|
||||
|
|
@ -5,9 +5,10 @@
|
|||
package wgengine
|
||||
|
||||
import (
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
)
|
||||
|
||||
type fakeTun struct {
|
||||
|
@ -16,6 +17,9 @@ type fakeTun 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 {
|
||||
return &fakeTun{
|
||||
datachan: make(chan []byte),
|
||||
|
|
|
@ -16,13 +16,13 @@ import (
|
|||
"time"
|
||||
"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/tun"
|
||||
"github.com/tailscale/wireguard-go/wgcfg"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"golang.zx2c4.com/winipcfg"
|
||||
"tailscale.com/wgengine/winnet"
|
||||
)
|
||||
|
||||
|
|
|
@ -32,5 +32,6 @@ func (r *darwinRouter) SetRoutes(rs RouteSettings) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *darwinRouter) Close() {
|
||||
func (r *darwinRouter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !windows,!linux,!darwin
|
||||
// +build !windows,!linux,!darwin,!openbsd
|
||||
|
||||
package wgengine
|
||||
|
||||
|
@ -13,5 +13,5 @@ import (
|
|||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -16,11 +16,10 @@ type fakeRouter struct {
|
|||
}
|
||||
|
||||
func NewFakeRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router {
|
||||
r := fakeRouter{
|
||||
return &fakeRouter{
|
||||
logf: logf,
|
||||
tunname: tunname,
|
||||
}
|
||||
return &r
|
||||
}
|
||||
|
||||
func (r *fakeRouter) Up() error {
|
||||
|
@ -33,6 +32,7 @@ func (r *fakeRouter) SetRoutes(rs RouteSettings) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *fakeRouter) Close() {
|
||||
func (r *fakeRouter) Close() error {
|
||||
r.logf("Warning: fakeRouter.Close: not implemented.\n")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ func cmd(args ...string) *exec.Cmd {
|
|||
func (r *linuxRouter) Up() error {
|
||||
out, err := cmd("ip", "link", "set", r.tunname, "up").CombinedOutput()
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -154,6 +156,7 @@ func (r *linuxRouter) SetRoutes(rs RouteSettings) error {
|
|||
r.local = rs.LocalAddr
|
||||
r.routes = newRoutes
|
||||
|
||||
// TODO: this:
|
||||
if false {
|
||||
if err := r.replaceResolvConf(rs.DNS, rs.DNSDomains); err != nil {
|
||||
errq = fmt.Errorf("replacing resolv.conf failed: %v", err)
|
||||
|
@ -162,12 +165,17 @@ func (r *linuxRouter) SetRoutes(rs RouteSettings) error {
|
|||
return errq
|
||||
}
|
||||
|
||||
func (r *linuxRouter) Close() {
|
||||
func (r *linuxRouter) Close() error {
|
||||
var ret error
|
||||
r.mon.Close()
|
||||
if err := r.restoreResolvConf(); err != nil {
|
||||
r.logf("failed to restore system resolv.conf: %v", err)
|
||||
if ret == nil {
|
||||
ret = err
|
||||
}
|
||||
}
|
||||
// TODO(apenwarr): clean up iptables etc.
|
||||
return ret
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -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 }
|
|
@ -7,9 +7,9 @@ package wgengine
|
|||
import (
|
||||
"log"
|
||||
|
||||
winipcfg "github.com/tailscale/winipcfg-go"
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"golang.zx2c4.com/winipcfg"
|
||||
"tailscale.com/logger"
|
||||
)
|
||||
|
||||
|
@ -43,7 +43,7 @@ func (r *winRouter) Up() 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 {
|
||||
r.logf("ConfigureInterface: %v\n", err)
|
||||
return err
|
||||
|
@ -51,8 +51,9 @@ func (r *winRouter) SetRoutes(rs RouteSettings) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *winRouter) Close() {
|
||||
func (r *winRouter) Close() error {
|
||||
if r.routeChangeCallback != nil {
|
||||
r.routeChangeCallback.Unregister()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !windows
|
||||
|
||||
// Package rtnlmon watches for "interesting" changes to the network
|
||||
// stack and fires a callback.
|
||||
package rtnlmon
|
||||
|
|
|
@ -11,7 +11,10 @@ import (
|
|||
"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{}) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
|
|
@ -23,7 +23,7 @@ func rusageMaxRSS() float64 {
|
|||
rss /= 1 << 20 // ru_maxrss is bytes on darwin
|
||||
} else {
|
||||
// ru_maxrss is kilobytes elsewhere (linux, openbsd, etc)
|
||||
rss /= 1024
|
||||
rss /= 1 << 10
|
||||
}
|
||||
return rss
|
||||
}
|
||||
|
|
|
@ -67,14 +67,14 @@ func NewUserspaceEngine(logf logger.Logf, tunname string, listenPort uint16, der
|
|||
|
||||
tuntap, err := tun.CreateTUN(tunname, device.DefaultMTU)
|
||||
if err != nil {
|
||||
log.Printf("CreateTUN: %v\n", err)
|
||||
logf("CreateTUN: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("CreateTUN ok.\n")
|
||||
logf("CreateTUN ok.\n")
|
||||
|
||||
e, err := NewUserspaceEngineAdvanced(logf, tuntap, NewUserspaceRouter, listenPort, derp)
|
||||
if err != nil {
|
||||
log.Printf("NewUserspaceEngineAdv: %v\n", err)
|
||||
logf("NewUserspaceEngineAdv: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return e, err
|
||||
|
@ -205,7 +205,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
|
|||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -239,7 +239,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
|
|||
|
||||
rs := RouteSettings{
|
||||
LocalAddr: cidr,
|
||||
Cfg: *cfg,
|
||||
Cfg: cfg,
|
||||
DNS: cfg.Interface.Dns,
|
||||
DNSDomains: dnsDomains,
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
package wgengine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tailscale/wireguard-go/wgcfg"
|
||||
|
@ -45,7 +45,7 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error {
|
|||
t.Stop()
|
||||
return err
|
||||
case <-t.C:
|
||||
buf := new(bytes.Buffer)
|
||||
buf := new(strings.Builder)
|
||||
pprof.Lookup("goroutine").WriteTo(buf, 1)
|
||||
e.logf("wgengine watchdog stacks:\n%s", buf.String())
|
||||
e.fatalf("wgengine: watchdog timeout on %s", name)
|
||||
|
|
|
@ -14,6 +14,10 @@ import (
|
|||
"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 PeerStatus struct {
|
||||
|
@ -22,20 +26,29 @@ type PeerStatus struct {
|
|||
NodeKey tailcfg.NodeKey
|
||||
}
|
||||
|
||||
// Status is the Engine status.
|
||||
type Status struct {
|
||||
Peers []PeerStatus
|
||||
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 {
|
||||
LocalAddr wgcfg.CIDR
|
||||
LocalAddr wgcfg.CIDR // TODO: why is this here? how does it differ from wgcfg.Config's info?
|
||||
DNS []net.IP
|
||||
DNSDomains []string
|
||||
Cfg wgcfg.Config
|
||||
Cfg *wgcfg.Config
|
||||
}
|
||||
|
||||
// OnlyRelevantParts returns a string minimally describing the route settings.
|
||||
func (rs *RouteSettings) OnlyRelevantParts() string {
|
||||
var peers [][]wgcfg.CIDR
|
||||
for _, p := range rs.Cfg.Peers {
|
||||
|
@ -45,31 +58,58 @@ func (rs *RouteSettings) OnlyRelevantParts() string {
|
|||
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 {
|
||||
// Up brings the router up.
|
||||
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 {
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// Update the packet filter.
|
||||
SetFilter(filt *filter.Filter)
|
||||
// Set the function to call when wireguard status changes.
|
||||
SetStatusCallback(cb StatusCallback)
|
||||
// Request a wireguard status update right away, sent to the callback.
|
||||
|
||||
// SetFilter updates the packet filter.
|
||||
SetFilter(*filter.Filter)
|
||||
|
||||
// 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()
|
||||
// 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()
|
||||
// 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()
|
||||
|
||||
// LinkChange informs the engine that the system network
|
||||
// link has changed. The isExpensive parameter is set on links
|
||||
// where sending packets uses substantial power or dollars
|
||||
// (such as LTE on a phone).
|
||||
// where sending packets uses substantial power or money,
|
||||
// such as mobile data on a phone.
|
||||
LinkChange(isExpensive bool)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue