Compare commits
29 Commits
awly/cli-j
...
v1.2.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f4e6d959a | ||
|
|
449cbf5cfb | ||
|
|
c242540a97 | ||
|
|
e29f92f653 | ||
|
|
82dbf148a3 | ||
|
|
df38ea4d65 | ||
|
|
28f3136611 | ||
|
|
1be01ddc6e | ||
|
|
bddc882f7d | ||
|
|
33505097c4 | ||
|
|
d8a531108d | ||
|
|
c73c3001a4 | ||
|
|
c572d622d7 | ||
|
|
fead79a02f | ||
|
|
266f654861 | ||
|
|
d91a9131b1 | ||
|
|
dced1d6a37 | ||
|
|
d5bc375b0e | ||
|
|
c1bae7ad64 | ||
|
|
76c2982d88 | ||
|
|
3d64eef37b | ||
|
|
4f292740b0 | ||
|
|
e1327154bb | ||
|
|
a702921620 | ||
|
|
b9b7fbdd21 | ||
|
|
9446e5c170 | ||
|
|
75cd82791e | ||
|
|
4d5d5f89a3 | ||
|
|
bb058703ee |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# Binaries for programs and plugins
|
||||
*~
|
||||
*.tmp
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.1.0 f81233524fddeec450940af8dc1a0dd8841bf28c
|
||||
1.2.6
|
||||
|
||||
@@ -9,12 +9,8 @@
|
||||
# this script, or executing equivalent commands in your
|
||||
# distro-specific build system.
|
||||
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
describe=$(./version/describe.sh)
|
||||
commit=$(git rev-parse --verify --quiet HEAD)
|
||||
eval $(./version/version.sh)
|
||||
|
||||
long=$(./version/mkversion.sh long "$describe" "")
|
||||
short=$(./version/mkversion.sh short "$describe" "")
|
||||
|
||||
exec go build -tags xversion -ldflags "-X tailscale.com/version.Long=${long} -X tailscale.com/version.Short=${short} -X tailscale.com/version.GitCommit=${commit}" "$@"
|
||||
exec go build -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}" "$@"
|
||||
|
||||
@@ -182,18 +182,11 @@ func runUp(ctx context.Context, args []string) error {
|
||||
var tags []string
|
||||
if upArgs.advertiseTags != "" {
|
||||
tags = strings.Split(upArgs.advertiseTags, ",")
|
||||
for i, tag := range tags {
|
||||
if strings.HasPrefix(tag, "tag:") {
|
||||
// Accept fully-qualified tags (starting with
|
||||
// "tag:"), as we do in the ACL file.
|
||||
err := tailcfg.CheckTag(tag)
|
||||
if err != nil {
|
||||
fatalf("tag: %q: %v", tag, err)
|
||||
}
|
||||
} else if err := tailcfg.CheckTagSuffix(tag); err != nil {
|
||||
fatalf("tag: %q: %v", tag, err)
|
||||
for _, tag := range tags {
|
||||
err := tailcfg.CheckTag(tag)
|
||||
if err != nil {
|
||||
fatalf("tag: %q: %s", tag, err)
|
||||
}
|
||||
tags[i] = "tag:" + tag
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ After=network-pre.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/etc/default/tailscaled
|
||||
ExecStartPre=/usr/sbin/tailscaled --cleanup
|
||||
ExecStart=/usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/run/tailscale/tailscaled.sock --port $PORT $FLAGS
|
||||
ExecStopPost=/usr/sbin/tailscaled --cleanup
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
@@ -91,9 +92,14 @@ func (p *Persist) Pretty() string {
|
||||
if !p.PrivateNodeKey.IsZero() {
|
||||
nk = p.PrivateNodeKey.Public()
|
||||
}
|
||||
ss := func(k wgcfg.Key) string {
|
||||
if k.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return k.ShortString()
|
||||
}
|
||||
return fmt.Sprintf("Persist{lm=%v, o=%v, n=%v u=%#v}",
|
||||
mk.ShortString(), ok.ShortString(), nk.ShortString(),
|
||||
p.LoginName)
|
||||
ss(mk), ss(ok), ss(nk), p.LoginName)
|
||||
}
|
||||
|
||||
// Direct is the client that connects to a tailcontrol server for a node.
|
||||
@@ -539,6 +545,10 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
|
||||
Hostinfo: hostinfo,
|
||||
DebugFlags: c.debugFlags,
|
||||
}
|
||||
if hostinfo != nil && ipForwardingBroken(hostinfo.RoutableIPs) {
|
||||
old := request.DebugFlags
|
||||
request.DebugFlags = append(old[:len(old):len(old)], "warn-ip-forwarding-off")
|
||||
}
|
||||
if c.newDecompressor != nil {
|
||||
request.Compress = "zstd"
|
||||
}
|
||||
@@ -781,6 +791,8 @@ func decode(res *http.Response, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg
|
||||
|
||||
var debugMap, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_MAP"))
|
||||
|
||||
var jsonEscapedZero = []byte(`\u0000`)
|
||||
|
||||
func (c *Direct) decodeMsg(msg []byte, v interface{}) error {
|
||||
c.mu.Lock()
|
||||
serverKey := c.serverKey
|
||||
@@ -809,6 +821,10 @@ func (c *Direct) decodeMsg(msg []byte, v interface{}) error {
|
||||
json.Indent(&buf, b, "", " ")
|
||||
log.Printf("MapResponse: %s", buf.Bytes())
|
||||
}
|
||||
|
||||
if bytes.Contains(b, jsonEscapedZero) {
|
||||
log.Printf("[unexpected] zero byte in controlclient.Direct.decodeMsg into %T: %q", v, b)
|
||||
}
|
||||
if err := json.Unmarshal(b, v); err != nil {
|
||||
return fmt.Errorf("response: %v", err)
|
||||
}
|
||||
@@ -821,6 +837,9 @@ func decodeMsg(msg []byte, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.Priv
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Contains(decrypted, jsonEscapedZero) {
|
||||
log.Printf("[unexpected] zero byte in controlclient decodeMsg into %T: %q", v, decrypted)
|
||||
}
|
||||
if err := json.Unmarshal(decrypted, v); err != nil {
|
||||
return fmt.Errorf("response: %v", err)
|
||||
}
|
||||
@@ -1051,3 +1070,34 @@ func TrimWGConfig() opt.Bool {
|
||||
v, _ := controlTrimWGConfig.Load().(opt.Bool)
|
||||
return v
|
||||
}
|
||||
|
||||
// ipForwardingBroken reports whether the system's IP forwarding is disabled
|
||||
// and will definitely not work for the routes provided.
|
||||
//
|
||||
// It should not return false positives.
|
||||
func ipForwardingBroken(routes []wgcfg.CIDR) bool {
|
||||
if len(routes) == 0 {
|
||||
// Nothing to route, so no need to warn.
|
||||
return false
|
||||
}
|
||||
if runtime.GOOS != "linux" {
|
||||
// We only do subnet routing on Linux for now.
|
||||
// It might work on darwin/macOS when building from source, so
|
||||
// don't return true for other OSes. We can OS-based warnings
|
||||
// already in the admin panel.
|
||||
return false
|
||||
}
|
||||
out, err := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward")
|
||||
if err != nil {
|
||||
// Try another way.
|
||||
out, err = exec.Command("sysctl", "-n", "net.ipv4.ip_forward").Output()
|
||||
}
|
||||
if err != nil {
|
||||
// Oh well, we tried. This is just for debugging.
|
||||
// We don't want false positives.
|
||||
// TODO: maybe we want a different warning for inability to check?
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(string(out)) == "0"
|
||||
// TODO: also check IPv6 if 'routes' contains any IPv6 routes
|
||||
}
|
||||
|
||||
46
ipn/local.go
46
ipn/local.go
@@ -419,7 +419,9 @@ func (b *LocalBackend) Start(opts Options) error {
|
||||
b.serverURL = b.prefs.ControlURL
|
||||
hostinfo.RoutableIPs = append(hostinfo.RoutableIPs, b.prefs.AdvertiseRoutes...)
|
||||
hostinfo.RequestTags = append(hostinfo.RequestTags, b.prefs.AdvertiseTags...)
|
||||
b.logf("Start: serverMode=%v; stateKey=%q; tags=%q; routes=%v; url=%v", b.inServerMode, b.stateKey, b.prefs.AdvertiseTags, b.prefs.AdvertiseRoutes, b.prefs.ControlURL)
|
||||
if b.inServerMode || runtime.GOOS == "windows" {
|
||||
b.logf("Start: serverMode=%v", b.inServerMode)
|
||||
}
|
||||
applyPrefsToHostinfo(hostinfo, b.prefs)
|
||||
|
||||
b.notify = opts.Notify
|
||||
@@ -704,6 +706,7 @@ func (b *LocalBackend) popBrowserAuthNow() {
|
||||
// initMachineKeyLocked is called to initialize b.machinePrivKey.
|
||||
//
|
||||
// b.prefs must already be initialized.
|
||||
// b.stateKey should be set too, but just for nicer log messages.
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) initMachineKeyLocked() (err error) {
|
||||
if temporarilySetMachineKeyInPersist() {
|
||||
@@ -748,7 +751,11 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
|
||||
// have a legacy machine key, use that. Otherwise generate a
|
||||
// new one.
|
||||
if !legacyMachineKey.IsZero() {
|
||||
b.logf("using frontend-provided legacy machine key")
|
||||
if b.stateKey == "" {
|
||||
b.logf("using frontend-provided legacy machine key")
|
||||
} else {
|
||||
b.logf("using legacy machine key from state key %q", b.stateKey)
|
||||
}
|
||||
b.machinePrivKey = legacyMachineKey
|
||||
} else {
|
||||
b.logf("generating new machine key")
|
||||
@@ -801,23 +808,32 @@ func (b *LocalBackend) writeServerModeStartState(userID string, prefs *Prefs) {
|
||||
// loadStateLocked sets b.prefs and b.stateKey based on a complex
|
||||
// combination of key, prefs, and legacyPath. b.mu must be held when
|
||||
// calling.
|
||||
func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath string) error {
|
||||
func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath string) (err error) {
|
||||
if prefs == nil && key == "" {
|
||||
panic("state key and prefs are both unset")
|
||||
}
|
||||
|
||||
// Optimistically set stateKey (for initMachineKeyLocked's
|
||||
// logging), but revert it if we return an error so a later SetPrefs
|
||||
// call can't pick it up if it's bogus.
|
||||
b.stateKey = key
|
||||
defer func() {
|
||||
if err != nil {
|
||||
b.stateKey = ""
|
||||
}
|
||||
}()
|
||||
|
||||
if key == "" {
|
||||
// Frontend owns the state, we just need to obey it.
|
||||
//
|
||||
// If the frontend (e.g. on Windows) supplied the
|
||||
// optional/legacy machine key then it's used as the
|
||||
// value instead of making up a new one.
|
||||
b.logf("Using frontend prefs")
|
||||
b.logf("using frontend prefs: %s", prefs.Pretty())
|
||||
b.prefs = prefs.Clone()
|
||||
if err := b.initMachineKeyLocked(); err != nil {
|
||||
return fmt.Errorf("initMachineKeyLocked: %w", err)
|
||||
}
|
||||
b.stateKey = ""
|
||||
b.writeServerModeStartState(b.userID, b.prefs)
|
||||
return nil
|
||||
}
|
||||
@@ -825,13 +841,13 @@ func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath st
|
||||
if prefs != nil {
|
||||
// Backend owns the state, but frontend is trying to migrate
|
||||
// state into the backend.
|
||||
b.logf("Importing frontend prefs into backend store")
|
||||
b.logf("importing frontend prefs into backend store; frontend prefs: %s", prefs.Pretty())
|
||||
if err := b.store.WriteState(key, prefs.ToBytes()); err != nil {
|
||||
return fmt.Errorf("store.WriteState: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
b.logf("Using backend prefs")
|
||||
b.logf("using backend prefs")
|
||||
bs, err := b.store.ReadState(key)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrStateNotExist) {
|
||||
@@ -843,16 +859,15 @@ func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath st
|
||||
}
|
||||
b.prefs = NewPrefs()
|
||||
} else {
|
||||
b.logf("Imported state from relaynode for %q", key)
|
||||
b.logf("imported prefs from relaynode for %q: %v", key, b.prefs.Pretty())
|
||||
}
|
||||
} else {
|
||||
b.prefs = NewPrefs()
|
||||
b.logf("Created empty state for %q", key)
|
||||
b.logf("created empty state for %q: %s", key, b.prefs.Pretty())
|
||||
}
|
||||
if err := b.initMachineKeyLocked(); err != nil {
|
||||
return fmt.Errorf("initMachineKeyLocked: %w", err)
|
||||
}
|
||||
b.stateKey = key
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("store.ReadState(%q): %v", key, err)
|
||||
@@ -861,7 +876,7 @@ func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath st
|
||||
if err != nil {
|
||||
return fmt.Errorf("PrefsFromBytes: %v", err)
|
||||
}
|
||||
b.stateKey = key
|
||||
b.logf("backend prefs for %q: %s", key, b.prefs.Pretty())
|
||||
if err := b.initMachineKeyLocked(); err != nil {
|
||||
return fmt.Errorf("initMachineKeyLocked: %w", err)
|
||||
}
|
||||
@@ -1139,6 +1154,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
uc := b.prefs
|
||||
nm := b.netMap
|
||||
hasPAC := b.prevIfState.HasPAC()
|
||||
disableSubnetsIfPAC := nm != nil && nm.Debug != nil && nm.Debug.DisableSubnetsIfPAC.EqualBool(true)
|
||||
b.mu.Unlock()
|
||||
|
||||
if blocked {
|
||||
@@ -1163,13 +1179,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
if uc.AllowSingleHosts {
|
||||
flags |= controlclient.AllowSingleHosts
|
||||
}
|
||||
if hasPAC {
|
||||
// TODO(bradfitz): make this policy configurable per
|
||||
// domain, flesh out all the edge cases where subnet
|
||||
// routes might shadow corp HTTP proxies, DNS servers,
|
||||
// domain controllers, etc. For now we just want
|
||||
// Tailscale to stay enabled while laptops roam
|
||||
// between corp & non-corp networks.
|
||||
if hasPAC && disableSubnetsIfPAC {
|
||||
if flags&controlclient.AllowSubnetRoutes != 0 {
|
||||
b.logf("authReconfig: have PAC; disabling subnet routes")
|
||||
flags &^= controlclient.AllowSubnetRoutes
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package ipn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -19,6 +20,8 @@ import (
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
var jsonEscapedZero = []byte(`\u0000`)
|
||||
|
||||
type NoArgs struct{}
|
||||
|
||||
type StartArgs struct {
|
||||
@@ -85,6 +88,9 @@ func (bs *BackendServer) send(n Notify) {
|
||||
if err != nil {
|
||||
log.Fatalf("Failed json.Marshal(notify): %v\n%#v", err, n)
|
||||
}
|
||||
if bytes.Contains(b, jsonEscapedZero) {
|
||||
log.Printf("[unexpected] zero byte in BackendServer.send notify message: %q", b)
|
||||
}
|
||||
bs.sendNotifyMsg(b)
|
||||
}
|
||||
|
||||
@@ -204,6 +210,9 @@ func (bc *BackendClient) GotNotifyMsg(b []byte) {
|
||||
// not interesting
|
||||
return
|
||||
}
|
||||
if bytes.Contains(b, jsonEscapedZero) {
|
||||
log.Printf("[unexpected] zero byte in BackendClient.GotNotifyMsg message: %q", b)
|
||||
}
|
||||
n := Notify{}
|
||||
if err := json.Unmarshal(b, &n); err != nil {
|
||||
log.Fatalf("BackendClient.Notify: cannot decode message (length=%d)\n%#v", len(b), string(b))
|
||||
@@ -230,6 +239,9 @@ func (bc *BackendClient) send(cmd Command) {
|
||||
if err != nil {
|
||||
log.Fatalf("Failed json.Marshal(cmd): %v\n%#v\n", err, cmd)
|
||||
}
|
||||
if bytes.Contains(b, jsonEscapedZero) {
|
||||
log.Printf("[unexpected] zero byte in BackendClient.send command: %q", b)
|
||||
}
|
||||
bc.sendCommandMsg(b)
|
||||
}
|
||||
|
||||
|
||||
@@ -152,9 +152,15 @@ func (p *Prefs) pretty(goos string) string {
|
||||
if len(p.AdvertiseRoutes) > 0 || p.NoSNAT {
|
||||
fmt.Fprintf(&sb, "snat=%v ", !p.NoSNAT)
|
||||
}
|
||||
if len(p.AdvertiseTags) > 0 {
|
||||
fmt.Fprintf(&sb, "tags=%s ", strings.Join(p.AdvertiseTags, ","))
|
||||
}
|
||||
if goos == "linux" {
|
||||
fmt.Fprintf(&sb, "nf=%v ", p.NetfilterMode)
|
||||
}
|
||||
if p.ControlURL != "" && p.ControlURL != "https://login.tailscale.com" {
|
||||
fmt.Fprintf(&sb, "url=%q ", p.ControlURL)
|
||||
}
|
||||
if p.Persist != nil {
|
||||
sb.WriteString(p.Persist.Pretty())
|
||||
} else {
|
||||
|
||||
@@ -326,6 +326,32 @@ func TestPrefsPretty(t *testing.T) {
|
||||
"windows",
|
||||
"Prefs{ra=false dns=false want=true server=true Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
AllowSingleHosts: true,
|
||||
WantRunning: true,
|
||||
ControlURL: "http://localhost:1234",
|
||||
AdvertiseTags: []string{"tag:foo", "tag:bar"},
|
||||
},
|
||||
"darwin",
|
||||
`Prefs{ra=false dns=false want=true tags=tag:foo,tag:bar url="http://localhost:1234" Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
Persist: &controlclient.Persist{},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist{lm=, o=, n= u=""}}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
Persist: &controlclient.Persist{
|
||||
PrivateNodeKey: wgcfg.PrivateKey{1: 1},
|
||||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist{lm=, o=, n=[B1VKl] u=""}}`,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := tt.p.pretty(tt.os)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@@ -100,6 +101,14 @@ func (s *FileStore) String() string { return fmt.Sprintf("FileStore(%q)", s.path
|
||||
// NewFileStore returns a new file store that persists to path.
|
||||
func NewFileStore(path string) (*FileStore, error) {
|
||||
bs, err := ioutil.ReadFile(path)
|
||||
|
||||
// Treat an empty file as a missing file.
|
||||
// (https://github.com/tailscale/tailscale/issues/895#issuecomment-723255589)
|
||||
if err == nil && len(bs) == 0 {
|
||||
log.Printf("ipn.NewFileStore(%q): file empty; treating it like a missing file [warning]", path)
|
||||
err = os.ErrNotExist
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Write out an initial file, to verify that we can write
|
||||
|
||||
@@ -7,6 +7,7 @@ package interfaces
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
@@ -176,7 +177,12 @@ func getPACWindows() string {
|
||||
return ""
|
||||
}
|
||||
defer globalFree.Call(uintptr(unsafe.Pointer(res)))
|
||||
return windows.UTF16PtrToString(res)
|
||||
s := windows.UTF16PtrToString(res)
|
||||
if _, err := url.Parse(s); err != nil {
|
||||
log.Printf("getPACWindows: invalid URL %q from winhttp; ignoring", s)
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
const (
|
||||
ERROR_WINHTTP_AUTODETECTION_FAILED = 12180
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/tailscale/wireguard-go/wgcfg"
|
||||
"go4.org/mem"
|
||||
@@ -211,8 +210,13 @@ func (m MachineStatus) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func isNum(r rune) bool { return r >= '0' && r <= '9' }
|
||||
func isAlpha(r rune) bool { return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') }
|
||||
func isNum(b byte) bool {
|
||||
return b >= '0' && b <= '9'
|
||||
}
|
||||
|
||||
func isAlpha(b byte) bool {
|
||||
return (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
|
||||
}
|
||||
|
||||
// CheckTag valids whether a given string can be used as an ACL tag.
|
||||
// For now we allow only ascii alphanumeric tags, and they need to start
|
||||
@@ -227,34 +231,20 @@ func CheckTag(tag string) error {
|
||||
if !strings.HasPrefix(tag, "tag:") {
|
||||
return errors.New("tags must start with 'tag:'")
|
||||
}
|
||||
suffix := tag[len("tag:"):]
|
||||
if err := CheckTagSuffix(suffix); err != nil {
|
||||
return fmt.Errorf("invalid tag %q: %w", tag, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckTagSuffix checks whether tag is a valid tag suffix (the part
|
||||
// appearing after "tag:"). The error message does not reference
|
||||
// "tag:", so it's suitable for use by the "tailscale up" CLI tool
|
||||
// where the "tag:" isn't required. The returned error also does not
|
||||
// reference the tag itself, so the caller can wrap it as needed with
|
||||
// either the full or short form.
|
||||
func CheckTagSuffix(tag string) error {
|
||||
tag = tag[4:]
|
||||
if tag == "" {
|
||||
return errors.New("tag names must not be empty")
|
||||
}
|
||||
if i := strings.IndexFunc(tag, func(r rune) bool { return r >= utf8.RuneSelf }); i != -1 {
|
||||
return errors.New("tag names must only contain ASCII")
|
||||
if !isAlpha(tag[0]) {
|
||||
return errors.New("tag names must start with a letter, after 'tag:'")
|
||||
}
|
||||
if !isAlpha(rune(tag[0])) {
|
||||
return errors.New("tag name must start with a letter")
|
||||
}
|
||||
for _, r := range tag {
|
||||
if !isNum(r) && !isAlpha(r) && r != '-' {
|
||||
|
||||
for _, b := range []byte(tag) {
|
||||
if !isNum(b) && !isAlpha(b) && b != '-' {
|
||||
return errors.New("tag names can only contain numbers, letters, or dashes")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -509,6 +499,12 @@ type MapRequest struct {
|
||||
// added and removed all the time during development, and offer no
|
||||
// compatibility promise. To roll out semantic changes, bump
|
||||
// Version instead.
|
||||
//
|
||||
// Current DebugFlags values are:
|
||||
// * "warn-ip-forwarding-off": client is trying to be a subnet
|
||||
// router but their IP forwarding is broken.
|
||||
// * "v6-overlay": IPv6 development flag to have control send
|
||||
// v6 node addrs
|
||||
DebugFlags []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
@@ -529,9 +525,28 @@ type NetPortRange struct {
|
||||
}
|
||||
|
||||
// FilterRule represents one rule in a packet filter.
|
||||
//
|
||||
// A rule is logically a set of source CIDRs to match (described by
|
||||
// SrcIPs and SrcBits), and a set of destination targets that are then
|
||||
// allowed if a source IP is mathces of those CIDRs.
|
||||
type FilterRule struct {
|
||||
SrcIPs []string // "*" means all
|
||||
SrcBits []int
|
||||
// SrcIPs are the source IPs/networks to match.
|
||||
// The special value "*" means to match all.
|
||||
SrcIPs []string
|
||||
|
||||
// SrcBits values correspond to the SrcIPs above.
|
||||
//
|
||||
// If present at the same index, it changes the SrcIP above to
|
||||
// be a network with /n CIDR bits. If the slice is nil or
|
||||
// insufficiently long, the default value (for an IPv4
|
||||
// address) for a position is 32, as if the SrcIPs above were
|
||||
// a /32 mask. For a "*" SrcIPs value, the corresponding
|
||||
// SrcBits value is ignored.
|
||||
// TODO: for IPv6, clarify default bits length.
|
||||
SrcBits []int
|
||||
|
||||
// DstPorts are the port ranges to allow once a source IP
|
||||
// matches (is in the CIDR described by SrcIPs & SrcBits).
|
||||
DstPorts []NetPortRange
|
||||
}
|
||||
|
||||
@@ -635,6 +650,10 @@ type Debug struct {
|
||||
// TrimWGConfig controls whether Tailscale does lazy, on-demand
|
||||
// wireguard configuration of peers.
|
||||
TrimWGConfig opt.Bool `json:",omitempty"`
|
||||
|
||||
// DisableSubnetsIfPAC controls whether subnet routers should be
|
||||
// disabled if WPAD is present on the network.
|
||||
DisableSubnetsIfPAC opt.Bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (k MachineKey) String() string { return fmt.Sprintf("mkey:%x", k[:]) }
|
||||
|
||||
@@ -70,12 +70,12 @@ func TestMkversion(t *testing.T) {
|
||||
VERSION_XCODE="101.15.129"
|
||||
VERSION_WINRES="1,15,129,0"`},
|
||||
{"abcdef", "", 1, 2, 0, 17, `
|
||||
VERSION_SHORT="0.0.0"
|
||||
VERSION_LONG="0.0.0-tabcdef"
|
||||
VERSION_SHORT="1.2.0"
|
||||
VERSION_LONG="1.2.0-17-tabcdef"
|
||||
VERSION_GIT_HASH="abcdef"
|
||||
VERSION_EXTRA_HASH=""
|
||||
VERSION_XCODE="100.0.0"
|
||||
VERSION_WINRES="0,0,0,0"`},
|
||||
VERSION_XCODE="101.2.0"
|
||||
VERSION_WINRES="1,2,0,0"`},
|
||||
{"abcdef", "defghi", 1, 15, 0, 129, `
|
||||
VERSION_SHORT="1.15.129"
|
||||
VERSION_LONG="1.15.129-tabcdef-gdefghi"
|
||||
|
||||
@@ -1,22 +1,43 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
# Return the commitid of the given ref in the given repo dir. If the worktree
|
||||
# or index is dirty, also appends -dirty.
|
||||
#
|
||||
# $ git_hash_dirty ../.. HEAD
|
||||
# 1be01ddc6e430ca3aa9beea3587d16750efb3241-dirty
|
||||
git_hash_dirty() {
|
||||
(
|
||||
cd "$1"
|
||||
x=$(git rev-parse HEAD)
|
||||
if ! git diff-index --quiet HEAD; then
|
||||
x="$x-dirty"
|
||||
fi
|
||||
echo "$x"
|
||||
)
|
||||
}
|
||||
|
||||
case $# in
|
||||
0|1)
|
||||
# extra_hash describes a git repository other than the current
|
||||
# one. It gets embedded as an additional commit hash in built
|
||||
# extra_hash_or_dir is either:
|
||||
# - a git commitid
|
||||
# or
|
||||
# - the path to a git repo from which to calculate the real hash.
|
||||
#
|
||||
# It gets embedded as an additional commit hash in built
|
||||
# binaries, to help us locate the exact set of tools and code
|
||||
# that were used.
|
||||
extra_hash="${1:-}"
|
||||
if [ -z "$extra_hash" ]; then
|
||||
extra_hash_or_dir="${1:-}"
|
||||
if [ -z "$extra_hash_or_dir" ]; then
|
||||
# Nothing, empty extra hash is fine.
|
||||
extra_hash=""
|
||||
elif [ -d "$extra_hash/.git" ]; then
|
||||
extra_hash=$(cd "$extra_hash" && git describe --always --dirty --exclude '*' --abbrev=200)
|
||||
elif ! expr "$extra_hash" : "^[0-9a-f]*$"; then
|
||||
echo "Invalid extra hash '$extra_hash', must be a git commit hash or path to a git repo" >&2
|
||||
elif [ -d "$extra_hash_or_dir/.git" ]; then
|
||||
extra_hash=$(git_hash_dirty "$extra_hash_or_dir" HEAD)
|
||||
elif ! expr "$extra_hash_or_dir" : "^[0-9a-f]*$"; then
|
||||
echo "Invalid extra hash '$extra_hash_or_dir', must be a git commit or path to a git repo" >&2
|
||||
exit 1
|
||||
else
|
||||
extra_hash="$extra_hash_or_dir"
|
||||
fi
|
||||
|
||||
# Load the base version and optional corresponding git hash
|
||||
@@ -25,15 +46,12 @@ case $# in
|
||||
version_file="$(dirname $0)/../VERSION.txt"
|
||||
IFS=".$IFS" read -r major minor patch base_git_hash <"$version_file"
|
||||
if [ -z "$base_git_hash" ]; then
|
||||
base_git_hash=$(git rev-list --max-count=1 HEAD -- $version_file)
|
||||
base_git_hash=$(git rev-list --max-count=1 HEAD -- "$version_file")
|
||||
fi
|
||||
|
||||
# The full git has we're currently building at. --abbrev=200 is an
|
||||
# arbitrary large number larger than all currently-known hashes, so
|
||||
# that git displays the full commit hash.
|
||||
git_hash=$(git describe --always --dirty --exclude '*' --abbrev=200)
|
||||
git_hash=$(git_hash_dirty . HEAD)
|
||||
# The number of extra commits between the release base to git_hash.
|
||||
change_count=$(git rev-list ${base_git_hash}..HEAD | wc -l)
|
||||
change_count=$(git rev-list --count HEAD "^$base_git_hash")
|
||||
;;
|
||||
6)
|
||||
# Test mode: rather than run git commands and whatnot, take in
|
||||
@@ -46,14 +64,14 @@ case $# in
|
||||
change_count=$6
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [extra-git-hash-or-checkout]"
|
||||
echo "Usage: $0 [extra-git-commitid-or-dir]"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
# Shortened versions of git hashes, so that they fit neatly into an
|
||||
# "elongated" but still human-readable version number.
|
||||
short_git_hash=$(echo $git_hash | cut -c-9)
|
||||
short_extra_hash=$(echo $extra_hash | cut -c-9)
|
||||
short_git_hash=$(echo "$git_hash" | cut -c1-9)
|
||||
short_extra_hash=$(echo "$extra_hash" | cut -c1-9)
|
||||
|
||||
# Convert major/minor/patch/change_count into an adjusted
|
||||
# major/minor/patch. This block is where all our policies on
|
||||
@@ -62,25 +80,28 @@ if expr "$minor" : "[0-9]*[13579]$" >/dev/null; then
|
||||
# Odd minor numbers are unstable builds.
|
||||
if [ "$patch" != "0" ]; then
|
||||
# This is a fatal error, because a non-zero patch number
|
||||
# indicates that we created an unstable git tag in violation
|
||||
# indicates that we created an unstable VERSION.txt in violation
|
||||
# of our versioning policy, and we want to blow up loudly to
|
||||
# get that fixed.
|
||||
echo "Unstable release $major.$minor.$patch has a non-zero patch number, which is not allowed" >&2
|
||||
exit 1
|
||||
fi
|
||||
patch="$change_count"
|
||||
change_suffix=""
|
||||
elif [ "$change_count" != "0" ]; then
|
||||
# Even minor numbers are stable builds, but stable builds are
|
||||
# supposed to have a zero change count. Therefore, we're currently
|
||||
# describing a commit that's on a release branch, but hasn't been
|
||||
# tagged as a patch release yet. We allow these commits to build
|
||||
# for testing purposes, but force their version number to 0.0.0,
|
||||
# to reflect that they're an unreleasable build. The git hashes
|
||||
# still completely describe the build commit, so we can still
|
||||
# figure out what this build is if it escapes into the wild.
|
||||
major="0"
|
||||
minor="0"
|
||||
patch="0"
|
||||
# tagged as a patch release yet.
|
||||
#
|
||||
# We used to change the version number to 0.0.0 in that case, but that
|
||||
# caused some features to get disabled due to the low version number.
|
||||
# Instead, add yet another suffix to the version number, with a change
|
||||
# count.
|
||||
change_suffix="-$change_count"
|
||||
else
|
||||
# Even minor number with no extra changes.
|
||||
change_suffix=""
|
||||
fi
|
||||
|
||||
# Hack for 1.1: add 1000 to the patch number. We switched from using
|
||||
@@ -95,15 +116,15 @@ fi
|
||||
# policies. All that remains is to output the various vars that other
|
||||
# code can use to embed version data.
|
||||
if [ -z "$extra_hash" ]; then
|
||||
long_version_suffix="-t$short_git_hash"
|
||||
long_version_suffix="$change_suffix-t$short_git_hash"
|
||||
else
|
||||
long_version_suffix="-t${short_git_hash}-g${short_extra_hash}"
|
||||
long_version_suffix="$change_suffix-t$short_git_hash-g$short_extra_hash"
|
||||
fi
|
||||
cat <<EOF
|
||||
VERSION_SHORT="${major}.${minor}.${patch}"
|
||||
VERSION_LONG="${major}.${minor}.${patch}${long_version_suffix}"
|
||||
VERSION_GIT_HASH="${git_hash}"
|
||||
VERSION_EXTRA_HASH="${extra_hash}"
|
||||
VERSION_XCODE="$((major + 100)).${minor}.${patch}"
|
||||
VERSION_WINRES="${major},${minor},${patch},0"
|
||||
VERSION_SHORT="$major.$minor.$patch"
|
||||
VERSION_LONG="$major.$minor.$patch$long_version_suffix"
|
||||
VERSION_GIT_HASH="$git_hash"
|
||||
VERSION_EXTRA_HASH="$extra_hash"
|
||||
VERSION_XCODE="$((major + 100)).$minor.$patch"
|
||||
VERSION_WINRES="$major,$minor,$patch,0"
|
||||
EOF
|
||||
|
||||
@@ -2492,6 +2492,9 @@ func (c *Conn) bind1(ruc **RebindingUDPConn, which string) error {
|
||||
host := ""
|
||||
if inTest() && !c.simulatedNetwork {
|
||||
host = "127.0.0.1"
|
||||
if which == "udp6" {
|
||||
host = "::1"
|
||||
}
|
||||
}
|
||||
var pc net.PacketConn
|
||||
var err error
|
||||
|
||||
@@ -63,7 +63,8 @@ func (lhs Config) Equal(rhs Config) bool {
|
||||
// ManagerConfig is the set of parameters from which
|
||||
// a manager implementation is chosen and initialized.
|
||||
type ManagerConfig struct {
|
||||
// logf is the logger for the manager to use.
|
||||
// Logf is the logger for the manager to use.
|
||||
// It is wrapped with a "dns: " prefix.
|
||||
Logf logger.Logf
|
||||
// InterfaceNAme is the name of the interface with which DNS settings should be associated.
|
||||
InterfaceName string
|
||||
|
||||
@@ -1023,6 +1023,20 @@ func supportsV6() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Older kernels don't support IPv6 policy routing.
|
||||
bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
|
||||
if err != nil {
|
||||
// Absent knob means policy routing is unsupported.
|
||||
return false
|
||||
}
|
||||
disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if disabled {
|
||||
return false
|
||||
}
|
||||
|
||||
// Some distros ship ip6tables separately from iptables.
|
||||
if _, err := exec.LookPath("ip6tables"); err != nil {
|
||||
return false
|
||||
|
||||
@@ -39,7 +39,7 @@ func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Devic
|
||||
nativeTun := tundev.(*tun.NativeTun)
|
||||
guid := nativeTun.GUID().String()
|
||||
mconfig := dns.ManagerConfig{
|
||||
Logf: logger.WithPrefix(logf, "dns: "),
|
||||
Logf: logf,
|
||||
InterfaceName: guid,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user