wgengine/router: remove wireguard-go config from settings.

Instead, pass in only exactly the relevant configuration pieces
that the OS network stack cares about.

Signed-off-by: David Anderson <danderson@tailscale.com>
pull/362/head
David Anderson 5 years ago committed by Dave Anderson
parent 8861bb5a19
commit b8f01eed34

@ -20,7 +20,6 @@ import (
winipcfg "github.com/tailscale/winipcfg-go" winipcfg "github.com/tailscale/winipcfg-go"
"github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
"tailscale.com/wgengine/winnet" "tailscale.com/wgengine/winnet"
@ -237,7 +236,7 @@ func setFirewall(ifcGUID *windows.GUID) (bool, error) {
return false, nil return false, nil
} }
func configureInterface(m *wgcfg.Config, tun *tun.NativeTun, dns []wgcfg.IP, dnsDomains []string) error { func configureInterface(rs Settings, tun *tun.NativeTun) error {
const mtu = 0 const mtu = 0
guid := tun.GUID() guid := tun.GUID()
log.Printf("wintun GUID is %v\n", guid) log.Printf("wintun GUID is %v\n", guid)
@ -263,13 +262,13 @@ func configureInterface(m *wgcfg.Config, tun *tun.NativeTun, dns []wgcfg.IP, dns
} }
}() }()
setDNSDomains(guid, dnsDomains) setDNSDomains(guid, rs.DNSDomains)
routes := []winipcfg.RouteData{} routes := []winipcfg.RouteData{}
var firstGateway4 *net.IP var firstGateway4 *net.IP
var firstGateway6 *net.IP var firstGateway6 *net.IP
addresses := make([]*net.IPNet, len(m.Addresses)) addresses := make([]*net.IPNet, len(rs.LocalAddrs))
for i, addr := range m.Addresses { for i, addr := range rs.LocalAddrs {
ipnet := addr.IPNet() ipnet := addr.IPNet()
addresses[i] = ipnet addresses[i] = ipnet
gateway := ipnet.IP gateway := ipnet.IP
@ -282,17 +281,16 @@ func configureInterface(m *wgcfg.Config, tun *tun.NativeTun, dns []wgcfg.IP, dns
foundDefault4 := false foundDefault4 := false
foundDefault6 := false foundDefault6 := false
for _, peer := range m.Peers { for _, route := range rs.Routes {
for _, allowedip := range peer.AllowedIPs { if (route.IP.Is4() && firstGateway4 == nil) || (route.IP.Is6() && firstGateway6 == nil) {
if (allowedip.IP.Is4() && firstGateway4 == nil) || (allowedip.IP.Is6() && firstGateway6 == nil) {
return errors.New("Due to a Windows limitation, one cannot have interface routes without an interface address") return errors.New("Due to a Windows limitation, one cannot have interface routes without an interface address")
} }
ipn := allowedip.IPNet() ipn := route.IPNet()
var gateway net.IP var gateway net.IP
if allowedip.IP.Is4() { if route.IP.Is4() {
gateway = *firstGateway4 gateway = *firstGateway4
} else if allowedip.IP.Is6() { } else if route.IP.Is6() {
gateway = *firstGateway6 gateway = *firstGateway6
} }
r := winipcfg.RouteData{ r := winipcfg.RouteData{
@ -311,20 +309,19 @@ func configureInterface(m *wgcfg.Config, tun *tun.NativeTun, dns []wgcfg.IP, dns
// then the interface's IP won't be pingable. // then the interface's IP won't be pingable.
continue continue
} }
if allowedip.IP.Is4() { if route.IP.Is4() {
if allowedip.Mask == 0 { if route.Mask == 0 {
foundDefault4 = true foundDefault4 = true
} }
r.NextHop = *firstGateway4 r.NextHop = *firstGateway4
} else if allowedip.IP.Is6() { } else if route.IP.Is6() {
if allowedip.Mask == 0 { if route.Mask == 0 {
foundDefault6 = true foundDefault6 = true
} }
r.NextHop = *firstGateway6 r.NextHop = *firstGateway6
} }
routes = append(routes, r) routes = append(routes, r)
} }
}
err = iface.SyncAddresses(addresses) err = iface.SyncAddresses(addresses)
if err != nil { if err != nil {
@ -362,7 +359,7 @@ func configureInterface(m *wgcfg.Config, tun *tun.NativeTun, dns []wgcfg.IP, dns
} }
var dnsIPs []net.IP var dnsIPs []net.IP
for _, ip := range dns { for _, ip := range rs.DNS {
dnsIPs = append(dnsIPs, ip.IP()) dnsIPs = append(dnsIPs, ip.IP())
} }
err = iface.SetDNS(dnsIPs) err = iface.SetDNS(dnsIPs)

@ -7,8 +7,6 @@
package router package router
import ( import (
"fmt"
"github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"github.com/tailscale/wireguard-go/wgcfg" "github.com/tailscale/wireguard-go/wgcfg"
@ -22,10 +20,10 @@ type Router interface {
// Up brings the router up. // Up brings the router up.
Up() error Up() error
// SetRoutes is called regularly on network map updates. // Set updates the OS network stack with new settings. It may be
// It's how you kernel route table entries are populated for // called multiple times with identical Settings, which the
// each peer. // implementation should handle gracefully.
SetRoutes(RouteSettings) error Set(Settings) error
// Close closes the router. // Close closes the router.
Close() error Close() error
@ -37,23 +35,12 @@ func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, err
return newUserspaceRouter(logf, wgdev, tundev) return newUserspaceRouter(logf, wgdev, tundev)
} }
// RouteSettings is the full WireGuard config data (set of peers keys, // Settings is the subset of Tailscale configuration that is relevant
// IP, etc in wgcfg.Config) plus the things that WireGuard doesn't do // to the OS's network stack.
// itself, like DNS stuff. type Settings struct {
type RouteSettings struct {
LocalAddrs []wgcfg.CIDR LocalAddrs []wgcfg.CIDR
DNS []wgcfg.IP DNS []wgcfg.IP
DNSDomains []string DNSDomains []string
Routes []wgcfg.CIDR // routes to point into the Tailscale interface
SubnetRoutes []wgcfg.CIDR // subnets being advertised to other Tailscale nodes SubnetRoutes []wgcfg.CIDR // subnets being advertised to other Tailscale nodes
Cfg *wgcfg.Config
}
// OnlyRelevantParts returns a string minimally describing the route settings.
func (rs *RouteSettings) OnlyRelevantParts() string {
var peers [][]wgcfg.CIDR
for _, p := range rs.Cfg.Peers {
peers = append(peers, p.AllowedIPs)
}
return fmt.Sprintf("%v %v %v %v %v",
rs.LocalAddrs, rs.DNS, rs.DNSDomains, rs.SubnetRoutes, peers)
} }

@ -26,7 +26,7 @@ func (r *darwinRouter) Up() error {
return nil return nil
} }
func (r *darwinRouter) SetRoutes(rs RouteSettings) error { func (r *darwinRouter) Set(rs Settings) error {
if SetRoutesFunc != nil { if SetRoutesFunc != nil {
return SetRoutesFunc(rs) return SetRoutesFunc(rs)
} }

@ -6,7 +6,7 @@
package router package router
// SetRoutesFunc applies the given route settings to the OS network // SetRoutesFunc applies the given router settings to the OS network
// stack. // stack.
// //
// This is logically part of the router_darwin.go implementation, and // This is logically part of the router_darwin.go implementation, and
@ -22,4 +22,4 @@ package router
// as MacOS, so that we don't have to wait until the Mac CI to // as MacOS, so that we don't have to wait until the Mac CI to
// discover that we broke it. So this one definition needs to exist in // discover that we broke it. So this one definition needs to exist in
// both the darwin and linux builds. Hence this file and build tag. // both the darwin and linux builds. Hence this file and build tag.
var SetRoutesFunc func(rs RouteSettings) error var SetRoutesFunc func(rs Settings) error

@ -25,8 +25,8 @@ func (r fakeRouter) Up() error {
return nil return nil
} }
func (r fakeRouter) SetRoutes(rs RouteSettings) error { func (r fakeRouter) Set(rs Settings) error {
r.logf("Warning: fakeRouter.SetRoutes: not implemented.") r.logf("Warning: fakeRouter.Set: not implemented.")
return nil return nil
} }

@ -55,7 +55,7 @@ func (r *freebsdRouter) Up() error {
return nil return nil
} }
func (r *freebsdRouter) SetRoutes(rs RouteSettings) error { func (r *freebsdRouter) Set(rs Settings) error {
if len(rs.LocalAddrs) == 0 { if len(rs.LocalAddrs) == 0 {
return nil return nil
} }
@ -95,11 +95,9 @@ func (r *freebsdRouter) SetRoutes(rs RouteSettings) error {
} }
newRoutes := make(map[wgcfg.CIDR]struct{}) newRoutes := make(map[wgcfg.CIDR]struct{})
for _, peer := range rs.Cfg.Peers { for _, route := range rs.Routes {
for _, route := range peer.AllowedIPs {
newRoutes[route] = struct{}{} newRoutes[route] = struct{}{}
} }
}
// Delete any pre-existing routes. // Delete any pre-existing routes.
for route := range r.routes { for route := range r.routes {
if _, keep := newRoutes[route]; !keep { if _, keep := newRoutes[route]; !keep {

@ -138,90 +138,59 @@ func (r *linuxRouter) Close() error {
return ret return ret
} }
func (r *linuxRouter) SetRoutes(rs RouteSettings) error { // Set implements the Router interface.
var errq error func (r *linuxRouter) Set(rs Settings) error {
// cidrDiff calls add and del as needed to make the set of prefixes in
// old and new match. Returns a map version of new, and the first
// error encountered while reconfiguring, if any.
cidrDiff := func(kind string, old map[wgcfg.CIDR]bool, new []wgcfg.CIDR, add, del func(wgcfg.CIDR) error) (map[wgcfg.CIDR]bool, error) {
var (
ret = make(map[wgcfg.CIDR]bool, len(new))
errq error
)
newAddrs := make(map[wgcfg.CIDR]bool) for _, cidr := range new {
for _, addr := range rs.LocalAddrs { ret[cidr] = true
newAddrs[addr] = true
} }
for addr := range r.addrs { for cidr := range old {
if newAddrs[addr] { if ret[cidr] {
continue continue
} }
if err := r.delAddress(addr); err != nil { if err := del(cidr); err != nil {
r.logf("addr del failed: %v", err) r.logf("%s del failed: %v", kind, err)
if errq == nil { if errq == nil {
errq = err errq = err
} }
} }
} }
for addr := range newAddrs { for cidr := range ret {
if r.addrs[addr] { if old[cidr] {
continue continue
} }
if err := r.addAddress(addr); err != nil { if err := add(cidr); err != nil {
r.logf("addr add failed: %v", err) r.logf("%s add failed: %v", kind, err)
if errq == nil { if errq == nil {
errq = err errq = err
} }
} }
} }
newRoutes := make(map[wgcfg.CIDR]bool) return ret, errq
for _, peer := range rs.Cfg.Peers {
for _, route := range peer.AllowedIPs {
newRoutes[route] = true
}
}
for route := range r.routes {
if newRoutes[route] {
continue
}
if err := r.delRoute(route); err != nil {
r.logf("route del failed: %v", err)
if errq == nil {
errq = err
}
}
}
for route := range newRoutes {
if r.routes[route] {
continue
}
if err := r.addRoute(route); err != nil {
r.logf("route add failed: %v", err)
if errq == nil {
errq = err
}
}
} }
newSubnetRoutes := map[wgcfg.CIDR]bool{} var errq error
for _, route := range rs.SubnetRoutes {
newSubnetRoutes[route] = true newAddrs, err := cidrDiff("addr", r.addrs, rs.LocalAddrs, r.addAddress, r.delAddress)
} if err != nil && errq == nil {
for route := range r.subnetRoutes {
if newSubnetRoutes[route] {
continue
}
if err := r.delSubnetRule(route); err != nil {
r.logf("subnet rule del failed: %v", err)
if errq == nil {
errq = err errq = err
} }
} newRoutes, err := cidrDiff("route", r.routes, rs.Routes, r.addRoute, r.delRoute)
} if err != nil && errq == nil {
for route := range newSubnetRoutes {
if r.subnetRoutes[route] {
continue
}
if err := r.addSubnetRule(route); err != nil {
r.logf("subnet rule add failed: %v", err)
if errq == nil {
errq = err errq = err
} }
} newSubnetRoutes, err := cidrDiff("subnet rule", r.subnetRoutes, rs.SubnetRoutes, r.addSubnetRule, r.delSubnetRule)
if err != nil && errq == nil {
errq = err
} }
r.addrs = newAddrs r.addrs = newAddrs

@ -60,7 +60,7 @@ func (r *openbsdRouter) Up() error {
return nil return nil
} }
func (r *openbsdRouter) SetRoutes(rs RouteSettings) error { func (r *openbsdRouter) Set(rs Settings) error {
// TODO: support configuring multiple local addrs on interface. // TODO: support configuring multiple local addrs on interface.
if len(rs.LocalAddrs) != 1 { if len(rs.LocalAddrs) != 1 {
return errors.New("freebsd doesn't support setting multiple local addrs yet") return errors.New("freebsd doesn't support setting multiple local addrs yet")
@ -114,11 +114,9 @@ func (r *openbsdRouter) SetRoutes(rs RouteSettings) error {
} }
newRoutes := make(map[wgcfg.CIDR]struct{}) newRoutes := make(map[wgcfg.CIDR]struct{})
for _, peer := range rs.Cfg.Peers { for _, route := range rs.Routes {
for _, route := range peer.AllowedIPs {
newRoutes[route] = struct{}{} newRoutes[route] = struct{}{}
} }
}
for route := range r.routes { for route := range r.routes {
if _, keep := newRoutes[route]; !keep { if _, keep := newRoutes[route]; !keep {
net := route.IPNet() net := route.IPNet()

@ -45,8 +45,8 @@ func (r *winRouter) Up() error {
return nil return nil
} }
func (r *winRouter) SetRoutes(rs RouteSettings) error { func (r *winRouter) Set(rs Settings) error {
err := configureInterface(rs.Cfg, r.nativeTun, rs.DNS, rs.DNSDomains) err := configureInterface(rs, r.nativeTun)
if err != nil { if err != nil {
r.logf("ConfigureInterface: %v\n", err) r.logf("ConfigureInterface: %v\n", err)
return err return err

@ -58,7 +58,6 @@ type userspaceEngine struct {
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
lastReconfig string lastReconfig string
lastCfg wgcfg.Config lastCfg wgcfg.Config
lastRoutes string
mu sync.Mutex // guards following; see lock order comment below mu sync.Mutex // guards following; see lock order comment below
filt *filter.Filter filt *filter.Filter
@ -241,7 +240,7 @@ func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
e.wgdev.Close() e.wgdev.Close()
return nil, err return nil, err
} }
if err := e.router.SetRoutes(router.RouteSettings{Cfg: new(wgcfg.Config)}); err != nil { if err := e.router.Set(router.Settings{}); err != nil {
e.wgdev.Close() e.wgdev.Close()
return nil, err return nil, err
} }
@ -324,6 +323,16 @@ func (e *userspaceEngine) pinger(peerKey wgcfg.Key, ips []wgcfg.IP) {
} }
} }
func configSignature(cfg *wgcfg.Config, dnsDomains []string, localRoutes []wgcfg.CIDR) (string, error) {
// TODO(apenwarr): get rid of uapi stuff for in-process comms
uapi, err := cfg.ToUAPI()
if err != nil {
return "", err
}
return fmt.Sprintf("%s %v %v", uapi, dnsDomains, localRoutes), nil
}
// TODO(apenwarr): dnsDomains really ought to be in wgcfg.Config. // TODO(apenwarr): dnsDomains really ought to be in wgcfg.Config.
// However, we don't actually ever provide it to wireguard and it's not in // However, we don't actually ever provide it to wireguard and it's not in
// the traditional wireguard config format. On the other hand, wireguard // the traditional wireguard config format. On the other hand, wireguard
@ -346,13 +355,10 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, local
} }
e.mu.Unlock() e.mu.Unlock()
// TODO(apenwarr): get rid of uapi stuff for in-process comms rc, err := configSignature(cfg, dnsDomains, localRoutes)
uapi, err := cfg.ToUAPI()
if err != nil { if err != nil {
return err return err
} }
rc := uapi + "\x00" + strings.Join(dnsDomains, "\x00")
if rc == e.lastReconfig { if rc == e.lastReconfig {
return ErrNoChanges return ErrNoChanges
} }
@ -390,31 +396,19 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, local
}) })
} }
rs := router.RouteSettings{ rs := router.Settings{
LocalAddrs: addrs, LocalAddrs: addrs,
Cfg: cfg,
DNS: cfg.DNS, DNS: cfg.DNS,
DNSDomains: dnsDomains, DNSDomains: dnsDomains,
SubnetRoutes: localRoutes, SubnetRoutes: localRoutes,
} }
for _, peer := range cfg.Peers {
rs.Routes = append(rs.Routes, peer.AllowedIPs...)
}
// TODO(apenwarr): all the parts of RouteSettings should be "relevant." if err := e.router.Set(rs); err != nil {
// We're checking only the "relevant" parts to see if they have
// changed, and if not, skipping SetRoutes(). But if SetRoutes()
// is getting the non-relevant parts of Cfg, it might act on them,
// and this optimization is unsafe. Probably we should not pass
// a whole Cfg object as part of RouteSettings; instead, trim it to
// just what's absolutely needed (the set of actual routes).
rss := rs.OnlyRelevantParts()
if rss != e.lastRoutes {
e.logf("wgengine: Reconfig: reconfiguring router. la=%v dns=%v dom=%v; new routes: %v",
rs.LocalAddrs, rs.DNS, rs.DNSDomains, rss)
e.lastRoutes = rss
err = e.router.SetRoutes(rs)
if err != nil {
return err return err
} }
}
e.logf("wgengine: Reconfig done") e.logf("wgengine: Reconfig done")
return nil return nil

Loading…
Cancel
Save