ipn: handle advertised routes provided by frontend.

Signed-off-by: David Anderson <dave@natulte.net>
pull/75/head
David Anderson 5 years ago committed by Dave Anderson
parent 5d79530caa
commit 47da432991

@ -17,6 +17,7 @@ import (
"github.com/apenwarr/fixconsole" "github.com/apenwarr/fixconsole"
"github.com/pborman/getopt/v2" "github.com/pborman/getopt/v2"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/safesocket" "tailscale.com/safesocket"
@ -55,6 +56,7 @@ func main() {
nuroutes := getopt.BoolLong("no-single-routes", 'N', "disallow (non-subnet) routes to single nodes") nuroutes := getopt.BoolLong("no-single-routes", 'N', "disallow (non-subnet) routes to single nodes")
routeall := getopt.BoolLong("remote-routes", 'R', "accept routes advertised by remote nodes") routeall := getopt.BoolLong("remote-routes", 'R', "accept routes advertised by remote nodes")
nopf := getopt.BoolLong("no-packet-filter", 'F', "disable packet filter") nopf := getopt.BoolLong("no-packet-filter", 'F', "disable packet filter")
advroutes := getopt.ListLong("routes", 'r', "routes to advertise to other nodes (comma-separated, e.g. 10.0.0.0/8,192.168.1.0/24)")
getopt.Parse() getopt.Parse()
pol := logpolicy.New("tailnode.log.tailscale.io", "tailscale") pol := logpolicy.New("tailnode.log.tailscale.io", "tailscale")
if len(getopt.Args()) > 0 { if len(getopt.Args()) > 0 {
@ -63,6 +65,15 @@ func main() {
defer pol.Close() defer pol.Close()
var adv []wgcfg.CIDR
for _, s := range *advroutes {
cidr, err := wgcfg.ParseCIDR(s)
if err != nil {
log.Fatalf("%q is not a valid CIDR prefix: %v", s, err)
}
adv = append(adv, *cidr)
}
// TODO(apenwarr): fix different semantics between prefs and uflags // TODO(apenwarr): fix different semantics between prefs and uflags
// TODO(apenwarr): allow setting/using CorpDNS // TODO(apenwarr): allow setting/using CorpDNS
prefs := ipn.Prefs{ prefs := ipn.Prefs{
@ -70,6 +81,7 @@ func main() {
RouteAll: *routeall, RouteAll: *routeall,
AllowSingleHosts: !*nuroutes, AllowSingleHosts: !*nuroutes,
UsePacketFilter: !*nopf, UsePacketFilter: !*nopf,
AdvertiseRoutes: adv,
} }
c, err := safesocket.Connect("", "Tailscale", "tailscaled", 41112) c, err := safesocket.Connect("", "Tailscale", "tailscaled", 41112)

@ -156,6 +156,8 @@ func (b *LocalBackend) Start(opts Options) error {
return fmt.Errorf("loading requested state: %v", err) return fmt.Errorf("loading requested state: %v", err)
} }
hi.RoutableIPs = append(hi.RoutableIPs, b.prefs.AdvertiseRoutes...)
b.notify = opts.Notify b.notify = opts.Notify
b.netMapCache = nil b.netMapCache = nil
b.mu.Unlock() b.mu.Unlock()
@ -502,8 +504,17 @@ func (b *LocalBackend) SetPrefs(new Prefs) {
b.logf("Failed to save new controlclient state: %v", err) b.logf("Failed to save new controlclient state: %v", err)
} }
} }
oldHi := b.hiCache
newHi := oldHi.Copy()
newHi.RoutableIPs = append([]wgcfg.CIDR(nil), b.prefs.AdvertiseRoutes...)
b.hiCache = *newHi
cli := b.c
b.mu.Unlock() b.mu.Unlock()
if cli != nil && !oldHi.Equal(newHi) {
cli.SetHostinfo(*newHi)
}
if old.WantRunning != new.WantRunning { if old.WantRunning != new.WantRunning {
b.stateMachine() b.stateMachine()
} else { } else {

@ -5,15 +5,14 @@
package ipn package ipn
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"os" "os"
"path/filepath" "path/filepath"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/atomicfile" "tailscale.com/atomicfile"
"tailscale.com/control/controlclient" "tailscale.com/control/controlclient"
) )
@ -44,7 +43,7 @@ type Prefs struct {
UsePacketFilter bool UsePacketFilter bool
// AdvertiseRoutes specifies CIDR prefixes to advertise into the // AdvertiseRoutes specifies CIDR prefixes to advertise into the
// Tailscale network as reachable through the current node. // Tailscale network as reachable through the current node.
AdvertiseRoutes []*net.IPNet AdvertiseRoutes []wgcfg.CIDR
// NotepadURLs is a debugging setting that opens OAuth URLs in // NotepadURLs is a debugging setting that opens OAuth URLs in
// notepad.exe on Windows, rather than loading them in a browser. // notepad.exe on Windows, rather than loading them in a browser.
@ -102,12 +101,12 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.Persist.Equals(p2.Persist) p.Persist.Equals(p2.Persist)
} }
func compareIPNets(a, b []*net.IPNet) bool { func compareIPNets(a, b []wgcfg.CIDR) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false
} }
for i := range a { for i := range a {
if !a[i].IP.Equal(b[i].IP) || !bytes.Equal(a[i].Mask, b[i].Mask) { if !a[i].IP.Equal(&b[i].IP) || a[i].Mask != b[i].Mask {
return false return false
} }
} }

@ -5,10 +5,10 @@
package ipn package ipn
import ( import (
"net"
"reflect" "reflect"
"testing" "testing"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/control/controlclient" "tailscale.com/control/controlclient"
) )
@ -26,13 +26,13 @@ func TestPrefsEqual(t *testing.T) {
have, prefsHandles) have, prefsHandles)
} }
nets := func(strs ...string) (ns []*net.IPNet) { nets := func(strs ...string) (ns []wgcfg.CIDR) {
for _, s := range strs { for _, s := range strs {
_, n, err := net.ParseCIDR(s) n, err := wgcfg.ParseCIDR(s)
if err != nil { if err != nil {
panic(err) panic(err)
} }
ns = append(ns, n) ns = append(ns, *n)
} }
return ns return ns
} }
@ -124,12 +124,12 @@ func TestPrefsEqual(t *testing.T) {
{ {
&Prefs{AdvertiseRoutes: nil}, &Prefs{AdvertiseRoutes: nil},
&Prefs{AdvertiseRoutes: []*net.IPNet{}}, &Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
true, true,
}, },
{ {
&Prefs{AdvertiseRoutes: []*net.IPNet{}}, &Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
&Prefs{AdvertiseRoutes: []*net.IPNet{}}, &Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
true, true,
}, },
{ {

@ -220,7 +220,7 @@ type Hostinfo struct {
Services []Service `json:",omitempty"` // services advertised by this machine Services []Service `json:",omitempty"` // services advertised by this machine
// NOTE: any new fields containing pointers in this type // NOTE: any new fields containing pointers in this type
// require changes to Hostinfo.Copy. // require changes to Hostinfo.Copy and Hostinfo.Equal.
} }
// Copy makes a deep copy of Hostinfo. // Copy makes a deep copy of Hostinfo.
@ -234,6 +234,11 @@ func (hinfo *Hostinfo) Copy() (res *Hostinfo) {
return res return res
} }
// Equal reports whether h and h2 are equal.
func (h *Hostinfo) Equal(h2 *Hostinfo) bool {
return reflect.DeepEqual(h, h2)
}
type RegisterRequest struct { type RegisterRequest struct {
Version int Version int
NodeKey NodeKey NodeKey NodeKey

@ -19,6 +19,143 @@ func fieldsOf(t reflect.Type) (fields []string) {
return return
} }
func TestHostinfoEqual(t *testing.T) {
hiHandles := []string{"IPNVersion", "FrontendLogID", "BackendLogID", "OS", "Hostname", "RoutableIPs", "Services"}
if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) {
t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
have, hiHandles)
}
nets := func(strs ...string) (ns []wgcfg.CIDR) {
for _, s := range strs {
n, err := wgcfg.ParseCIDR(s)
if err != nil {
panic(err)
}
ns = append(ns, *n)
}
return ns
}
tests := []struct {
a, b *Hostinfo
want bool
}{
{
nil,
nil,
true,
},
{
&Hostinfo{},
nil,
false,
},
{
nil,
&Hostinfo{},
false,
},
{
&Hostinfo{},
&Hostinfo{},
true,
},
{
&Hostinfo{IPNVersion: "1"},
&Hostinfo{IPNVersion: "2"},
false,
},
{
&Hostinfo{IPNVersion: "2"},
&Hostinfo{IPNVersion: "2"},
true,
},
{
&Hostinfo{FrontendLogID: "1"},
&Hostinfo{FrontendLogID: "2"},
false,
},
{
&Hostinfo{FrontendLogID: "2"},
&Hostinfo{FrontendLogID: "2"},
true,
},
{
&Hostinfo{BackendLogID: "1"},
&Hostinfo{BackendLogID: "2"},
false,
},
{
&Hostinfo{BackendLogID: "2"},
&Hostinfo{BackendLogID: "2"},
true,
},
{
&Hostinfo{OS: "windows"},
&Hostinfo{OS: "linux"},
false,
},
{
&Hostinfo{OS: "windows"},
&Hostinfo{OS: "windows"},
true,
},
{
&Hostinfo{Hostname: "vega"},
&Hostinfo{Hostname: "iris"},
false,
},
{
&Hostinfo{Hostname: "vega"},
&Hostinfo{Hostname: "vega"},
true,
},
{
&Hostinfo{RoutableIPs: nil},
&Hostinfo{RoutableIPs: nets("10.0.0.0/16")},
false,
},
{
&Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
&Hostinfo{RoutableIPs: nets("10.2.0.0/16", "192.168.2.0/24")},
false,
},
{
&Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
&Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.2.0/24")},
false,
},
{
&Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
&Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
true,
},
{
&Hostinfo{Services: []Service{Service{TCP, 1234, "foo"}}},
&Hostinfo{Services: []Service{Service{UDP, 2345, "bar"}}},
false,
},
{
&Hostinfo{Services: []Service{Service{TCP, 1234, "foo"}}},
&Hostinfo{Services: []Service{Service{TCP, 1234, "foo"}}},
true,
},
}
for i, tt := range tests {
got := tt.a.Equal(tt.b)
if got != tt.want {
t.Errorf("%d. Equal = %v; want %v", i, got, tt.want)
}
}
}
func TestNodeEqual(t *testing.T) { func TestNodeEqual(t *testing.T) {
nodeHandles := []string{"ID", "Name", "User", "Key", "KeyExpiry", "Machine", "Addresses", "AllowedIPs", "Endpoints", "Hostinfo", "Created", "LastSeen", "MachineAuthorized"} nodeHandles := []string{"ID", "Name", "User", "Key", "KeyExpiry", "Machine", "Addresses", "AllowedIPs", "Endpoints", "Hostinfo", "Created", "LastSeen", "MachineAuthorized"}
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) { if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {

Loading…
Cancel
Save