Compare commits

..

1 Commits

Author SHA1 Message Date
Brad Fitzpatrick
8b2b679ba3 api.md: add TOC 2021-01-19 12:34:22 -08:00
45 changed files with 413 additions and 1508 deletions

View File

@@ -48,9 +48,6 @@ RUN go mod download
COPY . .
ARG goflags_arg # default intentionally unset
ENV GOFLAGS=$goflags_arg
RUN go install -v ./cmd/...
FROM alpine:3.11

46
LICENSE
View File

@@ -1,29 +1,27 @@
BSD 3-Clause License
Copyright (c) 2020 Tailscale & AUTHORS.
All rights reserved.
Copyright (c) 2020 Tailscale & AUTHORS. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Tailscale Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1 +1 @@
1.5.0
1.3.0

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env sh
#
# Runs `go build` with flags configured for docker distribution. All
# it does differently from `go build` is burn git commit and version
# information into the binaries inside docker, so that we can track down user
# issues.
#
############################################################################
#
# WARNING: Tailscale is not yet officially supported in Docker,
# Kubernetes, etc.
#
# It might work, but we don't regularly test it, and it's not as polished as
# our currently supported platforms. This is provided for people who know
# how Tailscale works and what they're doing.
#
# Our tracking bug for officially support container use cases is:
# https://github.com/tailscale/tailscale/issues/504
#
# Also, see the various bugs tagged "containers":
# https://github.com/tailscale/tailscale/labels/containers
#
############################################################################
set -eu
eval $(./version/version.sh)
GOFLAGS='-tags xversion -ldflags '"-X tailscale.com/version.Long=${VERSION_LONG} -X tailscale.com/version.Short=${VERSION_SHORT} -X tailscale.com/version.GitCommit=${VERSION_GIT_HASH}"
docker build --build-arg goflags_arg="'""${GOFLAGS}""'" -t tailscale:tailscale .

View File

@@ -1,116 +0,0 @@
// Copyright (c) 2021 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.
// The hello binary runs hello.ipn.dev.
package main // import "tailscale.com/cmd/hello"
import (
"encoding/json"
"flag"
"fmt"
"html/template"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strings"
"tailscale.com/tailcfg"
)
var (
httpAddr = flag.String("http", ":80", "address to run an HTTP server on, or empty for none")
httpsAddr = flag.String("https", ":443", "address to run an HTTPS server on, or empty for none")
)
func main() {
flag.Parse()
http.HandleFunc("/", root)
log.Printf("Starting hello server.")
errc := make(chan error, 1)
if *httpAddr != "" {
log.Printf("running HTTP server on %s", *httpAddr)
go func() {
errc <- http.ListenAndServe(*httpAddr, nil)
}()
}
if *httpsAddr != "" {
log.Printf("running HTTPS server on %s", *httpsAddr)
go func() {
errc <- http.ListenAndServeTLS(*httpsAddr,
"/etc/hello/hello.ipn.dev.crt",
"/etc/hello/hello.ipn.dev.key",
nil,
)
}()
}
log.Fatal(<-errc)
}
func slurpHTML() string {
slurp, err := ioutil.ReadFile("hello.tmpl.html")
if err != nil {
log.Fatal(err)
}
return string(slurp)
}
var tmpl = template.Must(template.New("home").Parse(slurpHTML()))
type tmplData struct {
DisplayName string // "Foo Barberson"
LoginName string // "foo@bar.com"
MachineName string // "imac5k"
IP string // "100.2.3.4"
}
func root(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil && *httpsAddr != "" {
host := r.Host
if strings.Contains(r.Host, "100.101.102.103") {
host = "hello.ipn.dev"
}
http.Redirect(w, r, "https://"+host, http.StatusFound)
return
}
if r.RequestURI != "/" {
http.Redirect(w, r, "/", http.StatusFound)
return
}
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
http.Error(w, "no remote addr", 500)
return
}
who, err := whoIs(ip)
if err != nil {
log.Printf("whois(%q) error: %v", ip, err)
http.Error(w, "Your Tailscale works, but we failed to look you up.", 500)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tmpl.Execute(w, tmplData{
DisplayName: who.UserProfile.DisplayName,
LoginName: who.UserProfile.LoginName,
MachineName: who.Node.ComputedName,
IP: ip,
})
}
func whoIs(ip string) (*tailcfg.WhoIsResponse, error) {
res, err := http.Get("http://127.0.0.1:4242/whois?ip=" + url.QueryEscape(ip))
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
slurp, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("HTTP %s: %s", res.Status, slurp)
}
r := new(tailcfg.WhoIsResponse)
return r, json.NewDecoder(res.Body).Decode(r)
}

View File

@@ -1,17 +0,0 @@
<html>
<head>
<title>Hello from Tailscale</title>
</head>
<body>
<h1>Hello!</h1>
<p>
Hello {{.DisplayName}} ({{.LoginName}}) from {{.MachineName}} ({{.IP}}).
</p>
<p>
<b>Your Tailscale is working!</b>
</p>
<p>
Welcome to Tailscale.
</p>
</body>
</html>

View File

@@ -64,63 +64,34 @@ func runPing(ctx context.Context, args []string) error {
c, bc, ctx, cancel := connect(ctx)
defer cancel()
if len(args) != 1 || args[0] == "" {
if len(args) != 1 {
return errors.New("usage: ping <hostname-or-IP>")
}
hostOrIP := args[0]
var ip string
prc := make(chan *ipnstate.PingResult, 1)
stc := make(chan *ipnstate.Status, 1)
var res net.Resolver
if addrs, err := res.LookupHost(ctx, hostOrIP); err != nil {
return fmt.Errorf("error looking up IP of %q: %v", hostOrIP, err)
} else if len(addrs) == 0 {
return fmt.Errorf("no IPs found for %q", hostOrIP)
} else {
ip = addrs[0]
}
if pingArgs.verbose && ip != hostOrIP {
log.Printf("lookup %q => %q", hostOrIP, ip)
}
ch := make(chan *ipnstate.PingResult, 1)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if pr := n.PingResult; pr != nil && pr.IP == ip {
prc <- pr
}
if n.Status != nil {
stc <- n.Status
ch <- pr
}
})
go pump(ctx, bc, c)
hostOrIP := args[0]
// If the argument is an IP address, use it directly without any resolution.
if net.ParseIP(hostOrIP) != nil {
ip = hostOrIP
}
// Otherwise, try to resolve it first from the network peer list.
if ip == "" {
bc.RequestStatus()
select {
case st := <-stc:
for _, ps := range st.Peer {
if hostOrIP == dnsOrQuoteHostname(st, ps) || hostOrIP == ps.DNSName {
ip = ps.TailAddr
break
}
}
case <-ctx.Done():
return ctx.Err()
}
}
// Finally, use DNS.
if ip == "" {
var res net.Resolver
if addrs, err := res.LookupHost(ctx, hostOrIP); err != nil {
return fmt.Errorf("error looking up IP of %q: %v", hostOrIP, err)
} else if len(addrs) == 0 {
return fmt.Errorf("no IPs found for %q", hostOrIP)
} else {
ip = addrs[0]
}
}
if pingArgs.verbose && ip != hostOrIP {
log.Printf("lookup %q => %q", hostOrIP, ip)
}
n := 0
anyPong := false
for {
@@ -130,7 +101,7 @@ func runPing(ctx context.Context, args []string) error {
select {
case <-timer.C:
fmt.Printf("timeout waiting for ping reply\n")
case pr := <-prc:
case pr := <-ch:
timer.Stop()
if pr.Err != "" {
return errors.New(pr.Err)

View File

@@ -14,6 +14,7 @@ import (
"net"
"net/http"
"os"
"sort"
"strings"
"time"
@@ -180,7 +181,7 @@ func runStatus(ctx context.Context, args []string) error {
}
peers = append(peers, ps)
}
ipnstate.SortPeers(peers)
sort.Slice(peers, func(i, j int) bool { return sortKey(peers[i]) < sortKey(peers[j]) })
for _, ps := range peers {
active := peerActive(ps)
if statusArgs.active && !active {
@@ -210,6 +211,16 @@ func dnsOrQuoteHostname(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
return fmt.Sprintf("(%q)", strings.ReplaceAll(ps.SimpleHostName(), " ", "_"))
}
func sortKey(ps *ipnstate.PeerStatus) string {
if ps.DNSName != "" {
return ps.DNSName
}
if ps.HostName != "" {
return ps.HostName
}
return ps.TailAddr
}
func ownerLogin(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
if ps.UserID.IsZero() {
return "-"

View File

@@ -120,11 +120,6 @@ func checkIPForwarding() {
}
}
var (
ipv4default = netaddr.MustParseIPPrefix("0.0.0.0/0")
ipv6default = netaddr.MustParseIPPrefix("::/0")
)
func runUp(ctx context.Context, args []string) error {
if len(args) > 0 {
log.Fatalf("too many non-flag arguments: %q", args)
@@ -144,7 +139,6 @@ func runUp(ctx context.Context, args []string) error {
}
var routes []netaddr.IPPrefix
var default4, default6 bool
if upArgs.advertiseRoutes != "" {
advroutes := strings.Split(upArgs.advertiseRoutes, ",")
for _, s := range advroutes {
@@ -155,18 +149,8 @@ func runUp(ctx context.Context, args []string) error {
if ipp != ipp.Masked() {
fatalf("%s has non-address bits set; expected %s", ipp, ipp.Masked())
}
if ipp == ipv4default {
default4 = true
} else if ipp == ipv6default {
default6 = true
}
routes = append(routes, ipp)
}
if default4 && !default6 {
fatalf("%s advertised without its IPv6 counterpart, please also advertise %s", ipv4default, ipv6default)
} else if default6 && !default4 {
fatalf("%s advertised without its IPv6 counterpart, please also advertise %s", ipv6default, ipv4default)
}
checkIPForwarding()
}

View File

@@ -66,7 +66,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/tstime from tailscale.com/wgengine/magicsock
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/key from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
@@ -89,7 +88,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/tsdns from tailscale.com/ipn+
tailscale.com/wgengine/tstun from tailscale.com/wgengine
tailscale.com/wgengine/wglog from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+

View File

@@ -103,7 +103,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/control/controlclient+
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/tstime from tailscale.com/wgengine/magicsock
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
tailscale.com/types/key from tailscale.com/derp+
@@ -113,7 +112,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/strbuilder from tailscale.com/net/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
tailscale.com/types/wgkey from tailscale.com/control/controlclient+
tailscale.com/util/dnsname from tailscale.com/wgengine/tsdns+
tailscale.com/util/dnsname from tailscale.com/control/controlclient+
LW tailscale.com/util/endian from tailscale.com/net/netns+
tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
@@ -130,7 +129,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/tsdns from tailscale.com/ipn+
tailscale.com/wgengine/tstun from tailscale.com/wgengine+
tailscale.com/wgengine/wglog from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+

View File

@@ -103,6 +103,10 @@ func main() {
os.Exit(0)
}
if args.statepath == "" {
log.Fatalf("--state is required")
}
if args.socketpath == "" && runtime.GOOS != "windows" {
log.Fatalf("--socket is required")
}
@@ -136,10 +140,6 @@ func run() error {
return nil
}
if args.statepath == "" {
log.Fatalf("--state is required")
}
var debugMux *http.ServeMux
if args.debug != "" {
debugMux = newDebugMux()

View File

@@ -34,8 +34,6 @@ ProtectHome=true
ProtectKernelTunables=true
ProtectSystem=strict
ReadWritePaths=/etc/
ReadWritePaths=/run/
ReadWritePaths=/var/run/
RestrictSUIDSGID=true
SystemCallArchitectures=native

View File

@@ -32,9 +32,7 @@ import (
"github.com/gliderlabs/ssh"
"github.com/kr/pty"
gossh "golang.org/x/crypto/ssh"
"inet.af/netaddr"
"tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
)
var (
@@ -98,13 +96,7 @@ func handleSSH(s ssh.Session) {
s.Exit(1)
return
}
tanetaddr, ok := netaddr.FromStdIP(ta.IP)
if !ok {
log.Printf("tsshd: rejecting unparseable addr %v", ta.IP)
s.Exit(1)
return
}
if !tsaddr.IsTailscaleIP(tanetaddr) {
if !interfaces.IsTailscaleIP(ta.IP) {
log.Printf("tsshd: rejecting non-Tailscale addr %v", ta.IP)
s.Exit(1)
return

View File

@@ -744,14 +744,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw
}
resp.Peers = filtered
}
if Debug.StripEndpoints {
for _, p := range resp.Peers {
// We need at least one endpoint here for now else
// other code doesn't even create the discoEndpoint.
// TODO(bradfitz): fix that and then just nil this out.
p.Endpoints = []string{"127.9.9.9:456"}
}
}
if pf := resp.PacketFilter; pf != nil {
lastParsedPacketFilter = c.parsePacketFilter(pf)
@@ -770,7 +762,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw
c.mu.Unlock()
nm := &NetworkMap{
SelfNode: resp.Node,
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
PrivateKey: persist.PrivateNodeKey,
MachineKey: machinePubKey,
@@ -799,10 +790,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw
}
}
addUserProfile(nm.User)
magicDNSSuffix := nm.MagicDNSSuffix()
nm.SelfNode.InitDisplayNames(magicDNSSuffix)
for _, peer := range resp.Peers {
peer.InitDisplayNames(magicDNSSuffix)
if !peer.Sharer.IsZero() {
if c.keepSharerAndUserSplit {
addUserProfile(peer.Sharer)
@@ -984,21 +972,19 @@ func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (w
var Debug = initDebug()
type debug struct {
NetMap bool
ProxyDNS bool
OnlyDisco bool
Disco bool
StripEndpoints bool // strip endpoints from control (only use disco messages)
NetMap bool
ProxyDNS bool
OnlyDisco bool
Disco bool
}
func initDebug() debug {
use := os.Getenv("TS_DEBUG_USE_DISCO")
return debug{
NetMap: envBool("TS_DEBUG_NETMAP"),
ProxyDNS: envBool("TS_DEBUG_PROXY_DNS"),
StripEndpoints: envBool("TS_DEBUG_STRIP_ENDPOINTS"),
OnlyDisco: use == "only",
Disco: use == "only" || use == "" || envBool("TS_DEBUG_USE_DISCO"),
NetMap: envBool("TS_DEBUG_NETMAP"),
ProxyDNS: envBool("TS_DEBUG_PROXY_DNS"),
OnlyDisco: use == "only",
Disco: use == "only" || use == "" || envBool("TS_DEBUG_USE_DISCO"),
}
}
@@ -1079,24 +1065,6 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
}
}
sortNodes(newFull)
if mapRes.PeerSeenChange != nil {
peerByID := make(map[tailcfg.NodeID]*tailcfg.Node, len(newFull))
for _, n := range newFull {
peerByID[n.ID] = n
}
now := time.Now()
for nodeID, seen := range mapRes.PeerSeenChange {
if n, ok := peerByID[nodeID]; ok {
if seen {
n.LastSeen = &now
} else {
n.LastSeen = nil
}
}
}
}
mapRes.Peers = newFull
mapRes.PeersChanged = nil
mapRes.PeersRemoved = nil

View File

@@ -18,13 +18,13 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/wgkey"
"tailscale.com/util/dnsname"
"tailscale.com/wgengine/filter"
)
type NetworkMap struct {
// Core networking
SelfNode *tailcfg.Node
NodeKey tailcfg.NodeKey
PrivateKey wgkey.Private
Expiry time.Time
@@ -63,16 +63,27 @@ type NetworkMap struct {
// TODO(crawshaw): Capabilities []tailcfg.Capability
}
// MagicDNSSuffix returns the domain's MagicDNS suffix (even if
// MagicDNS isn't necessarily in use).
//
// It will neither start nor end with a period.
// MagicDNSSuffix returns the domain's MagicDNS suffix, or empty if none.
// If non-empty, it will neither start nor end with a period.
func (nm *NetworkMap) MagicDNSSuffix() string {
name := strings.Trim(nm.Name, ".")
if i := strings.Index(name, "."); i != -1 {
name = name[i+1:]
searchPathUsedAsDNSSuffix := func(suffix string) bool {
if dnsname.HasSuffix(nm.Name, suffix) {
return true
}
for _, p := range nm.Peers {
if dnsname.HasSuffix(p.Name, suffix) {
return true
}
}
return false
}
return name
for _, d := range nm.DNS.Domains {
if searchPathUsedAsDNSSuffix(d) {
return strings.Trim(d, ".")
}
}
return ""
}
func (nm *NetworkMap) String() string {
@@ -301,14 +312,12 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
for _, allowedIP := range peer.AllowedIPs {
if allowedIP.Bits == 0 {
if (flags & AllowDefaultRoute) == 0 {
logf("[v1] wgcfg: not accepting default route from %q (%v)",
nodeDebugName(peer), peer.Key.ShortString())
logf("[v1] wgcfg: %v skipping default route", peer.Key.ShortString())
continue
}
} else if cidrIsSubnet(peer, allowedIP) {
if (flags & AllowSubnetRoutes) == 0 {
logf("[v1] wgcfg: not accepting subnet route %v from %q (%v)",
allowedIP, nodeDebugName(peer), peer.Key.ShortString())
logf("[v1] wgcfg: %v skipping subnet route", peer.Key.ShortString())
continue
}
}
@@ -319,20 +328,6 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
return cfg, nil
}
func nodeDebugName(n *tailcfg.Node) string {
name := n.Name
if name == "" {
name = n.Hostinfo.Hostname
}
if i := strings.Index(name, "."); i != -1 {
name = name[:i]
}
if name == "" && len(n.Addresses) != 0 {
return n.Addresses[0].String()
}
return name
}
// cidrIsSubnet reports whether cidr is a non-default-route subnet
// exported by node that is not one of its own self addresses.
func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool {

View File

@@ -70,7 +70,7 @@ func Parse(p []byte) (Message, error) {
case TypePong:
return parsePong(ver, p)
case TypeCallMeMaybe:
return parseCallMeMaybe(ver, p)
return CallMeMaybe{}, nil
default:
return nil, fmt.Errorf("unknown message type 0x%02x", byte(t))
}
@@ -122,57 +122,13 @@ func parsePing(ver uint8, p []byte) (m *Ping, err error) {
//
// The recipient may choose to not open a path back, if it's already
// happy with its path. But usually it will.
type CallMeMaybe struct {
// MyNumber is what the peer believes its endpoints are.
//
// Prior to Tailscale 1.4, the endpoints were exchanged purely
// between nodes and the control server.
//
// Starting with Tailscale 1.4, clients advertise their endpoints.
// Older clients won't use this, but newer clients should
// use any endpoints in here that aren't included from control.
//
// Control might have sent stale endpoints if the client was idle
// before contacting us. In that case, the client likely did a STUN
// request immediately before sending the CallMeMaybe to recreate
// their NAT port mapping, and that new good endpoint is included
// in this field, but might not yet be in control's endpoints.
// (And in the future, control will stop distributing endpoints
// when clients are suitably new.)
MyNumber []netaddr.IPPort
}
type CallMeMaybe struct{}
const epLength = 16 + 2 // 16 byte IP address + 2 byte port
func (m *CallMeMaybe) AppendMarshal(b []byte) []byte {
ret, p := appendMsgHeader(b, TypeCallMeMaybe, v0, epLength*len(m.MyNumber))
for _, ipp := range m.MyNumber {
a := ipp.IP.As16()
copy(p[:], a[:])
binary.BigEndian.PutUint16(p[16:], ipp.Port)
p = p[epLength:]
}
func (CallMeMaybe) AppendMarshal(b []byte) []byte {
ret, _ := appendMsgHeader(b, TypeCallMeMaybe, v0, 0)
return ret
}
func parseCallMeMaybe(ver uint8, p []byte) (m *CallMeMaybe, err error) {
m = new(CallMeMaybe)
if len(p)%epLength != 0 || ver != 0 || len(p) == 0 {
return m, nil
}
m.MyNumber = make([]netaddr.IPPort, 0, len(p)/epLength)
for len(p) > 0 {
var a [16]byte
copy(a[:], p)
m.MyNumber = append(m.MyNumber, netaddr.IPPort{
IP: netaddr.IPFrom16(a),
Port: binary.BigEndian.Uint16(p[16:18]),
})
p = p[epLength:]
}
return m, nil
}
// Pong is a response a Ping.
//
// It includes the sender's source IP + port, so it's effectively a
@@ -215,7 +171,7 @@ func MessageSummary(m Message) string {
return fmt.Sprintf("ping tx=%x", m.TxID[:6])
case *Pong:
return fmt.Sprintf("pong tx=%x", m.TxID[:6])
case *CallMeMaybe:
case CallMeMaybe:
return "call-me-maybe"
default:
return fmt.Sprintf("%#v", m)

View File

@@ -44,19 +44,9 @@ func TestMarshalAndParse(t *testing.T) {
},
{
name: "call_me_maybe",
m: &CallMeMaybe{},
m: CallMeMaybe{},
want: "03 00",
},
{
name: "call_me_maybe_endpoints",
m: &CallMeMaybe{
MyNumber: []netaddr.IPPort{
netaddr.MustParseIPPort("1.2.3.4:567"),
netaddr.MustParseIPPort("[2001::3456]:789"),
},
},
want: "03 00 00 00 00 00 00 00 00 00 00 00 ff ff 01 02 03 04 02 37 20 01 00 00 00 00 00 00 00 00 00 00 00 00 34 56 03 15",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

2
go.mod
View File

@@ -24,7 +24,7 @@ require (
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
github.com/peterbourgon/ff/v2 v2.0.0
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
github.com/tailscale/wireguard-go v0.0.0-20210120212909-7ad8a0443bd3
github.com/tailscale/wireguard-go v0.0.0-20210116013233-4cd297ed5a7d
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174

2
go.sum
View File

@@ -294,8 +294,6 @@ github.com/tailscale/wireguard-go v0.0.0-20210114205708-a1377e83f551 h1:hjBVxvVa
github.com/tailscale/wireguard-go v0.0.0-20210114205708-a1377e83f551/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8=
github.com/tailscale/wireguard-go v0.0.0-20210116013233-4cd297ed5a7d h1:8GcGtZ4Ui+lzHm6gOq7s2Oe4ksxkbUYtS/JuoJ2Nce8=
github.com/tailscale/wireguard-go v0.0.0-20210116013233-4cd297ed5a7d/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8=
github.com/tailscale/wireguard-go v0.0.0-20210120212909-7ad8a0443bd3 h1:wpgSErXul2ysBGZVVM0fKISMgZ9BZRXuOYAyn8MxAbY=
github.com/tailscale/wireguard-go v0.0.0-20210120212909-7ad8a0443bd3/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=

View File

@@ -7,7 +7,6 @@ package ipnserver
import (
"bufio"
"context"
"encoding/json"
"errors"
"fmt"
"io"
@@ -33,7 +32,6 @@ import (
"tailscale.com/net/netstat"
"tailscale.com/safesocket"
"tailscale.com/smallzstd"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/util/pidowner"
"tailscale.com/util/systemd"
@@ -505,6 +503,41 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
}()
logf("Listening on %v", listen.Addr())
bo := backoff.NewBackoff("ipnserver", logf, 30*time.Second)
var unservedConn net.Conn // if non-nil, accepted, but hasn't served yet
eng, err := getEngine()
if err != nil {
logf("ipnserver: initial getEngine call: %v", err)
for i := 1; ctx.Err() == nil; i++ {
c, err := listen.Accept()
if err != nil {
logf("%d: Accept: %v", i, err)
bo.BackOff(ctx, err)
continue
}
logf("ipnserver: try%d: trying getEngine again...", i)
eng, err = getEngine()
if err == nil {
logf("%d: GetEngine worked; exiting failure loop", i)
unservedConn = c
break
}
logf("ipnserver%d: getEngine failed again: %v", i, err)
errMsg := err.Error()
go func() {
defer c.Close()
serverToClient := func(b []byte) { ipn.WriteMsg(c, b) }
bs := ipn.NewBackendServer(logf, nil, serverToClient)
bs.SendErrorMessage(errMsg)
time.Sleep(time.Second)
}()
}
if err := ctx.Err(); err != nil {
return err
}
}
var store ipn.StateStore
if opts.StatePath != "" {
store, err = ipn.NewFileStore(opts.StatePath)
@@ -533,82 +566,6 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
store = &ipn.MemoryStore{}
}
bo := backoff.NewBackoff("ipnserver", logf, 30*time.Second)
var unservedConn net.Conn // if non-nil, accepted, but hasn't served yet
eng, err := getEngine()
if err != nil {
logf("ipnserver: initial getEngine call: %v", err)
// Issue 1187: on Windows, in unattended mode,
// sometimes we try 5 times and fail to create the
// engine before the system's ready. Hack until the
// bug if fixed properly: if we're running in
// unattended mode on Windows, keep trying forever,
// waiting for the machine to be ready (networking to
// come up?) and then dial our own safesocket TCP
// listener to wake up the usual mechanism that lets
// us surface getEngine errors to UI clients. (We
// don't want to just call getEngine in a loop without
// the listener.Accept, as we do want to handle client
// connections so we can tell them about errors)
bootRaceWaitForEngine, bootRaceWaitForEngineCancel := context.WithTimeout(context.Background(), time.Minute)
if runtime.GOOS == "windows" && opts.AutostartStateKey != "" {
logf("ipnserver: in unattended mode, waiting for engine availability")
getEngine = getEngineUntilItWorksWrapper(getEngine)
// Wait for it to be ready.
go func() {
defer bootRaceWaitForEngineCancel()
t0 := time.Now()
for {
time.Sleep(10 * time.Second)
if _, err := getEngine(); err != nil {
logf("ipnserver: unattended mode engine load: %v", err)
continue
}
c, err := net.Dial("tcp", listen.Addr().String())
logf("ipnserver: engine created after %v; waking up Accept: Dial error: %v", time.Since(t0).Round(time.Second), err)
if err == nil {
c.Close()
}
break
}
}()
} else {
bootRaceWaitForEngineCancel()
}
for i := 1; ctx.Err() == nil; i++ {
c, err := listen.Accept()
if err != nil {
logf("%d: Accept: %v", i, err)
bo.BackOff(ctx, err)
continue
}
<-bootRaceWaitForEngine.Done()
logf("ipnserver: try%d: trying getEngine again...", i)
eng, err = getEngine()
if err == nil {
logf("%d: GetEngine worked; exiting failure loop", i)
unservedConn = c
break
}
logf("ipnserver%d: getEngine failed again: %v", i, err)
errMsg := err.Error()
go func() {
defer c.Close()
serverToClient := func(b []byte) { ipn.WriteMsg(c, b) }
bs := ipn.NewBackendServer(logf, nil, serverToClient)
bs.SendErrorMessage(errMsg)
time.Sleep(time.Second)
}()
}
if err := ctx.Err(); err != nil {
return err
}
}
b, err := ipn.NewLocalBackend(logf, logid, store, eng)
if err != nil {
return fmt.Errorf("NewLocalBackend: %v", err)
@@ -622,7 +579,6 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
opts.DebugMux.HandleFunc("/debug/ipn", func(w http.ResponseWriter, r *http.Request) {
serveHTMLStatus(w, b)
})
opts.DebugMux.Handle("/whois", whoIsHandler{b})
}
server.b = b
@@ -800,27 +756,6 @@ func FixedEngine(eng wgengine.Engine) func() (wgengine.Engine, error) {
return func() (wgengine.Engine, error) { return eng, nil }
}
// getEngineUntilItWorksWrapper returns a getEngine wrapper that does
// not call getEngine concurrently and stops calling getEngine once
// it's returned a working engine.
func getEngineUntilItWorksWrapper(getEngine func() (wgengine.Engine, error)) func() (wgengine.Engine, error) {
var mu sync.Mutex
var engGood wgengine.Engine
return func() (wgengine.Engine, error) {
mu.Lock()
defer mu.Unlock()
if engGood != nil {
return engGood, nil
}
e, err := getEngine()
if err != nil {
return nil, err
}
engGood = e
return e, nil
}
}
type dummyAddr string
type oneConnListener struct {
conn net.Conn
@@ -886,40 +821,3 @@ func peerPid(entries []netstat.Entry, la, ra netaddr.IPPort) int {
}
return 0
}
// whoIsHandler is the debug server's /debug?ip=$IP HTTP handler.
type whoIsHandler struct {
b *ipn.LocalBackend
}
func (h whoIsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
b := h.b
var ip netaddr.IP
if v := r.FormValue("ip"); v != "" {
var err error
ip, err = netaddr.ParseIP(r.FormValue("ip"))
if err != nil {
http.Error(w, "invalid 'ip' parameter", 400)
return
}
} else {
http.Error(w, "missing 'ip' parameter", 400)
return
}
n, u, ok := b.WhoIs(ip)
if !ok {
http.Error(w, "no match for IP", 404)
return
}
res := &tailcfg.WhoIsResponse{
Node: n,
UserProfile: &u,
}
j, err := json.MarshalIndent(res, "", "\t")
if err != nil {
http.Error(w, "JSON encoding error", 500)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(j)
}

View File

@@ -21,21 +21,14 @@ import (
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/util/dnsname"
)
// Status represents the entire state of the IPN network.
type Status struct {
BackendState string
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Self *PeerStatus
// MagicDNSSuffix is the network's MagicDNS suffix for nodes
// in the network such as "userfoo.tailscale.net".
// There are no surrounding dots.
// MagicDNSSuffix should be populated regardless of whether a domain
// has MagicDNS enabled.
MagicDNSSuffix string
BackendState string
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Self *PeerStatus
MagicDNSSuffix string // e.g. "userfoo.tailscale.net" (no surrounding dots)
Peer map[key.Public]*PeerStatus
User map[tailcfg.UserID]tailcfg.UserProfile
@@ -281,22 +274,13 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
f("<p>Tailscale IP: %s", strings.Join(ips, ", "))
f("<table>\n<thead>\n")
f("<tr><th>Peer</th><th>OS</th><th>Node</th><th>Owner</th><th>Rx</th><th>Tx</th><th>Activity</th><th>Connection</th></tr>\n")
f("<tr><th>Peer</th><th>Node</th><th>Owner</th><th>Rx</th><th>Tx</th><th>Activity</th><th>Endpoints</th></tr>\n")
f("</thead>\n<tbody>\n")
now := time.Now()
var peers []*PeerStatus
for _, peer := range st.Peers() {
ps := st.Peer[peer]
if ps.ShareeNode {
continue
}
peers = append(peers, ps)
}
SortPeers(peers)
for _, ps := range peers {
var actAgo string
if !ps.LastWrite.IsZero() {
ago := now.Sub(ps.LastWrite)
@@ -312,44 +296,40 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
owner = owner[:i]
}
}
hostName := ps.SimpleHostName()
dnsName := strings.TrimRight(ps.DNSName, ".")
if i := strings.Index(dnsName, "."); i != -1 && dnsname.HasSuffix(dnsName, st.MagicDNSSuffix) {
dnsName = dnsName[:i]
}
if strings.EqualFold(dnsName, hostName) || ps.UserID != st.Self.UserID {
hostName = ""
}
var hostNameHTML string
if hostName != "" {
hostNameHTML = "<br>" + html.EscapeString(hostName)
}
f("<tr><td>%s</td><td class=acenter>%s</td>"+
"<td><b>%s</b>%s<div class=\"tailaddr\">%s</div></td><td class=\"acenter owner\">%s</td><td class=\"aright\">%v</td><td class=\"aright\">%v</td><td class=\"aright\">%v</td>",
ps.PublicKey.ShortString(),
f("<tr><td>%s</td><td>%s %s<br><span class=\"tailaddr\">%s</span></td><td class=\"acenter owner\">%s</td><td class=\"aright\">%v</td><td class=\"aright\">%v</td><td class=\"aright\">%v</td>",
peer.ShortString(),
html.EscapeString(ps.SimpleHostName()),
osEmoji(ps.OS),
html.EscapeString(dnsName),
hostNameHTML,
ps.TailAddr,
html.EscapeString(owner),
ps.RxBytes,
ps.TxBytes,
actAgo,
)
f("<td>")
f("<td class=\"aright\">")
// TODO: let server report this active bool instead
active := !ps.LastWrite.IsZero() && time.Since(ps.LastWrite) < 2*time.Minute
if active {
if ps.Relay != "" && ps.CurAddr == "" {
f("relay <b>%s</b>", html.EscapeString(ps.Relay))
} else if ps.CurAddr != "" {
f("direct <b>%s</b>", html.EscapeString(ps.CurAddr))
relay := ps.Relay
if relay != "" {
if active && ps.CurAddr == "" {
f("🔗 <b>derp-%v</b><br>", html.EscapeString(relay))
} else {
f("derp-%v<br>", html.EscapeString(relay))
}
}
match := false
for _, addr := range ps.Addrs {
if addr == ps.CurAddr {
match = true
f("🔗 <b>%s</b><br>", addr)
} else {
f("%s<br>", addr)
}
}
if ps.CurAddr != "" && !match {
f("<b>%s</b> \xf0\x9f\xa7\xb3<br>", ps.CurAddr)
}
f("</td>") // end Addrs
f("</tr>\n")
@@ -395,17 +375,3 @@ type PingResult struct {
// TODO(bradfitz): details like whether port mapping was used on either side? (Once supported)
}
func SortPeers(peers []*PeerStatus) {
sort.Slice(peers, func(i, j int) bool { return sortKey(peers[i]) < sortKey(peers[j]) })
}
func sortKey(ps *PeerStatus) string {
if ps.DNSName != "" {
return ps.DNSName
}
if ps.HostName != "" {
return ps.HostName
}
return ps.TailAddr
}

View File

@@ -90,7 +90,6 @@ type LocalBackend struct {
hostinfo *tailcfg.Hostinfo
// netMap is not mutated in-place once set.
netMap *controlclient.NetworkMap
nodeByAddr map[netaddr.IP]*tailcfg.Node
activeLogin string // last logged LoginName from netMap
engineStatus EngineStatus
endpoints []string
@@ -235,22 +234,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
})
}
}
}
// WhoIs reports the node and user who owns the node with the given IP.
// If ok == true, n and u are valid.
func (b *LocalBackend) WhoIs(ip netaddr.IP) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
b.mu.Lock()
defer b.mu.Unlock()
n, ok = b.nodeByAddr[ip]
if !ok {
return nil, u, false
}
u, ok = b.netMap.UserProfiles[n.User]
if !ok {
return nil, u, false
}
return n, u, true
}
// SetDecompressor sets a decompression function, which must be a zstd
@@ -578,13 +562,12 @@ func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Pre
localNets := unmapIPPrefixes(netMap.Addresses, advRoutes)
oldFilter := b.e.GetFilter()
if shieldsUp {
b.logf("netmap packet filter: (shields up)")
b.e.SetFilter(filter.NewShieldsUpFilter(localNets, oldFilter, b.logf))
b.e.SetFilter(filter.NewShieldsUpFilter(b.logf))
} else {
b.logf("netmap packet filter: %v", packetFilter)
b.e.SetFilter(filter.New(packetFilter, localNets, oldFilter, b.logf))
b.e.SetFilter(filter.New(packetFilter, localNets, b.e.GetFilter(), b.logf))
}
}
@@ -1523,39 +1506,6 @@ func (b *LocalBackend) setNetMapLocked(nm *controlclient.NetworkMap) {
b.logf("active login: %v", login)
b.activeLogin = login
}
if nm == nil {
b.nodeByAddr = nil
return
}
// Update the nodeByAddr index.
if b.nodeByAddr == nil {
b.nodeByAddr = map[netaddr.IP]*tailcfg.Node{}
}
// First pass, mark everything unwanted.
for k := range b.nodeByAddr {
b.nodeByAddr[k] = nil
}
addNode := func(n *tailcfg.Node) {
for _, ipp := range n.Addresses {
if ipp.IsSingleIP() {
b.nodeByAddr[ipp.IP] = n
}
}
}
if nm.SelfNode != nil {
addNode(nm.SelfNode)
}
for _, p := range nm.Peers {
addNode(p)
}
// Third pass, actually delete the unwanted items.
for k, v := range b.nodeByAddr {
if v == nil {
delete(b.nodeByAddr, k)
}
}
}
// TestOnlyPublicKeys returns the current machine and node public

View File

@@ -10,7 +10,6 @@ import (
"net"
"net/http"
"reflect"
"runtime"
"sort"
"strings"
@@ -40,11 +39,8 @@ func Tailscale() (net.IP, *net.Interface, error) {
continue
}
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok {
nip, ok := netaddr.FromStdIP(ipnet.IP)
if ok && tsaddr.IsTailscaleIP(nip) {
return ipnet.IP, &iface, nil
}
if ipnet, ok := a.(*net.IPNet); ok && IsTailscaleIP(ipnet.IP) {
return ipnet.IP, &iface, nil
}
}
}
@@ -61,21 +57,16 @@ func maybeTailscaleInterfaceName(s string) bool {
strings.HasPrefix(s, "utun")
}
// IsTailscaleIP reports whether ip is an IP in a range used by
// Tailscale virtual network interfaces.
func IsTailscaleIP(ip net.IP) bool {
nip, _ := netaddr.FromStdIP(ip) // TODO: push this up to caller, change func signature
return tsaddr.IsTailscaleIP(nip)
}
func isUp(nif *net.Interface) bool { return nif.Flags&net.FlagUp != 0 }
func isLoopback(nif *net.Interface) bool { return nif.Flags&net.FlagLoopback != 0 }
func isProblematicInterface(nif *net.Interface) bool {
name := nif.Name
// Don't try to send disco/etc packets over zerotier; they effectively
// DoS each other by doing traffic amplification, both of them
// preferring/trying to use each other for transport. See:
// https://github.com/tailscale/tailscale/issues/1208
if strings.HasPrefix(name, "zt") || (runtime.GOOS == "windows" && strings.Contains(name, "ZeroTier")) {
return true
}
return false
}
// LocalAddresses returns the machine's IP addresses, separated by
// whether they're loopback addresses.
func LocalAddresses() (regular, loopback []string, err error) {
@@ -86,10 +77,8 @@ func LocalAddresses() (regular, loopback []string, err error) {
}
for i := range ifaces {
iface := &ifaces[i]
if !isUp(iface) || isProblematicInterface(iface) {
// Skip down interfaces and ones that are
// problematic that we don't want to try to
// send Tailscale traffic over.
if !isUp(iface) {
// Down interfaces don't count
continue
}
ifcIsLoopback := isLoopback(iface)

View File

@@ -5,9 +5,30 @@
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)
}
}
}
func TestGetState(t *testing.T) {
st, err := GetState()
if err != nil {

View File

@@ -59,32 +59,12 @@ func listen(path string, port uint16) (ln net.Listener, _ uint16, err error) {
return nil, 0, fmt.Errorf("%v: address already in use", path)
}
_ = os.Remove(path)
perm := socketPermissionsForOS()
sockDir := filepath.Dir(path)
if _, err := os.Stat(sockDir); os.IsNotExist(err) {
os.MkdirAll(sockDir, 0755) // best effort
// If we're on a platform where we want the socket
// world-readable, open up the permissions on the
// just-created directory too, in case a umask ate
// it. This primarily affects running tailscaled by
// hand as root in a shell, as there is no umask when
// running under systemd.
if perm == 0666 {
if fi, err := os.Stat(sockDir); err == nil && fi.Mode()&0077 == 0 {
if err := os.Chmod(sockDir, 0755); err != nil {
log.Print(err)
}
}
}
}
os.MkdirAll(filepath.Dir(path), 0755) // best effort
pipe, err := net.Listen("unix", path)
if err != nil {
return nil, 0, err
}
os.Chmod(path, perm)
os.Chmod(path, socketPermissionsForOS())
return pipe, 0, err
}

View File

@@ -20,7 +20,6 @@ import (
"tailscale.com/types/key"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
"tailscale.com/util/dnsname"
)
// CurrentMapRequestVersion is the current MapRequest.Version value.
@@ -33,10 +32,7 @@ import (
// 7: 2020-12-15: FilterRule.SrcIPs accepts CIDRs+ranges, doesn't warn about 0.0.0.0/::
// 8: 2020-12-19: client can receive IPv6 addresses and routes if beta enabled server-side
// 9: 2020-12-30: client doesn't auto-add implicit search domains from peers; only DNSConfig.Domains
// 10: 2021-01-17: client understands MapResponse.PeerSeenChange
const CurrentMapRequestVersion = 10
type StableID string
const CurrentMapRequestVersion = 9
type ID int64
@@ -58,12 +54,6 @@ func (u NodeID) IsZero() bool {
return u == 0
}
type StableNodeID StableID
func (u StableNodeID) IsZero() bool {
return u == ""
}
type GroupID ID
func (u GroupID) IsZero() bool {
@@ -157,9 +147,8 @@ type UserProfile struct {
}
type Node struct {
ID NodeID
StableID StableNodeID
Name string // DNS
ID NodeID
Name string // DNS
// User is the user who created the node. If ACL tags are in
// use for the node then it doesn't reflect the ACL identity
@@ -184,98 +173,6 @@ type Node struct {
KeepAlive bool `json:",omitempty"` // open and keep open a connection to this peer
MachineAuthorized bool `json:",omitempty"` // TODO(crawshaw): replace with MachineStatus
// The following three computed fields hold the various names that can
// be used for this node in UIs. They are populated from controlclient
// (not from control) by calling node.InitDisplayNames. These can be
// used directly or accessed via node.DisplayName or node.DisplayNames.
ComputedName string `json:",omitempty"` // MagicDNS base name (for normal non-shared-in nodes), FQDN (without trailing dot, for shared-in nodes), or Hostname (if no MagicDNS)
computedHostIfDifferent string // hostname, if different than ComputedName, otherwise empty
ComputedNameWithHost string `json:",omitempty"` // either "ComputedName" or "ComputedName (computedHostIfDifferent)", if computedHostIfDifferent is set
}
// DisplayName returns the user-facing name for a node which should
// be shown in client UIs.
//
// Parameter forOwner specifies whether the name is requested by
// the owner of the node. When forOwner is false, the hostname is
// never included in the return value.
//
// Return value is either either "Name" or "Name (Hostname)", where
// Name is the node's MagicDNS base name (for normal non-shared-in
// nodes), FQDN (without trailing dot, for shared-in nodes), or
// Hostname (if no MagicDNS). Hostname is only included in the
// return value if it varies from Name and forOwner is provided true.
//
// DisplayName is only valid if InitDisplayNames has been called.
func (n *Node) DisplayName(forOwner bool) string {
if forOwner {
return n.ComputedNameWithHost
}
return n.ComputedName
}
// DisplayName returns the decomposed user-facing name for a node.
//
// Parameter forOwner specifies whether the name is requested by
// the owner of the node. When forOwner is false, hostIfDifferent
// is always returned empty.
//
// Return value name is the node's primary name, populated with the
// node's MagicDNS base name (for normal non-shared-in nodes), FQDN
// (without trailing dot, for shared-in nodes), or Hostname (if no
// MagicDNS).
//
// Return value hostIfDifferent, when non-empty, is the node's
// hostname. hostIfDifferent is only populated when the hostname
// varies from name and forOwner is provided as true.
//
// DisplayNames is only valid if InitDisplayNames has been called.
func (n *Node) DisplayNames(forOwner bool) (name, hostIfDifferent string) {
if forOwner {
return n.ComputedName, n.computedHostIfDifferent
}
return n.ComputedName, ""
}
// InitDisplayNames computes and populates n's display name
// fields: n.ComputedName, n.computedHostIfDifferent, and
// n.ComputedNameWithHost.
func (n *Node) InitDisplayNames(networkMagicDNSSuffix string) {
dnsName := n.Name
if dnsName != "" {
dnsName = strings.TrimRight(dnsName, ".")
if i := strings.Index(dnsName, "."); i != -1 && dnsname.HasSuffix(dnsName, networkMagicDNSSuffix) {
dnsName = dnsName[:i]
}
}
name := dnsName
hostIfDifferent := n.Hostinfo.Hostname
if strings.EqualFold(name, hostIfDifferent) {
hostIfDifferent = ""
}
if name == "" {
if hostIfDifferent != "" {
name = hostIfDifferent
hostIfDifferent = ""
} else {
name = n.Key.String()
}
}
var nameWithHost string
if hostIfDifferent != "" {
nameWithHost = fmt.Sprintf("%s (%s)", name, hostIfDifferent)
} else {
nameWithHost = name
}
n.ComputedName = name
n.computedHostIfDifferent = hostIfDifferent
n.ComputedNameWithHost = nameWithHost
}
type MachineStatus int
@@ -739,11 +636,6 @@ type MapResponse struct {
// PeersRemoved are the NodeIDs that are no longer in the peer list.
PeersRemoved []NodeID `json:",omitempty"`
// PeerSeenChange contains information on how to update peers' LastSeen
// times. If the value is false, the peer is gone. If the value is true,
// the LastSeen time is now. Absent means unchanged.
PeerSeenChange map[NodeID]bool `json:",omitempty"`
// DNS is the same as DNSConfig.Nameservers.
//
// TODO(dmytro): should be sent in DNSConfig.Nameservers once clients have updated.
@@ -887,7 +779,6 @@ func (n *Node) Equal(n2 *Node) bool {
}
return n != nil && n2 != nil &&
n.ID == n2.ID &&
n.StableID == n2.StableID &&
n.Name == n2.Name &&
n.User == n2.User &&
n.Sharer == n2.Sharer &&
@@ -902,10 +793,7 @@ func (n *Node) Equal(n2 *Node) bool {
n.Hostinfo.Equal(&n2.Hostinfo) &&
n.Created.Equal(n2.Created) &&
eqTimePtr(n.LastSeen, n2.LastSeen) &&
n.MachineAuthorized == n2.MachineAuthorized &&
n.ComputedName == n2.ComputedName &&
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
n.ComputedNameWithHost == n2.ComputedNameWithHost
n.MachineAuthorized == n2.MachineAuthorized
}
func eqStrings(a, b []string) bool {
@@ -935,9 +823,3 @@ func eqCIDRs(a, b []netaddr.IPPrefix) bool {
func eqTimePtr(a, b *time.Time) bool {
return ((a == nil) == (b == nil)) && (a == nil || a.Equal(*b))
}
// WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler.
type WhoIsResponse struct {
Node *Node
UserProfile *UserProfile
}

View File

@@ -61,27 +61,23 @@ func (src *Node) Clone() *Node {
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig,RegisterResponse
var _NodeNeedsRegeneration = Node(struct {
ID NodeID
StableID StableNodeID
Name string
User UserID
Sharer UserID
Key NodeKey
KeyExpiry time.Time
Machine MachineKey
DiscoKey DiscoKey
Addresses []netaddr.IPPrefix
AllowedIPs []netaddr.IPPrefix
Endpoints []string
DERP string
Hostinfo Hostinfo
Created time.Time
LastSeen *time.Time
KeepAlive bool
MachineAuthorized bool
ComputedName string
computedHostIfDifferent string
ComputedNameWithHost string
ID NodeID
Name string
User UserID
Sharer UserID
Key NodeKey
KeyExpiry time.Time
Machine MachineKey
DiscoKey DiscoKey
Addresses []netaddr.IPPrefix
AllowedIPs []netaddr.IPPrefix
Endpoints []string
DERP string
Hostinfo Hostinfo
Created time.Time
LastSeen *time.Time
KeepAlive bool
MachineAuthorized bool
}{})
// Clone makes a deep copy of Hostinfo.

View File

@@ -189,11 +189,10 @@ func TestHostinfoEqual(t *testing.T) {
func TestNodeEqual(t *testing.T) {
nodeHandles := []string{
"ID", "StableID", "Name", "User", "Sharer",
"ID", "Name", "User", "Sharer",
"Key", "KeyExpiry", "Machine", "DiscoKey",
"Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo",
"Created", "LastSeen", "KeepAlive", "MachineAuthorized",
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
}
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
@@ -230,31 +229,6 @@ func TestNodeEqual(t *testing.T) {
&Node{},
true,
},
{
&Node{},
&Node{},
true,
},
{
&Node{ID: 1},
&Node{},
false,
},
{
&Node{ID: 1},
&Node{ID: 1},
true,
},
{
&Node{StableID: "node-abcd"},
&Node{},
false,
},
{
&Node{StableID: "node-abcd"},
&Node{StableID: "node-abcd"},
true,
},
{
&Node{User: 0},
&Node{User: 1},

View File

@@ -1,44 +0,0 @@
// Copyright (c) 2021 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 tstime
import (
crand "crypto/rand"
"encoding/binary"
"math/rand"
"sync"
"time"
)
// crandSource is a rand.Source64 that gets its numbers from
// crypto/rand.Reader.
type crandSource struct{ sync.Mutex }
var _ rand.Source64 = (*crandSource)(nil)
func (s *crandSource) Int63() int64 { return int64(s.Uint64() >> 1) }
func (s *crandSource) Uint64() uint64 {
s.Lock()
defer s.Unlock()
var buf [8]byte
crand.Read(buf[:])
return binary.BigEndian.Uint64(buf[:])
}
func (*crandSource) Seed(seed int64) {} // nope
var durRand = rand.New(new(crandSource))
// RandomDurationBetween returns a random duration in range [min,max).
// If panics if max < min.
func RandomDurationBetween(min, max time.Duration) time.Duration {
diff := max - min
if diff == 0 {
return min
}
ns := durRand.Int63n(int64(diff))
return min + time.Duration(ns)
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) 2021 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 tstime
import (
"testing"
"time"
)
func TestRandomDurationBetween(t *testing.T) {
if got := RandomDurationBetween(1, 1); got != 1 {
t.Errorf("between 1 and 1 = %v; want 1", int64(got))
}
const min = 1 * time.Second
const max = 10 * time.Second
for i := 0; i < 500; i++ {
if got := RandomDurationBetween(min, max); got < min || got >= max {
t.Fatalf("%v (%d) out of range", got, got)
}
}
}

View File

@@ -23,9 +23,8 @@ import (
"strings"
"time"
"inet.af/netaddr"
"tailscale.com/metrics"
"tailscale.com/net/tsaddr"
"tailscale.com/net/interfaces"
"tailscale.com/types/logger"
)
@@ -82,11 +81,8 @@ func AllowDebugAccess(r *http.Request) bool {
if err != nil {
return false
}
ip, err := netaddr.ParseIP(ipStr)
if err != nil {
return false
}
if tsaddr.IsTailscaleIP(ip) || ip.IsLoopback() || ipStr == os.Getenv("TS_ALLOW_DEBUG_IP") {
ip := net.ParseIP(ipStr)
if interfaces.IsTailscaleIP(ip) || ip.IsLoopback() || ipStr == os.Getenv("TS_ALLOW_DEBUG_IP") {
return true
}
if r.Method == "GET" {

View File

@@ -64,9 +64,9 @@ type limitData struct {
var disableRateLimit = os.Getenv("TS_DEBUG_LOG_RATE") == "all"
// rateFree are format string substrings that are exempt from rate limiting.
// rateFreePrefix are format string prefixes that are exempt from rate limiting.
// Things should not be added to this unless they're already limited otherwise.
var rateFree = []string{
var rateFreePrefix = []string{
"magicsock: disco: ",
"magicsock: CreateEndpoint:",
}
@@ -93,8 +93,8 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
)
judge := func(format string) verdict {
for _, sub := range rateFree {
if strings.Contains(format, sub) {
for _, pfx := range rateFreePrefix {
if strings.HasPrefix(format, pfx) {
return allow
}
}
@@ -132,7 +132,7 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
logf(format, args...)
case warn:
// For the warning, log the specific format string
logf("[RATE LIMITED] format string \"%s\" (example: \"%s\")", format, strings.TrimSpace(fmt.Sprintf(format, args...)))
logf("[RATE LIMITED] format string \"%s\" (example: \"%s\")", format, fmt.Sprintf(format, args...))
}
}
}

View File

@@ -132,16 +132,8 @@ func NewAllowNone(logf logger.Logf) *Filter {
return New(nil, nil, nil, logf)
}
// NewShieldsUpFilter returns a packet filter that rejects incoming connections.
//
// If shareStateWith is non-nil, the returned filter shares state with the previous one,
// as long as the previous one was also a shields up filter.
func NewShieldsUpFilter(localNets []netaddr.IPPrefix, shareStateWith *Filter, logf logger.Logf) *Filter {
// Don't permit sharing state with a prior filter that wasn't a shields-up filter.
if shareStateWith != nil && !shareStateWith.shieldsUp {
shareStateWith = nil
}
f := New(nil, localNets, shareStateWith, logf)
func NewShieldsUpFilter(logf logger.Logf) *Filter {
f := New(nil, nil, nil, logf)
f.shieldsUp = true
return f
}

View File

@@ -12,8 +12,6 @@ import (
"tailscale.com/net/packet"
)
//go:generate go run tailscale.com/cmd/cloner --type=Match --output=match_clone.go
// PortRange is a range of TCP and UDP ports.
type PortRange struct {
First, Last uint16 // inclusive

View File

@@ -1,31 +0,0 @@
// 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.
// Code generated by tailscale.com/cmd/cloner -type Match; DO NOT EDIT.
package filter
import (
"inet.af/netaddr"
)
// Clone makes a deep copy of Match.
// The result aliases no memory with the original.
func (src *Match) Clone() *Match {
if src == nil {
return nil
}
dst := new(Match)
*dst = *src
dst.Dsts = append(src.Dsts[:0:0], src.Dsts...)
dst.Srcs = append(src.Srcs[:0:0], src.Srcs...)
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Match
var _MatchNeedsRegeneration = Match(struct {
Dsts []NetPortRange
Srcs []netaddr.IPPrefix
}{})

View File

@@ -438,7 +438,8 @@ func (a *addrSet) DstToBytes() []byte {
return packIPPort(a.dst())
}
func (a *addrSet) DstToString() string {
return a.Addrs()
dst := a.dst()
return dst.String()
}
func (a *addrSet) DstIP() net.IP {
return a.dst().IP.IPAddr().IP // TODO: add netaddr accessor to cut an alloc here?

View File

@@ -45,10 +45,10 @@ import (
"tailscale.com/net/stun"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tstime"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/nettype"
"tailscale.com/types/opt"
"tailscale.com/types/wgkey"
"tailscale.com/version"
)
@@ -161,11 +161,6 @@ type Conn struct {
// whether a DERP channel read should be done.
derpRecvCountLast int64 // owned by ReceiveIPv4
// ippEndpoint4 and ippEndpoint6 are owned by ReceiveIPv4 and
// ReceiveIPv6, respectively, to cache an IPPort->endpoint for
// hot flows.
ippEndpoint4, ippEndpoint6 ippEndpointCache
// ============================================================
mu sync.Mutex // guards all following fields; see userspaceEngine lock ordering rules
muCond *sync.Cond
@@ -173,19 +168,6 @@ type Conn struct {
started bool // Start was called
closed bool // Close was called
// derpCleanupTimer is the timer that fires to occasionally clean
// up idle DERP connections. It's only used when there is a non-home
// DERP connection in use.
derpCleanupTimer *time.Timer
// derpCleanupTimerArmed is whether derpCleanupTimer is
// scheduled to fire within derpCleanStaleInterval.
derpCleanupTimerArmed bool
// periodicReSTUNTimer, when non-nil, is an AfterFunc timer
// that will call Conn.doPeriodicSTUN.
periodicReSTUNTimer *time.Timer
// endpointsUpdateActive indicates that updateEndpoints is
// currently running. It's used to deduplicate concurrent endpoint
// update requests.
@@ -200,14 +182,6 @@ type Conn struct {
// change notifications.
lastEndpoints []string
// lastEndpointsTime is the last time the endpoints were updated,
// even if there was no change.
lastEndpointsTime time.Time
// onEndpointRefreshed are funcs to run (in their own goroutines)
// when endpoints are refreshed.
onEndpointRefreshed map[*discoEndpoint]func()
// peerSet is the set of peers that are currently configured in
// WireGuard. These are not used to filter inbound or outbound
// traffic at all, but only to track what state can be cleaned up
@@ -503,6 +477,13 @@ func (c *Conn) Start() {
c.mu.Unlock()
c.ReSTUN("initial")
// We assume that LinkChange notifications are plumbed through well
// on our mobile clients, so don't do the timer thing to save radio/battery/CPU/etc.
if !version.IsMobile() {
go c.periodicReSTUN()
}
go c.periodicDerpCleanup()
}
// ignoreSTUNPackets sets a STUN packet processing func that does nothing.
@@ -510,17 +491,6 @@ func (c *Conn) ignoreSTUNPackets() {
c.stunReceiveFunc.Store(func([]byte, netaddr.IPPort) {})
}
// doPeriodicSTUN is called (in a new goroutine) by
// periodicReSTUNTimer when periodic STUNs are active.
func (c *Conn) doPeriodicSTUN() { c.ReSTUN("periodic") }
func (c *Conn) stopPeriodicReSTUNTimerLocked() {
if t := c.periodicReSTUNTimer; t != nil {
t.Stop()
c.periodicReSTUNTimer = nil
}
}
// c.mu must NOT be held.
func (c *Conn) updateEndpoints(why string) {
defer func() {
@@ -528,37 +498,13 @@ func (c *Conn) updateEndpoints(why string) {
defer c.mu.Unlock()
why := c.wantEndpointsUpdate
c.wantEndpointsUpdate = ""
if !c.closed {
if why != "" {
go c.updateEndpoints(why)
return
}
if c.shouldDoPeriodicReSTUNLocked() {
// Pick a random duration between 20
// and 26 seconds (just under 30s, a
// common UDP NAT timeout on Linux,
// etc)
d := tstime.RandomDurationBetween(20*time.Second, 26*time.Second)
if t := c.periodicReSTUNTimer; t != nil {
if debugReSTUNStopOnIdle {
c.logf("resetting existing periodicSTUN to run in %v", d)
}
t.Reset(d)
} else {
if debugReSTUNStopOnIdle {
c.logf("scheduling periodicSTUN to run in %v", d)
}
c.periodicReSTUNTimer = time.AfterFunc(d, c.doPeriodicSTUN)
}
} else {
if debugReSTUNStopOnIdle {
c.logf("periodic STUN idle")
}
c.stopPeriodicReSTUNTimerLocked()
}
if why != "" && !c.closed {
go c.updateEndpoints(why)
} else {
c.endpointsUpdateActive = false
c.muCond.Broadcast()
}
c.endpointsUpdateActive = false
c.muCond.Broadcast()
}()
c.logf("[v1] magicsock: starting endpoint update (%s)", why)
@@ -603,12 +549,6 @@ func (c *Conn) setEndpoints(endpoints []string, reasons map[string]string) (chan
return false
}
c.lastEndpointsTime = time.Now()
for de, fn := range c.onEndpointRefreshed {
go fn()
delete(c.onEndpointRefreshed, de)
}
if stringsEqual(endpoints, c.lastEndpoints) {
return false
}
@@ -1274,7 +1214,6 @@ func (c *Conn) derpWriteChanOfAddr(addr netaddr.IPPort, peer key.Public) chan<-
c.activeDerp[regionID] = ad
c.logActiveDerpLocked()
c.setPeerLastDerpLocked(peer, regionID, regionID)
c.scheduleCleanStaleDerpLocked()
// Build a startGate for the derp reader+writer
// goroutines, so they don't start running until any
@@ -1544,7 +1483,7 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, error) {
if err != nil {
return 0, nil, err
}
if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint6); ok {
if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr)); ok {
return n, ep, nil
}
}
@@ -1581,37 +1520,28 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) {
}
return 0, nil, err
}
if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint4); ok {
if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr)); ok {
return n, ep, nil
}
}
}
// receiveIP is the shared bits of ReceiveIPv4 and ReceiveIPv6.
func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) {
func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr) (ep conn.Endpoint, ok bool) {
ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone)
if !ok {
return nil, false
return
}
if stun.Is(b) {
c.stunReceiveFunc.Load().(func([]byte, netaddr.IPPort))(b, ipp)
return nil, false
return
}
if c.handleDiscoMessage(b, ipp) {
return nil, false
return
}
if cache.ipp == ipp && cache.de != nil && cache.gen == cache.de.numStopAndReset() {
ep = cache.de
} else {
ep = c.findEndpoint(ipp, ua, b)
if ep == nil {
return nil, false
}
if de, ok := ep.(*discoEndpoint); ok {
cache.ipp = ipp
cache.de = de
cache.gen = de.numStopAndReset()
}
ep = c.findEndpoint(ipp, ua, b)
if ep == nil {
return
}
c.noteRecvActivityFromEndpoint(ep)
return ep, true
@@ -1678,7 +1608,7 @@ func (c *Conn) receiveIPv4DERP(b []byte) (n int, ep conn.Endpoint, err error) {
c.mu.Lock()
discoEp = c.endpointOfDisco[dk]
c.logf("magicsock: DERP packet received from idle peer %v; created=%v", dm.src.ShortString(), discoEp != nil)
c.logf("magicsock: DERP packet received from idle peer %v; created=%v", dm.src.ShortString(), ep != nil)
}
}
if !c.disableLegacy {
@@ -1890,18 +1820,15 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
return true
}
de.handlePongConnLocked(dm, src)
case *disco.CallMeMaybe:
case disco.CallMeMaybe:
if src.IP != derpMagicIPAddr {
// CallMeMaybe messages should only come via DERP.
c.logf("[unexpected] CallMeMaybe packets should only come via DERP")
return true
}
if de != nil {
c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
c.discoShort, de.discoShort,
de.publicKey.ShortString(), derpStr(src.String()),
len(dm.MyNumber))
go de.handleCallMeMaybe(dm)
c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe", c.discoShort, de.discoShort, de.publicKey.ShortString(), derpStr(src.String()))
go de.handleCallMeMaybe()
}
}
@@ -1932,47 +1859,6 @@ func (c *Conn) handlePingLocked(dm *disco.Ping, de *discoEndpoint, src netaddr.I
}, discoVerboseLog)
}
// enqueueCallMeMaybe schedules a send of disco.CallMeMaybe to de via derpAddr
// once we know that our STUN endpoint is fresh.
//
// derpAddr is de.derpAddr at the time of send. It's assumed the peer won't be
// flipping primary DERPs in the 0-30ms it takes to confirm our STUN endpoint.
// If they do, traffic will just go over DERP for a bit longer until the next
// discovery round.
func (c *Conn) enqueueCallMeMaybe(derpAddr netaddr.IPPort, de *discoEndpoint) {
c.mu.Lock()
defer c.mu.Unlock()
if !c.lastEndpointsTime.After(time.Now().Add(-endpointsFreshEnoughDuration)) {
c.logf("magicsock: want call-me-maybe but endpoints stale; restunning")
if c.onEndpointRefreshed == nil {
c.onEndpointRefreshed = map[*discoEndpoint]func(){}
}
c.onEndpointRefreshed[de] = func() {
c.logf("magicsock: STUN done; sending call-me-maybe to %v %v", de.discoShort, de.publicKey.ShortString())
c.enqueueCallMeMaybe(derpAddr, de)
}
// TODO(bradfitz): make a new 'reSTUNQuickly' method
// that passes down a do-a-lite-netcheck flag down to
// netcheck that does 1 (or 2 max) STUN queries
// (UDP-only, not HTTPs) to find our port mapping to
// our home DERP and maybe one other. For now we do a
// "full" ReSTUN which may or may not be a full one
// (depending on age) and may do HTTPS timing queries
// (if UDP is blocked). Good enough for now.
go c.ReSTUN("refresh-for-peering")
return
}
eps := make([]netaddr.IPPort, 0, len(c.lastEndpoints))
for _, ep := range c.lastEndpoints {
if ipp, err := netaddr.ParseIPPort(ep); err == nil {
eps = append(eps, ipp)
}
}
go de.sendDiscoMessage(derpAddr, &disco.CallMeMaybe{MyNumber: eps}, discoLog)
}
// setAddrToDiscoLocked records that newk is at src.
//
// c.mu must be held.
@@ -2092,8 +1978,6 @@ func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error {
} else if newKey.IsZero() {
c.logf("magicsock: SetPrivateKey called (zeroed)")
c.closeAllDerpLocked("zero-private-key")
c.stopPeriodicReSTUNTimerLocked()
c.onEndpointRefreshed = nil
} else {
c.logf("magicsock: SetPrivateKey called (changed)")
c.closeAllDerpLocked("new-private-key")
@@ -2301,14 +2185,9 @@ func (c *Conn) foreachActiveDerpSortedLocked(fn func(regionID int, ad activeDerp
func (c *Conn) cleanStaleDerp() {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed {
return
}
c.derpCleanupTimerArmed = false
tooOld := time.Now().Add(-derpInactiveCleanupTime)
const inactivityTime = 60 * time.Second
tooOld := time.Now().Add(-inactivityTime)
dirty := false
someNonHomeOpen := false
for i, ad := range c.activeDerp {
if i == c.myDerp {
continue
@@ -2316,31 +2195,11 @@ func (c *Conn) cleanStaleDerp() {
if ad.lastWrite.Before(tooOld) {
c.closeDerpLocked(i, "idle")
dirty = true
} else {
someNonHomeOpen = true
}
}
if dirty {
c.logActiveDerpLocked()
}
if someNonHomeOpen {
c.scheduleCleanStaleDerpLocked()
}
}
func (c *Conn) scheduleCleanStaleDerpLocked() {
if c.derpCleanupTimerArmed {
// Already going to fire soon. Let the existing one
// fire lest it get infinitely delayed by repeated
// calls to scheduleCleanStaleDerpLocked.
return
}
c.derpCleanupTimerArmed = true
if c.derpCleanupTimer != nil {
c.derpCleanupTimer.Reset(derpCleanStaleInterval)
} else {
c.derpCleanupTimer = time.AfterFunc(derpCleanStaleInterval, c.cleanStaleDerp)
}
}
// DERPs reports the number of active DERP connections.
@@ -2363,10 +2222,6 @@ func (c *Conn) Close() error {
if c.closed {
return nil
}
if c.derpCleanupTimerArmed {
c.derpCleanupTimer.Stop()
}
c.stopPeriodicReSTUNTimerLocked()
for _, ep := range c.endpointOfDisco {
ep.stopAndReset()
@@ -2390,6 +2245,13 @@ func (c *Conn) Close() error {
return err
}
// isClosed reports whether c is closed.
func (c *Conn) isClosed() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.closed
}
func (c *Conn) goroutinesRunningLocked() bool {
if c.endpointsUpdateActive {
return true
@@ -2415,36 +2277,86 @@ func (c *Conn) goroutinesRunningLocked() bool {
func maxIdleBeforeSTUNShutdown() time.Duration {
if debugReSTUNStopOnIdle {
return 45 * time.Second
return time.Minute
}
return sessionActiveTimeout
return 5 * time.Minute
}
func (c *Conn) shouldDoPeriodicReSTUNLocked() bool {
func (c *Conn) shouldDoPeriodicReSTUN() bool {
if c.networkDown() {
return false
}
if len(c.peerSet) == 0 || c.privateKey.IsZero() {
// If no peers, not worth doing.
// Also don't if there's no key (not running).
c.mu.Lock()
defer c.mu.Unlock()
if len(c.peerSet) == 0 {
// No peers, so not worth doing.
return false
}
// If it turns out this optimization was a mistake, we can
// override it from the control server without waiting for a
// new software rollout:
if c.netMap != nil && c.netMap.Debug != nil && c.netMap.Debug.ForceBackgroundSTUN && !debugReSTUNStopOnIdle {
return true
}
if f := c.idleFunc; f != nil {
idleFor := f()
if debugReSTUNStopOnIdle {
c.logf("magicsock: periodicReSTUN: idle for %v", idleFor.Round(time.Second))
}
if idleFor > maxIdleBeforeSTUNShutdown() {
if c.netMap != nil && c.netMap.Debug != nil && c.netMap.Debug.ForceBackgroundSTUN {
// Overridden by control.
return true
if debugReSTUNStopOnIdle || version.IsMobile() { // TODO: make this unconditional later
return false
}
return false
}
}
return true
}
func (c *Conn) periodicReSTUN() {
prand := rand.New(rand.NewSource(time.Now().UnixNano()))
dur := func() time.Duration {
// Just under 30s, a common UDP NAT timeout (Linux at least)
return time.Duration(20+prand.Intn(7)) * time.Second
}
timer := time.NewTimer(dur())
defer timer.Stop()
var lastIdleState opt.Bool
for {
select {
case <-c.donec:
return
case <-timer.C:
doReSTUN := c.shouldDoPeriodicReSTUN()
if !lastIdleState.EqualBool(doReSTUN) {
if doReSTUN {
c.logf("[v1] magicsock: periodicReSTUN enabled")
} else {
c.logf("[v1] magicsock: periodicReSTUN disabled due to inactivity")
}
lastIdleState.Set(doReSTUN)
}
if doReSTUN {
c.ReSTUN("periodic")
}
timer.Reset(dur())
}
}
}
func (c *Conn) periodicDerpCleanup() {
ticker := time.NewTicker(15 * time.Second) // arbitrary
defer ticker.Stop()
for {
select {
case <-c.donec:
return
case <-ticker.C:
c.cleanStaleDerp()
}
}
}
// ReSTUN triggers an address discovery.
// The provided why string is for debug logging only.
func (c *Conn) ReSTUN(why string) {
@@ -2869,8 +2781,7 @@ func udpAddrDebugString(ua net.UDPAddr) string {
// advertise a DiscoKey and participate in active discovery.
type discoEndpoint struct {
// atomically accessed; declared first for alignment reasons
lastRecvUnixAtomic int64
numStopAndResetAtomic int64
lastRecvUnixAtomic int64
// These fields are initialized once and never modified.
c *Conn
@@ -2898,7 +2809,6 @@ type discoEndpoint struct {
trustBestAddrUntil time.Time // time when bestAddr expires
sentPing map[stun.TxID]sentPing
endpointState map[netaddr.IPPort]*endpointState
isCallMeMaybeEP map[netaddr.IPPort]bool
pendingCLIPings []pendingCLIPing // any outstanding "tailscale ping" commands running
}
@@ -2911,8 +2821,6 @@ type pendingCLIPing struct {
const (
// sessionActiveTimeout is how long since the last activity we
// try to keep an established discoEndpoint peering alive.
// It's also the idle time at which we stop doing STUN queries to
// keep NAT mappings alive.
sessionActiveTimeout = 2 * time.Minute
// upgradeInterval is how often we try to upgrade to a better path
@@ -2940,19 +2848,6 @@ const (
// goodEnoughLatency is the latency at or under which we don't
// try to upgrade to a better path.
goodEnoughLatency = 5 * time.Millisecond
// derpInactiveCleanupTime is how long a non-home DERP connection
// needs to be idle (last written to) before we close it.
derpInactiveCleanupTime = 60 * time.Second
// derpCleanStaleInterval is how often cleanStaleDerp runs when there
// are potentially-stale DERP connections to close.
derpCleanStaleInterval = 15 * time.Second
// endpointsFreshEnoughDuration is how long we consider a
// STUN-derived endpoint valid for. UDP NAT mappings typically
// expire at 30 seconds, so this is a few seconds shy of that.
endpointsFreshEnoughDuration = 27 * time.Second
)
// endpointState is some state and history for a specific endpoint of
@@ -2970,10 +2865,6 @@ type endpointState struct {
// updated and use it to discard old candidates.
lastGotPing time.Time
// callMeMaybeTime, if non-zero, is the time this endpoint
// was advertised last via a call-me-maybe disco message.
callMeMaybeTime time.Time
recentPongs []pongReply // ring buffer up to pongHistoryCount entries
recentPong uint16 // index into recentPongs of most recent; older before, wrapped
@@ -2987,13 +2878,11 @@ const indexSentinelDeleted = -1
// shouldDeleteLocked reports whether we should delete this endpoint.
func (st *endpointState) shouldDeleteLocked() bool {
switch {
case !st.callMeMaybeTime.IsZero():
return false
case st.lastGotPing.IsZero():
// This was an endpoint from the network map. Is it still in the network map?
return st.index == indexSentinelDeleted
default:
// This was an endpoint discovered at runtime.
// Thiw was an endpoint discovered at runtime.
return time.Since(st.lastGotPing) > sessionActiveTimeout
}
}
@@ -3093,6 +2982,10 @@ func (de *discoEndpoint) heartbeat() {
de.heartBeatTimer = nil
if de.c.isClosed() {
return
}
if de.lastSend.IsZero() {
// Shouldn't happen.
return
@@ -3307,12 +3200,13 @@ func (de *discoEndpoint) sendPingsLocked(now time.Time, sendCallMeMaybe bool) {
}
derpAddr := de.derpAddr
if sentAny && sendCallMeMaybe && !derpAddr.IsZero() {
// Have our magicsock.Conn figure out its STUN endpoint (if
// it doesn't know already) and then send a CallMeMaybe
// message to our peer via DERP informing them that we've
// sent so our firewall ports are probably open and now
// would be a good time for them to connect.
go de.c.enqueueCallMeMaybe(derpAddr, de)
// In just a bit of a time (for goroutines above to schedule and run),
// send a message to peer via DERP informing them that we've sent
// so our firewall ports are probably open and now would be a good time
// for them to connect.
time.AfterFunc(5*time.Millisecond, func() {
de.sendDiscoMessage(derpAddr, disco.CallMeMaybe{}, discoLog)
})
}
}
@@ -3494,55 +3388,10 @@ func (st *endpointState) addPongReplyLocked(r pongReply) {
// DERP. The contract for use of this message is that the peer has
// already sent to us via UDP, so their stateful firewall should be
// open. Now we can Ping back and make it through.
func (de *discoEndpoint) handleCallMeMaybe(m *disco.CallMeMaybe) {
func (de *discoEndpoint) handleCallMeMaybe() {
de.mu.Lock()
defer de.mu.Unlock()
now := time.Now()
for ep := range de.isCallMeMaybeEP {
de.isCallMeMaybeEP[ep] = false // mark for deletion
}
if de.isCallMeMaybeEP == nil {
de.isCallMeMaybeEP = map[netaddr.IPPort]bool{}
}
var newEPs []netaddr.IPPort
for _, ep := range m.MyNumber {
if ep.IP.Is6() && ep.IP.IsLinkLocalUnicast() {
// We send these out, but ignore them for now.
// TODO: teach the ping code to ping on all interfaces
// for these.
continue
}
de.isCallMeMaybeEP[ep] = true
if es, ok := de.endpointState[ep]; ok {
es.callMeMaybeTime = now
} else {
de.endpointState[ep] = &endpointState{callMeMaybeTime: now}
newEPs = append(newEPs, ep)
}
}
if len(newEPs) > 0 {
de.c.logf("magicsock: disco: call-me-maybe from %v %v added new endpoints: %v",
de.publicKey.ShortString(), de.discoShort,
logger.ArgWriter(func(w *bufio.Writer) {
for i, ep := range newEPs {
if i > 0 {
w.WriteString(", ")
}
w.WriteString(ep.String())
}
}))
}
// Delete any prior CalllMeMaybe endpoints that weren't included
// in this message.
for ep, want := range de.isCallMeMaybeEP {
if !want {
delete(de.isCallMeMaybeEP, ep)
de.deleteEndpointLocked(ep)
}
}
// Zero out all the lastPing times to force sendPingsLocked to send new ones,
// even if it's been less than 5 seconds ago.
for _, st := range de.endpointState {
@@ -3571,7 +3420,6 @@ func (de *discoEndpoint) populatePeerStatus(ps *ipnstate.PeerStatus) {
// It's called when a discovery endpoint is no longer present in the NetworkMap,
// or when magicsock is transition from running to stopped state (via SetPrivateKey(zero))
func (de *discoEndpoint) stopAndReset() {
atomic.AddInt64(&de.numStopAndResetAtomic, 1)
de.mu.Lock()
defer de.mu.Unlock()
@@ -3599,10 +3447,6 @@ func (de *discoEndpoint) stopAndReset() {
de.pendingCLIPings = nil
}
func (de *discoEndpoint) numStopAndReset() int64 {
return atomic.LoadInt64(&de.numStopAndResetAtomic)
}
// derpStr replaces DERP IPs in s with "derp-".
func derpStr(s string) string { return strings.ReplaceAll(s, "127.3.3.40:", "derp-") }
@@ -3624,11 +3468,3 @@ func (c *Conn) WhoIs(ip netaddr.IP) (n *tailcfg.Node, u tailcfg.UserProfile, ok
}
return nil, u, false
}
// ippEndpointCache is a mutex-free single-element cache, mapping from
// a single netaddr.IPPort to a single endpoint.
type ippEndpointCache struct {
ipp netaddr.IPPort
gen int64
de *discoEndpoint
}

View File

@@ -46,7 +46,6 @@ import (
"tailscale.com/types/wgkey"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/tstun"
"tailscale.com/wgengine/wglog"
)
func init() {
@@ -128,7 +127,6 @@ type magicStack struct {
tun *tuntest.ChannelTUN // TUN device to send/receive packets
tsTun *tstun.TUN // wrapped tun that implements filtering and wgengine hooks
dev *device.Device // the wireguard-go Device that connects the previous things
wgLogger *wglog.Logger // wireguard-go log wrapper
}
// newMagicStack builds and initializes an idle magicsock and
@@ -165,9 +163,8 @@ func newMagicStack(t testing.TB, logf logger.Logf, l nettype.PacketListener, der
tsTun := tstun.WrapTUN(logf, tun.TUN())
tsTun.SetFilter(filter.NewAllowAllForTest(logf))
wgLogger := wglog.NewLogger(logf)
dev := device.NewDevice(tsTun, &device.DeviceOptions{
Logger: wgLogger.DeviceLogger,
Logger: wireguardGoLogger(logf),
CreateEndpoint: conn.CreateEndpoint,
CreateBind: conn.CreateBind,
SkipBindUpdate: true,
@@ -190,15 +187,9 @@ func newMagicStack(t testing.TB, logf logger.Logf, l nettype.PacketListener, der
tun: tun,
tsTun: tsTun,
dev: dev,
wgLogger: wgLogger,
}
}
func (s *magicStack) Reconfig(cfg *wgcfg.Config) error {
s.wgLogger.SetPeers(cfg.Peers)
return s.dev.Reconfig(cfg)
}
func (s *magicStack) String() string {
pub := s.Public()
return pub.ShortString()
@@ -299,7 +290,7 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) {
// blow up. Shouldn't happen anyway.
panic(fmt.Sprintf("failed to construct wgcfg from netmap: %v", err))
}
if err := m.Reconfig(wg); err != nil {
if err := m.dev.Reconfig(wg); err != nil {
panic(fmt.Sprintf("device reconfig failed: %v", err))
}
}
@@ -535,7 +526,7 @@ func TestDeviceStartStop(t *testing.T) {
tun := tuntest.NewChannelTUN()
dev := device.NewDevice(tun.TUN(), &device.DeviceOptions{
Logger: wglog.NewLogger(t.Logf).DeviceLogger,
Logger: wireguardGoLogger(t.Logf),
CreateEndpoint: conn.CreateEndpoint,
CreateBind: conn.CreateBind,
SkipBindUpdate: true,
@@ -905,10 +896,7 @@ func testTwoDevicePing(t *testing.T, d *devices) {
// This gets reassigned inside every test, so that the connections
// all log using the "current" t.Logf function. Sigh.
nestedLogf, setT := makeNestable(t)
logf, closeLogf := logger.LogfCloser(nestedLogf)
defer closeLogf()
logf, setT := makeNestable(t)
derpMap, cleanup := runDERPAndStun(t, logf, d.stun, d.stunIP)
defer cleanup()
@@ -924,10 +912,10 @@ func testTwoDevicePing(t *testing.T, d *devices) {
}
cfgs := makeConfigs(t, addrs)
if err := m1.Reconfig(&cfgs[0]); err != nil {
if err := m1.dev.Reconfig(&cfgs[0]); err != nil {
t.Fatal(err)
}
if err := m2.Reconfig(&cfgs[1]); err != nil {
if err := m2.dev.Reconfig(&cfgs[1]); err != nil {
t.Fatal(err)
}
@@ -992,7 +980,7 @@ func testTwoDevicePing(t *testing.T, d *devices) {
t.Run("no-op dev1 reconfig", func(t *testing.T) {
setT(t)
defer setT(outerT)
if err := m1.Reconfig(&cfgs[0]); err != nil {
if err := m1.dev.Reconfig(&cfgs[0]); err != nil {
t.Fatal(err)
}
ping1(t)
@@ -1069,10 +1057,10 @@ func testTwoDevicePing(t *testing.T, d *devices) {
ep1 := cfgs[1].Peers[0].Endpoints
ep1 = derpEp + "," + ep1
cfgs[1].Peers[0].Endpoints = ep1
if err := m1.Reconfig(&cfgs[0]); err != nil {
if err := m1.dev.Reconfig(&cfgs[0]); err != nil {
t.Fatal(err)
}
if err := m2.Reconfig(&cfgs[1]); err != nil {
if err := m2.dev.Reconfig(&cfgs[1]); err != nil {
t.Fatal(err)
}
@@ -1085,10 +1073,10 @@ func testTwoDevicePing(t *testing.T, d *devices) {
// Disable real route.
cfgs[0].Peers[0].Endpoints = derpEp
cfgs[1].Peers[0].Endpoints = derpEp
if err := m1.Reconfig(&cfgs[0]); err != nil {
if err := m1.dev.Reconfig(&cfgs[0]); err != nil {
t.Fatal(err)
}
if err := m2.Reconfig(&cfgs[1]); err != nil {
if err := m2.dev.Reconfig(&cfgs[1]); err != nil {
t.Fatal(err)
}
time.Sleep(250 * time.Millisecond) // TODO remove
@@ -1114,10 +1102,10 @@ func testTwoDevicePing(t *testing.T, d *devices) {
if ep2 := cfgs[1].Peers[0].Endpoints; len(ep2) != 1 {
t.Errorf("unexpected peer endpoints in dev2: %v", ep2)
}
if err := m2.Reconfig(&cfgs[1]); err != nil {
if err := m2.dev.Reconfig(&cfgs[1]); err != nil {
t.Fatal(err)
}
if err := m1.Reconfig(&cfgs[0]); err != nil {
if err := m1.dev.Reconfig(&cfgs[0]); err != nil {
t.Fatal(err)
}
// Dear future human debugging a test failure here: this test is
@@ -1496,3 +1484,19 @@ func BenchmarkReceiveFrom_Native(b *testing.B) {
}
}
}
func wireguardGoLogger(logf logger.Logf) *device.Logger {
// wireguard-go logs as it starts and stops routines.
// Silence those; there are a lot of them, and they're just noise.
allowLogf := func(s string) bool {
return !strings.Contains(s, "Routine:")
}
filtered := logger.Filtered(logf, allowLogf)
stdLogger := logger.StdLogger(filtered)
return &device.Logger{
Debug: stdLogger,
Info: stdLogger,
Error: stdLogger,
}
}

View File

@@ -237,7 +237,7 @@ func interfaceFromLUID(luid winipcfg.LUID, flags winipcfg.GAAFlags) (*winipcfg.I
return nil, fmt.Errorf("interfaceFromLUID: interface with LUID %v not found", luid)
}
func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
func configureInterface(cfg *Config, tun *tun.NativeTun) error {
const mtu = 0
luid := winipcfg.LUID(tun.LUID())
iface, err := interfaceFromLUID(luid,
@@ -251,15 +251,6 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
return err
}
// Send non-nil return errors to retErrc, to interupt our background
// setPrivateNetwork goroutine.
retErrc := make(chan error, 1)
defer func() {
if retErr != nil {
retErrc <- retErr
}
}()
go func() {
// It takes a weirdly long time for Windows to notice the
// new interface has come up. Poll periodically until it
@@ -271,18 +262,11 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
log.Printf("setPrivateNetwork(try=%d): %v", i, err)
} else {
if found {
if i > 0 {
log.Printf("setPrivateNetwork(try=%d): success", i)
}
return
}
log.Printf("setPrivateNetwork(try=%d): not found", i)
}
select {
case <-time.After(time.Second):
case <-retErrc:
return
}
time.Sleep(1 * time.Second)
}
log.Printf("setPrivateNetwork: adapter LUID %v not found after %d tries, giving up", luid, tries)
}()

View File

@@ -366,9 +366,7 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error {
// address is already assigned to the interface, or if the addition
// fails.
func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
if !r.v6Available && addr.IP.Is6() {
return nil
}
if err := r.cmd.run("ip", "addr", "add", addr.String(), "dev", r.tunname); err != nil {
return fmt.Errorf("adding address %q to tunnel interface: %w", addr, err)
}
@@ -382,9 +380,6 @@ func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
// the address is not assigned to the interface, or if the removal
// fails.
func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error {
if !r.v6Available && addr.IP.Is6() {
return nil
}
if err := r.delLoopbackRule(addr.IP); err != nil {
return err
}
@@ -442,9 +437,6 @@ func (r *linuxRouter) delLoopbackRule(addr netaddr.IP) error {
// interface. Fails if the route already exists, or if adding the
// route fails.
func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error {
if !r.v6Available && cidr.IP.Is6() {
return nil
}
args := []string{
"ip", "route", "add",
normalizeCIDR(cidr),
@@ -460,9 +452,6 @@ func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error {
// interface. Fails if the route doesn't exist, or if removing the
// route fails.
func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error {
if !r.v6Available && cidr.IP.Is6() {
return nil
}
args := []string{
"ip", "route", "del",
normalizeCIDR(cidr),

View File

@@ -346,8 +346,8 @@ func (t *TUN) filterIn(buf []byte) filter.Response {
}
if t.PostFilterIn != nil {
if res := t.PostFilterIn(p, t); res.IsDrop() {
return res
if t.PostFilterIn(p, t) == filter.Drop {
return filter.Drop
}
}

View File

@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
@@ -46,7 +47,6 @@ import (
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/tsdns"
"tailscale.com/wgengine/tstun"
"tailscale.com/wgengine/wglog"
)
// minimalMTU is the MTU we set on tailscale's TUN
@@ -84,7 +84,6 @@ const (
type userspaceEngine struct {
logf logger.Logf
wgLogger *wglog.Logger //a wireguard-go logging wrapper
reqCh chan struct{}
waitCh chan struct{} // chan is closed when first Close call completes; contrast with closing bool
timeNow func() time.Time
@@ -111,7 +110,6 @@ type userspaceEngine struct {
trimmedDisco map[tailcfg.DiscoKey]bool // set of disco keys of peers currently excluded from wireguard config
sentActivityAt map[netaddr.IP]*int64 // value is atomic int64 of unixtime
destIPActivityFuncs map[netaddr.IP]func()
statusBufioReader *bufio.Reader // reusable for UAPI
mu sync.Mutex // guards following; see lock order comment below
closing bool // Close was called (even if we're still closing)
@@ -194,7 +192,6 @@ func NewUserspaceEngine(logf logger.Logf, tunname string, listenPort uint16) (En
e, err := NewUserspaceEngineAdvanced(conf)
if err != nil {
tun.Close()
return nil, err
}
return e, err
@@ -282,9 +279,23 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
e.tundev.PostFilterOut = e.trackOpenPostFilterOut
}
e.wgLogger = wglog.NewLogger(logf)
// wireguard-go logs as it starts and stops routines.
// Silence those; there are a lot of them, and they're just noise.
allowLogf := func(s string) bool {
return !strings.Contains(s, "Routine:")
}
filtered := logger.Filtered(logf, allowLogf)
// flags==0 because logf is already nested in another logger.
// The outer one can display the preferred log prefixes, etc.
dlog := logger.StdLogger(filtered)
logger := device.Logger{
Debug: dlog,
Info: dlog,
Error: dlog,
}
opts := &device.DeviceOptions{
Logger: e.wgLogger.DeviceLogger,
Logger: &logger,
HandshakeDone: func(peerKey device.NoisePublicKey, peer *device.Peer, deviceAllowedIPs *device.AllowedIPs) {
// Send an unsolicited status event every time a
// handshake completes. This makes sure our UI can
@@ -763,7 +774,6 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked(discoChanged map[key.Publ
}
full := e.lastCfgFull
e.wgLogger.SetPeers(full.Peers)
// Compute a minimal config to pass to wireguard-go
// based on the full config. Prune off all the peers
@@ -1036,8 +1046,8 @@ func (e *userspaceEngine) getStatusCallback() StatusCallback {
return e.statusCallback
}
var singleNewline = []byte{'\n'}
// TODO: this function returns an error but it's always nil, and when
// there's actually a problem it just calls log.Fatal. Why?
func (e *userspaceEngine) getStatus() (*Status, error) {
// Grab derpConns before acquiring wgLock to not violate lock ordering;
// the DERPs method acquires magicsock.Conn.mu.
@@ -1062,12 +1072,15 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
return nil, nil
}
pr, pw := io.Pipe()
defer pr.Close() // to unblock writes on error path returns
// lineLen is the max UAPI line we expect. The longest I see is
// len("preshared_key=")+64 hex+"\n" == 79. Add some slop.
const lineLen = 100
pr, pw := io.Pipe()
errc := make(chan error, 1)
go func() {
defer pw.Close()
bw := bufio.NewWriterSize(pw, lineLen)
// TODO(apenwarr): get rid of silly uapi stuff for in-process comms
// FIXME: get notified of status changes instead of polling.
filter := device.IPCGetFilter{
@@ -1075,34 +1088,23 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
// unused below; request that they not be sent instead.
FilterAllowedIPs: true,
}
err := e.wgdev.IpcGetOperationFiltered(pw, filter)
if err != nil {
err = fmt.Errorf("IpcGetOperation: %w", err)
if err := e.wgdev.IpcGetOperationFiltered(bw, filter); err != nil {
errc <- fmt.Errorf("IpcGetOperation: %w", err)
return
}
errc <- err
errc <- bw.Flush()
}()
pp := make(map[wgkey.Key]*PeerStatus)
p := &PeerStatus{}
var hst1, hst2, n int64
var err error
br := e.statusBufioReader
if br != nil {
br.Reset(pr)
} else {
br = bufio.NewReaderSize(pr, 1<<10)
e.statusBufioReader = br
}
for {
line, err := br.ReadSlice('\n')
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("reading from UAPI pipe: %w", err)
}
line = bytes.TrimSuffix(line, singleNewline)
bs := bufio.NewScanner(pr)
bs.Buffer(make([]byte, lineLen), lineLen)
for bs.Scan() {
line := bs.Bytes()
k := line
var v mem.RO
if i := bytes.IndexByte(line, '='); i != -1 {
@@ -1113,7 +1115,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
case "public_key":
pk, err := key.NewPublicFromHexMem(v)
if err != nil {
return nil, fmt.Errorf("IpcGetOperation: invalid key in line %q", line)
log.Fatalf("IpcGetOperation: invalid key %#v", v)
}
p = &PeerStatus{}
pp[wgkey.Key(pk)] = p
@@ -1124,31 +1126,34 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
n, err = mem.ParseInt(v, 10, 64)
p.RxBytes = ByteCount(n)
if err != nil {
return nil, fmt.Errorf("IpcGetOperation: rx_bytes invalid: %#v", line)
log.Fatalf("IpcGetOperation: rx_bytes invalid: %#v", line)
}
case "tx_bytes":
n, err = mem.ParseInt(v, 10, 64)
p.TxBytes = ByteCount(n)
if err != nil {
return nil, fmt.Errorf("IpcGetOperation: tx_bytes invalid: %#v", line)
log.Fatalf("IpcGetOperation: tx_bytes invalid: %#v", line)
}
case "last_handshake_time_sec":
hst1, err = mem.ParseInt(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("IpcGetOperation: hst1 invalid: %#v", line)
log.Fatalf("IpcGetOperation: hst1 invalid: %#v", line)
}
case "last_handshake_time_nsec":
hst2, err = mem.ParseInt(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("IpcGetOperation: hst2 invalid: %#v", line)
log.Fatalf("IpcGetOperation: hst2 invalid: %#v", line)
}
if hst1 != 0 || hst2 != 0 {
p.LastHandshake = time.Unix(hst1, hst2)
} // else leave at time.IsZero()
}
}
if err := bs.Err(); err != nil {
log.Fatalf("reading IpcGetOperation output: %v", err)
}
if err := <-errc; err != nil {
return nil, fmt.Errorf("IpcGetOperation: %v", err)
log.Fatalf("IpcGetOperation: %v", err)
}
e.mu.Lock()

View File

@@ -1,89 +0,0 @@
// 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 wglog contains logging helpers for wireguard-go.
package wglog
import (
"encoding/base64"
"fmt"
"strings"
"sync/atomic"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/types/logger"
)
// A Logger is a wireguard-go log wrapper that cleans up and rewrites log lines.
// It can be modified at run time to adjust to new wireguard-go configurations.
type Logger struct {
DeviceLogger *device.Logger
replacer atomic.Value // of *strings.Replacer
}
// NewLogger creates a new logger for use with wireguard-go.
// This logger silences repetitive/unhelpful noisy log lines
// and rewrites peer keys from wireguard-go into Tailscale format.
func NewLogger(logf logger.Logf) *Logger {
ret := new(Logger)
wrapper := func(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
if strings.Contains(msg, "Routine:") {
// wireguard-go logs as it starts and stops routines.
// Drop those; there are a lot of them, and they're just noise.
return
}
r := ret.replacer.Load()
if r == nil {
// No replacements specified; log as originally planned.
logf(format, args...)
return
}
// Do the replacements.
new := r.(*strings.Replacer).Replace(msg)
if new == msg {
// No replacements. Log as originally planned.
logf(format, args...)
return
}
// We made some replacements. Log the new version.
// This changes the format string,
// which is somewhat unfortunate as it impacts rate limiting,
// but there's not much we can do about that.
logf("%s", new)
}
std := logger.StdLogger(wrapper)
ret.DeviceLogger = &device.Logger{
Debug: std,
Info: std,
Error: std,
}
return ret
}
// SetPeers adjusts x to rewrite the peer public keys found in peers.
// SetPeers is safe for concurrent use.
func (x *Logger) SetPeers(peers []wgcfg.Peer) {
// Construct a new peer public key log rewriter.
var replace []string
for _, peer := range peers {
old := "peer(" + wireguardGoString(peer.PublicKey) + ")"
new := peer.PublicKey.ShortString()
replace = append(replace, old, new)
}
r := strings.NewReplacer(replace...)
x.replacer.Store(r)
}
// wireguardGoString prints p in the same format used by wireguard-go.
func wireguardGoString(k wgcfg.Key) string {
base64Key := base64.StdEncoding.EncodeToString(k[:])
abbreviatedKey := "invalid"
if len(base64Key) == 44 {
abbreviatedKey = base64Key[0:4] + "…" + base64Key[39:43]
}
return abbreviatedKey
}

View File

@@ -1,59 +0,0 @@
// 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 wglog_test
import (
"fmt"
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/wgengine/wglog"
)
func TestLogger(t *testing.T) {
tests := []struct {
in string
want string
omit bool
}{
{"hi", "hi", false},
{"Routine: starting", "", true},
{"peer(IMTB…r7lM) says it misses you", "[IMTBr] says it misses you", false},
}
c := make(chan string, 1)
logf := func(format string, args ...interface{}) {
s := fmt.Sprintf(format, args...)
select {
case c <- s:
default:
t.Errorf("wrote %q, but shouldn't have", s)
}
}
x := wglog.NewLogger(logf)
key, err := wgcfg.ParseHexKey("20c4c1ae54e1fd37cab6e9a532ca20646aff496796cc41d4519560e5e82bee53")
if err != nil {
t.Fatal(err)
}
x.SetPeers([]wgcfg.Peer{{PublicKey: key}})
for _, tt := range tests {
if tt.omit {
// Write a message ourselves into the channel.
// Then if logf also attempts to write into the channel, it'll fail.
c <- ""
}
x.DeviceLogger.Info.Println(tt.in)
got := <-c
if tt.omit {
continue
}
tt.want += "\n"
if got != tt.want {
t.Errorf("Println(%q) = %q want %q", tt.in, got, tt.want)
}
}
}