mirror of
https://github.com/tailscale/tailscale.git
synced 2026-04-06 15:43:43 -04:00
Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest
This commit is contained in:
@@ -506,7 +506,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
||||
args: upArgsT{
|
||||
exitNodeIP: "foo",
|
||||
},
|
||||
wantErr: `invalid IP address "foo" for --exit-node: unable to parse IP`,
|
||||
wantErr: `invalid IP address "foo" for --exit-node: ParseIP("foo"): unable to parse IP`,
|
||||
},
|
||||
{
|
||||
name: "error_exit_node_allow_lan_without_exit_node",
|
||||
|
||||
9
go.mod
9
go.mod
@@ -26,23 +26,24 @@ require (
|
||||
github.com/peterbourgon/ff/v2 v2.0.0
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
|
||||
github.com/tailscale/wireguard-go v0.0.0-20210429195722-6cd106ab1339
|
||||
github.com/tailscale/wireguard-go v0.0.0-20210510175647-030c638da3df
|
||||
github.com/tcnksm/go-httpstat v0.2.0
|
||||
github.com/toqueteos/webbrowser v1.2.0
|
||||
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58
|
||||
golang.org/x/tools v0.1.0
|
||||
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
honnef.co/go/tools v0.1.0
|
||||
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44
|
||||
inet.af/netaddr v0.0.0-20210508014949-da1c2a70a83d
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
|
||||
inet.af/peercred v0.0.0-20210302202138-56e694897155
|
||||
inet.af/wf v0.0.0-20210424212123-eaa011a774a4
|
||||
rsc.io/goversion v1.2.0
|
||||
)
|
||||
|
||||
|
||||
17
go.sum
17
go.sum
@@ -23,6 +23,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/frankban/quicktest v1.12.1 h1:P6vQcHwZYgVGIpUzKB5DXzkEeYJppJOStPLuh9aB89c=
|
||||
@@ -106,6 +107,7 @@ github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwp
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/peterbourgon/ff/v2 v2.0.0 h1:lx0oYI5qr/FU1xnpNhQ+EZM04gKgn46jyYvGEEqBBbY=
|
||||
github.com/peterbourgon/ff/v2 v2.0.0/go.mod h1:xjwr+t+SjWm4L46fcj/D+Ap+6ME7+HqFzaP22pP5Ggk=
|
||||
github.com/peterbourgon/ff/v3 v3.0.0/go.mod h1:UILIFjRH5a/ar8TjXYLTkIvSvekZqPm5Eb/qbGk6CT0=
|
||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8=
|
||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -125,6 +127,8 @@ github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBW
|
||||
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20210429195722-6cd106ab1339 h1:OjLaZ57xeWJUUBAJN5KmsgjsaUABTZhcvgO/lKtZ8sQ=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20210429195722-6cd106ab1339/go.mod h1:ys4yUmhKncXy1jWP34qUHKipRjl322VVhxoh1Rkfo7c=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20210510175647-030c638da3df h1:ekBw6cxmDhXf9YxTmMZh7SPwUh9rnRRnaoX7HFiGobc=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20210510175647-030c638da3df/go.mod h1:ys4yUmhKncXy1jWP34qUHKipRjl322VVhxoh1Rkfo7c=
|
||||
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=
|
||||
@@ -197,14 +201,17 @@ golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
||||
@@ -220,8 +227,9 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag=
|
||||
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -243,11 +251,14 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c=
|
||||
honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM=
|
||||
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44 h1:p7fX77zWzZMuNdJUhniBsmN1OvFOrW9SOtvgnzqUZX4=
|
||||
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44/go.mod h1:I2i9ONCXRZDnG1+7O8fSuYzjcPxHQXrIfzD/IkR87x4=
|
||||
inet.af/netaddr v0.0.0-20210508014949-da1c2a70a83d h1:9tuJMxDV7THGfXWirKBD/v9rbsBC21bHd2eEYsYuIek=
|
||||
inet.af/netaddr v0.0.0-20210508014949-da1c2a70a83d/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22 h1:DNtszwGa6w76qlIr+PbPEnlBJdiRV8SaxeigOy0q1gg=
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22/go.mod h1:GVx+5OZtbG4TVOW5ilmyRZAZXr1cNwfqUEkTOtWK0PM=
|
||||
inet.af/peercred v0.0.0-20210302202138-56e694897155 h1:KojYNEYqDkZ2O3LdyTstR1l13L3ePKTIEM2h7ONkfkE=
|
||||
inet.af/peercred v0.0.0-20210302202138-56e694897155/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
|
||||
inet.af/wf v0.0.0-20210424212123-eaa011a774a4 h1:g1VVXY1xRKoO17aKY3g9KeJxDW0lGx1n2Y+WPSWkOL8=
|
||||
inet.af/wf v0.0.0-20210424212123-eaa011a774a4/go.mod h1:56/0QVlZ4NmPRh1QuU2OfrKqjSgt5P39R534gD2JMpQ=
|
||||
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
|
||||
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
|
||||
|
||||
@@ -78,8 +78,16 @@ func (k Key) HexString() string { return hex.EncodeToString(k[:]) }
|
||||
func (k Key) Equal(k2 Key) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 }
|
||||
|
||||
func (k *Key) ShortString() string {
|
||||
long := k.Base64()
|
||||
return "[" + long[0:5] + "]"
|
||||
// The goal here is to generate "[" + base64.StdEncoding.EncodeToString(k[:])[:5] + "]".
|
||||
// Since we only care about the first 5 characters, it suffices to encode the first 4 bytes of k.
|
||||
// Encoding those 4 bytes requires 8 bytes.
|
||||
// Make dst have size 9, to fit the leading '[' plus those 8 bytes.
|
||||
// We slice the unused ones away at the end.
|
||||
dst := make([]byte, 9)
|
||||
dst[0] = '['
|
||||
base64.StdEncoding.Encode(dst[1:], k[:4])
|
||||
dst[6] = ']'
|
||||
return string(dst[:7])
|
||||
}
|
||||
|
||||
func (k *Key) IsZero() bool {
|
||||
|
||||
@@ -171,3 +171,13 @@ func BenchmarkUnmarshalJSON(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sinkString string
|
||||
|
||||
func BenchmarkShortString(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
var k Key
|
||||
for i := 0; i < b.N; i++ {
|
||||
sinkString = k.ShortString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,16 @@ func ToFQDN(s string) (FQDN, error) {
|
||||
if isValidFQDN(s) {
|
||||
return FQDN(s), nil
|
||||
}
|
||||
if len(s) == 0 {
|
||||
if len(s) == 0 || s == "." {
|
||||
return FQDN("."), nil
|
||||
}
|
||||
|
||||
if s[len(s)-1] == '.' {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
if s[0] == '.' {
|
||||
s = s[1:]
|
||||
}
|
||||
if len(s) > maxNameLength {
|
||||
return "", fmt.Errorf("%q is too long to be a DNS name", s)
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ func TestFQDN(t *testing.T) {
|
||||
{".", ".", false, 0},
|
||||
{"foo.com", "foo.com.", false, 2},
|
||||
{"foo.com.", "foo.com.", false, 2},
|
||||
{".foo.com.", "foo.com.", false, 2},
|
||||
{".foo.com", "foo.com.", false, 2},
|
||||
{"com", "com.", false, 1},
|
||||
{"www.tailscale.com", "www.tailscale.com.", false, 3},
|
||||
{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com", "", true, 0},
|
||||
{strings.Repeat("aaaaa.", 60) + "com", "", true, 0},
|
||||
{".com", "", true, 0},
|
||||
{"foo..com", "", true, 0},
|
||||
}
|
||||
|
||||
|
||||
510
wf/firewall.go
Normal file
510
wf/firewall.go
Normal file
@@ -0,0 +1,510 @@
|
||||
// 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package wf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"inet.af/netaddr"
|
||||
"inet.af/wf"
|
||||
)
|
||||
|
||||
// Known addresses.
|
||||
var (
|
||||
linkLocalRange = netaddr.MustParseIPPrefix("ff80::/10")
|
||||
linkLocalDHCPMulticast = netaddr.MustParseIP("ff02::1:2")
|
||||
siteLocalDHCPMulticast = netaddr.MustParseIP("ff05::1:3")
|
||||
linkLocalRouterMulticast = netaddr.MustParseIP("ff02::2")
|
||||
)
|
||||
|
||||
type direction int
|
||||
|
||||
const (
|
||||
directionInbound direction = iota
|
||||
directionOutbound
|
||||
directionBoth
|
||||
)
|
||||
|
||||
type protocol int
|
||||
|
||||
const (
|
||||
protocolV4 protocol = iota
|
||||
protocolV6
|
||||
protocolAll
|
||||
)
|
||||
|
||||
// getLayers returns the wf.LayerIDs where the rules should be added based
|
||||
// on the protocol and direction.
|
||||
func (p protocol) getLayers(d direction) []wf.LayerID {
|
||||
var layers []wf.LayerID
|
||||
if p == protocolAll || p == protocolV4 {
|
||||
if d == directionBoth || d == directionInbound {
|
||||
layers = append(layers, wf.LayerALEAuthRecvAcceptV4)
|
||||
}
|
||||
if d == directionBoth || d == directionOutbound {
|
||||
layers = append(layers, wf.LayerALEAuthConnectV4)
|
||||
}
|
||||
}
|
||||
if p == protocolAll || p == protocolV6 {
|
||||
if d == directionBoth || d == directionInbound {
|
||||
layers = append(layers, wf.LayerALEAuthRecvAcceptV6)
|
||||
}
|
||||
if d == directionBoth || d == directionOutbound {
|
||||
layers = append(layers, wf.LayerALEAuthConnectV6)
|
||||
}
|
||||
}
|
||||
return layers
|
||||
}
|
||||
|
||||
func ruleName(action wf.Action, l wf.LayerID, name string) string {
|
||||
switch l {
|
||||
case wf.LayerALEAuthConnectV4:
|
||||
return fmt.Sprintf("%s outbound %s (IPv4)", action, name)
|
||||
case wf.LayerALEAuthConnectV6:
|
||||
return fmt.Sprintf("%s outbound %s (IPv6)", action, name)
|
||||
case wf.LayerALEAuthRecvAcceptV4:
|
||||
return fmt.Sprintf("%s inbound %s (IPv4)", action, name)
|
||||
case wf.LayerALEAuthRecvAcceptV6:
|
||||
return fmt.Sprintf("%s inbound %s (IPv6)", action, name)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Firewall uses the Windows Filtering Platform to implement a network firewall.
|
||||
type Firewall struct {
|
||||
luid uint64
|
||||
providerID wf.ProviderID
|
||||
sublayerID wf.SublayerID
|
||||
session *wf.Session
|
||||
|
||||
permittedRoutes map[netaddr.IPPrefix][]*wf.Rule
|
||||
}
|
||||
|
||||
// New returns a new Firewall for the provdied interface ID.
|
||||
func New(luid uint64) (*Firewall, error) {
|
||||
session, err := wf.New(&wf.Options{
|
||||
Name: "Tailscale firewall",
|
||||
Dynamic: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wguid, err := windows.GenerateGUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providerID := wf.ProviderID(wguid)
|
||||
if err := session.AddProvider(&wf.Provider{
|
||||
ID: providerID,
|
||||
Name: "Tailscale provider",
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wguid, err = windows.GenerateGUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sublayerID := wf.SublayerID(wguid)
|
||||
if err := session.AddSublayer(&wf.Sublayer{
|
||||
ID: sublayerID,
|
||||
Name: "Tailscale permissive and blocking filters",
|
||||
Weight: 0,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := &Firewall{
|
||||
luid: luid,
|
||||
session: session,
|
||||
providerID: providerID,
|
||||
sublayerID: sublayerID,
|
||||
permittedRoutes: make(map[netaddr.IPPrefix][]*wf.Rule),
|
||||
}
|
||||
if err := f.enable(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
type weight uint64
|
||||
|
||||
const (
|
||||
weightTailscaleTraffic weight = 15
|
||||
weightKnownTraffic weight = 12
|
||||
weightCatchAll weight = 0
|
||||
)
|
||||
|
||||
func (f *Firewall) enable() error {
|
||||
if err := f.permitTailscaleService(weightTailscaleTraffic); err != nil {
|
||||
return fmt.Errorf("permitTailscaleService failed: %w", err)
|
||||
}
|
||||
|
||||
if err := f.permitTunInterface(weightTailscaleTraffic); err != nil {
|
||||
return fmt.Errorf("permitTunInterface failed: %w", err)
|
||||
}
|
||||
|
||||
if err := f.permitDNS(weightTailscaleTraffic); err != nil {
|
||||
return fmt.Errorf("permitDNS failed: %w", err)
|
||||
}
|
||||
|
||||
if err := f.permitLoopback(weightKnownTraffic); err != nil {
|
||||
return fmt.Errorf("permitLoopback failed: %w", err)
|
||||
}
|
||||
|
||||
if err := f.permitDHCPv4(weightKnownTraffic); err != nil {
|
||||
return fmt.Errorf("permitDHCPv4 failed: %w", err)
|
||||
}
|
||||
|
||||
if err := f.permitDHCPv6(weightKnownTraffic); err != nil {
|
||||
return fmt.Errorf("permitDHCPv6 failed: %w", err)
|
||||
}
|
||||
|
||||
if err := f.permitNDP(weightKnownTraffic); err != nil {
|
||||
return fmt.Errorf("permitNDP failed: %w", err)
|
||||
}
|
||||
|
||||
/* TODO: actually evaluate if this does anything and if we need this. It's layer 2; our other rules are layer 3.
|
||||
* In other words, if somebody complains, try enabling it. For now, keep it off.
|
||||
* TODO(maisem): implement this.
|
||||
err = permitHyperV(session, baseObjects, weightKnownTraffic)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
*/
|
||||
|
||||
if err := f.blockAll(weightCatchAll); err != nil {
|
||||
return fmt.Errorf("blockAll failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatedPermittedRoutes adds rules to allow incoming and outgoing connections
|
||||
// from the provided prefixes. It will also remove rules for routes that were
|
||||
// previously added but have been removed.
|
||||
func (f *Firewall) UpdatePermittedRoutes(newRoutes []netaddr.IPPrefix) error {
|
||||
var routesToAdd []netaddr.IPPrefix
|
||||
routeMap := make(map[netaddr.IPPrefix]bool)
|
||||
for _, r := range newRoutes {
|
||||
routeMap[r] = true
|
||||
if _, ok := f.permittedRoutes[r]; !ok {
|
||||
routesToAdd = append(routesToAdd, r)
|
||||
}
|
||||
}
|
||||
var routesToRemove []netaddr.IPPrefix
|
||||
for r := range f.permittedRoutes {
|
||||
if !routeMap[r] {
|
||||
routesToRemove = append(routesToRemove, r)
|
||||
}
|
||||
}
|
||||
for _, r := range routesToRemove {
|
||||
for _, rule := range f.permittedRoutes[r] {
|
||||
if err := f.session.DeleteRule(rule.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
delete(f.permittedRoutes, r)
|
||||
}
|
||||
for _, r := range routesToAdd {
|
||||
conditions := []*wf.Match{
|
||||
{
|
||||
Field: wf.FieldIPRemoteAddress,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: r,
|
||||
},
|
||||
}
|
||||
var p protocol
|
||||
if r.IP.Is4() {
|
||||
p = protocolV4
|
||||
} else {
|
||||
p = protocolV6
|
||||
}
|
||||
rules, err := f.addRules("local route", weightKnownTraffic, conditions, wf.ActionPermit, p, directionBoth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.permittedRoutes[r] = rules
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewall) newRule(name string, w weight, layer wf.LayerID, conditions []*wf.Match, action wf.Action) (*wf.Rule, error) {
|
||||
id, err := windows.GenerateGUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wf.Rule{
|
||||
Name: ruleName(action, layer, name),
|
||||
ID: wf.RuleID(id),
|
||||
Provider: f.providerID,
|
||||
Sublayer: f.sublayerID,
|
||||
Layer: layer,
|
||||
Weight: uint64(w),
|
||||
Conditions: conditions,
|
||||
Action: action,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *Firewall) addRules(name string, w weight, conditions []*wf.Match, action wf.Action, p protocol, d direction) ([]*wf.Rule, error) {
|
||||
var rules []*wf.Rule
|
||||
for _, l := range p.getLayers(d) {
|
||||
r, err := f.newRule(name, w, l, conditions, action)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := f.session.AddRule(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules = append(rules, r)
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (f *Firewall) blockAll(w weight) error {
|
||||
_, err := f.addRules("all", w, nil, wf.ActionBlock, protocolAll, directionBoth)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *Firewall) permitNDP(w weight) error {
|
||||
// These are aliased according to:
|
||||
// https://social.msdn.microsoft.com/Forums/azure/en-US/eb2aa3cd-5f1c-4461-af86-61e7d43ccc23/filtering-icmp-by-type-code?forum=wfp
|
||||
fieldICMPType := wf.FieldIPLocalPort
|
||||
fieldICMPCode := wf.FieldIPRemotePort
|
||||
|
||||
var icmpConditions = func(t, c uint16, remoteAddress interface{}) []*wf.Match {
|
||||
conditions := []*wf.Match{
|
||||
{
|
||||
Field: wf.FieldIPProtocol,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: wf.IPProtoICMPV6,
|
||||
},
|
||||
{
|
||||
Field: fieldICMPType,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: t,
|
||||
},
|
||||
{
|
||||
Field: fieldICMPCode,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: c,
|
||||
},
|
||||
}
|
||||
if remoteAddress != nil {
|
||||
conditions = append(conditions, &wf.Match{
|
||||
Field: wf.FieldIPRemoteAddress,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: linkLocalRouterMulticast,
|
||||
})
|
||||
}
|
||||
return conditions
|
||||
}
|
||||
/* TODO: actually handle the hop limit somehow! The rules should vaguely be:
|
||||
* - icmpv6 133: must be outgoing, dst must be FF02::2/128, hop limit must be 255
|
||||
* - icmpv6 134: must be incoming, src must be FE80::/10, hop limit must be 255
|
||||
* - icmpv6 135: either incoming or outgoing, hop limit must be 255
|
||||
* - icmpv6 136: either incoming or outgoing, hop limit must be 255
|
||||
* - icmpv6 137: must be incoming, src must be FE80::/10, hop limit must be 255
|
||||
*/
|
||||
|
||||
//
|
||||
// Router Solicitation Message
|
||||
// ICMP type 133, code 0. Outgoing.
|
||||
//
|
||||
conditions := icmpConditions(133, 0, linkLocalRouterMulticast)
|
||||
if _, err := f.addRules("NDP type 133", w, conditions, wf.ActionPermit, protocolV6, directionOutbound); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// Router Advertisement Message
|
||||
// ICMP type 134, code 0. Incoming.
|
||||
//
|
||||
conditions = icmpConditions(134, 0, linkLocalRange)
|
||||
if _, err := f.addRules("NDP type 134", w, conditions, wf.ActionPermit, protocolV6, directionInbound); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// Neighbor Solicitation Message
|
||||
// ICMP type 135, code 0. Bi-directional.
|
||||
//
|
||||
conditions = icmpConditions(135, 0, nil)
|
||||
if _, err := f.addRules("NDP type 135", w, conditions, wf.ActionPermit, protocolV6, directionBoth); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// Neighbor Advertisement Message
|
||||
// ICMP type 136, code 0. Bi-directional.
|
||||
//
|
||||
conditions = icmpConditions(136, 0, nil)
|
||||
if _, err := f.addRules("NDP type 136", w, conditions, wf.ActionPermit, protocolV6, directionBoth); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// Redirect Message
|
||||
// ICMP type 137, code 0. Incoming.
|
||||
//
|
||||
conditions = icmpConditions(137, 0, linkLocalRange)
|
||||
if _, err := f.addRules("NDP type 137", w, conditions, wf.ActionPermit, protocolV6, directionInbound); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewall) permitDHCPv6(w weight) error {
|
||||
var dhcpConditions = func(remoteAddrs ...interface{}) []*wf.Match {
|
||||
conditions := []*wf.Match{
|
||||
{
|
||||
Field: wf.FieldIPProtocol,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: wf.IPProtoUDP,
|
||||
},
|
||||
{
|
||||
Field: wf.FieldIPLocalAddress,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: linkLocalRange,
|
||||
},
|
||||
{
|
||||
Field: wf.FieldIPLocalPort,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: uint16(546),
|
||||
},
|
||||
{
|
||||
Field: wf.FieldIPRemotePort,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: uint16(547),
|
||||
},
|
||||
}
|
||||
for _, a := range remoteAddrs {
|
||||
conditions = append(conditions, &wf.Match{
|
||||
Field: wf.FieldIPRemoteAddress,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: a,
|
||||
})
|
||||
}
|
||||
return conditions
|
||||
}
|
||||
conditions := dhcpConditions(linkLocalDHCPMulticast, siteLocalDHCPMulticast)
|
||||
if _, err := f.addRules("DHCP request", w, conditions, wf.ActionPermit, protocolV6, directionOutbound); err != nil {
|
||||
return err
|
||||
}
|
||||
conditions = dhcpConditions(linkLocalRange)
|
||||
if _, err := f.addRules("DHCP response", w, conditions, wf.ActionPermit, protocolV6, directionInbound); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewall) permitDHCPv4(w weight) error {
|
||||
var dhcpConditions = func(remoteAddrs ...interface{}) []*wf.Match {
|
||||
conditions := []*wf.Match{
|
||||
{
|
||||
Field: wf.FieldIPProtocol,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: wf.IPProtoUDP,
|
||||
},
|
||||
{
|
||||
Field: wf.FieldIPLocalPort,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: uint16(68),
|
||||
},
|
||||
{
|
||||
Field: wf.FieldIPRemotePort,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: uint16(67),
|
||||
},
|
||||
}
|
||||
for _, a := range remoteAddrs {
|
||||
conditions = append(conditions, &wf.Match{
|
||||
Field: wf.FieldIPRemoteAddress,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: a,
|
||||
})
|
||||
}
|
||||
return conditions
|
||||
}
|
||||
conditions := dhcpConditions(netaddr.IPv4(255, 255, 255, 255))
|
||||
if _, err := f.addRules("DHCP request", w, conditions, wf.ActionPermit, protocolV4, directionOutbound); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conditions = dhcpConditions()
|
||||
if _, err := f.addRules("DHCP response", w, conditions, wf.ActionPermit, protocolV4, directionInbound); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewall) permitTunInterface(w weight) error {
|
||||
condition := []*wf.Match{
|
||||
{
|
||||
Field: wf.FieldIPLocalInterface,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: f.luid,
|
||||
},
|
||||
}
|
||||
_, err := f.addRules("on TUN", w, condition, wf.ActionPermit, protocolAll, directionBoth)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *Firewall) permitLoopback(w weight) error {
|
||||
condition := []*wf.Match{
|
||||
{
|
||||
Field: wf.FieldFlags,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: wf.ConditionFlagIsLoopback,
|
||||
},
|
||||
}
|
||||
_, err := f.addRules("on loopback", w, condition, wf.ActionPermit, protocolAll, directionBoth)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *Firewall) permitDNS(w weight) error {
|
||||
conditions := []*wf.Match{
|
||||
{
|
||||
Field: wf.FieldIPRemotePort,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: uint16(53),
|
||||
},
|
||||
// Repeat the condition type for logical OR.
|
||||
{
|
||||
Field: wf.FieldIPProtocol,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: wf.IPProtoUDP,
|
||||
},
|
||||
{
|
||||
Field: wf.FieldIPProtocol,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: wf.IPProtoTCP,
|
||||
},
|
||||
}
|
||||
_, err := f.addRules("DNS", w, conditions, wf.ActionPermit, protocolAll, directionBoth)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *Firewall) permitTailscaleService(w weight) error {
|
||||
currentFile, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appID, err := wf.AppID(currentFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get app id for %q: %w", currentFile, err)
|
||||
}
|
||||
conditions := []*wf.Match{
|
||||
{
|
||||
Field: wf.FieldALEAppID,
|
||||
Op: wf.MatchTypeEqual,
|
||||
Value: appID,
|
||||
},
|
||||
}
|
||||
_, err = f.addRules("unrestricted traffic for Tailscale service", w, conditions, wf.ActionPermit, protocolAll, directionBoth)
|
||||
return err
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func main() {
|
||||
|
||||
// tx=134236 rx=133166 (1070 = 0.80% loss) (1088.9 Mbits/sec)
|
||||
case 101:
|
||||
setupWGTest(logf, traf, Addr1, Addr2)
|
||||
setupWGTest(nil, logf, traf, Addr1, Addr2)
|
||||
|
||||
default:
|
||||
log.Fatalf("provide a valid test number (0..n)")
|
||||
|
||||
@@ -43,7 +43,7 @@ func BenchmarkBatchTCP(b *testing.B) {
|
||||
|
||||
func BenchmarkWireGuardTest(b *testing.B) {
|
||||
run(b, func(logf logger.Logf, traf *TrafficGen) {
|
||||
setupWGTest(logf, traf, Addr1, Addr2)
|
||||
setupWGTest(b, logf, traf, Addr1, Addr2)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ func (t *TrafficGen) Generate(b []byte, ofs int) int {
|
||||
// GotPacket processes a packet that came back on the receive side.
|
||||
func (t *TrafficGen) GotPacket(b []byte, ofs int) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
s := &t.cur
|
||||
seq := int64(binary.BigEndian.Uint64(
|
||||
@@ -203,9 +204,6 @@ func (t *TrafficGen) GotPacket(b []byte, ofs int) {
|
||||
|
||||
f := t.onFirstPacket
|
||||
t.onFirstPacket = nil
|
||||
|
||||
t.mu.Unlock()
|
||||
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"inet.af/netaddr"
|
||||
@@ -25,7 +26,7 @@
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
)
|
||||
|
||||
func setupWGTest(logf logger.Logf, traf *TrafficGen, a1, a2 netaddr.IPPrefix) {
|
||||
func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netaddr.IPPrefix) {
|
||||
l1 := logger.WithPrefix(logf, "e1: ")
|
||||
k1, err := wgkey.NewPrivate()
|
||||
if err != nil {
|
||||
@@ -49,6 +50,9 @@ func setupWGTest(logf logger.Logf, traf *TrafficGen, a1, a2 netaddr.IPPrefix) {
|
||||
if err != nil {
|
||||
log.Fatalf("e1 init: %v", err)
|
||||
}
|
||||
if b != nil {
|
||||
b.Cleanup(e1.Close)
|
||||
}
|
||||
|
||||
l2 := logger.WithPrefix(logf, "e2: ")
|
||||
k2, err := wgkey.NewPrivate()
|
||||
@@ -73,6 +77,9 @@ func setupWGTest(logf logger.Logf, traf *TrafficGen, a1, a2 netaddr.IPPrefix) {
|
||||
if err != nil {
|
||||
log.Fatalf("e2 init: %v", err)
|
||||
}
|
||||
if b != nil {
|
||||
b.Cleanup(e2.Close)
|
||||
}
|
||||
|
||||
e1.SetFilter(filter.NewAllowAllForTest(l1))
|
||||
e2.SetFilter(filter.NewAllowAllForTest(l2))
|
||||
@@ -80,6 +87,7 @@ func setupWGTest(logf logger.Logf, traf *TrafficGen, a1, a2 netaddr.IPPrefix) {
|
||||
var wait sync.WaitGroup
|
||||
wait.Add(2)
|
||||
|
||||
var e1waitDoneOnce sync.Once
|
||||
e1.SetStatusCallback(func(st *wgengine.Status, err error) {
|
||||
if err != nil {
|
||||
log.Fatalf("e1 status err: %v", err)
|
||||
@@ -111,9 +119,10 @@ func setupWGTest(logf logger.Logf, traf *TrafficGen, a1, a2 netaddr.IPPrefix) {
|
||||
}
|
||||
c2.Peers = []wgcfg.Peer{p}
|
||||
e2.Reconfig(&c2, &router.Config{}, new(dns.Config))
|
||||
wait.Done()
|
||||
e1waitDoneOnce.Do(wait.Done)
|
||||
})
|
||||
|
||||
var e2waitDoneOnce sync.Once
|
||||
e2.SetStatusCallback(func(st *wgengine.Status, err error) {
|
||||
if err != nil {
|
||||
log.Fatalf("e2 status err: %v", err)
|
||||
@@ -145,7 +154,7 @@ func setupWGTest(logf logger.Logf, traf *TrafficGen, a1, a2 netaddr.IPPrefix) {
|
||||
}
|
||||
c1.Peers = []wgcfg.Peer{p}
|
||||
e1.Reconfig(&c1, &router.Config{}, new(dns.Config))
|
||||
wait.Done()
|
||||
e2waitDoneOnce.Do(wait.Done)
|
||||
})
|
||||
|
||||
// Not using DERP in this test (for now?).
|
||||
|
||||
Reference in New Issue
Block a user