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>
This commit is contained in:
David Anderson 2020-05-08 01:07:13 +00:00 committed by Dave Anderson
parent 8861bb5a19
commit b8f01eed34
10 changed files with 132 additions and 189 deletions

View File

@ -20,7 +20,6 @@
winipcfg "github.com/tailscale/winipcfg-go"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"tailscale.com/wgengine/winnet"
@ -237,7 +236,7 @@ func setFirewall(ifcGUID *windows.GUID) (bool, error) {
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
guid := tun.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{}
var firstGateway4 *net.IP
var firstGateway6 *net.IP
addresses := make([]*net.IPNet, len(m.Addresses))
for i, addr := range m.Addresses {
addresses := make([]*net.IPNet, len(rs.LocalAddrs))
for i, addr := range rs.LocalAddrs {
ipnet := addr.IPNet()
addresses[i] = ipnet
gateway := ipnet.IP
@ -282,48 +281,46 @@ func configureInterface(m *wgcfg.Config, tun *tun.NativeTun, dns []wgcfg.IP, dns
foundDefault4 := false
foundDefault6 := false
for _, peer := range m.Peers {
for _, allowedip := range peer.AllowedIPs {
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")
}
ipn := allowedip.IPNet()
var gateway net.IP
if allowedip.IP.Is4() {
gateway = *firstGateway4
} else if allowedip.IP.Is6() {
gateway = *firstGateway6
}
r := winipcfg.RouteData{
Destination: net.IPNet{
IP: ipn.IP.Mask(ipn.Mask),
Mask: ipn.Mask,
},
NextHop: gateway,
Metric: 0,
}
if bytes.Compare(r.Destination.IP, gateway) == 0 {
// no need to add a route for the interface's
// own IP. The kernel does that for us.
// If we try to replace it, we'll fail to
// add the route unless NextHop is set, but
// then the interface's IP won't be pingable.
continue
}
if allowedip.IP.Is4() {
if allowedip.Mask == 0 {
foundDefault4 = true
}
r.NextHop = *firstGateway4
} else if allowedip.IP.Is6() {
if allowedip.Mask == 0 {
foundDefault6 = true
}
r.NextHop = *firstGateway6
}
routes = append(routes, r)
for _, route := range rs.Routes {
if (route.IP.Is4() && firstGateway4 == nil) || (route.IP.Is6() && firstGateway6 == nil) {
return errors.New("Due to a Windows limitation, one cannot have interface routes without an interface address")
}
ipn := route.IPNet()
var gateway net.IP
if route.IP.Is4() {
gateway = *firstGateway4
} else if route.IP.Is6() {
gateway = *firstGateway6
}
r := winipcfg.RouteData{
Destination: net.IPNet{
IP: ipn.IP.Mask(ipn.Mask),
Mask: ipn.Mask,
},
NextHop: gateway,
Metric: 0,
}
if bytes.Compare(r.Destination.IP, gateway) == 0 {
// no need to add a route for the interface's
// own IP. The kernel does that for us.
// If we try to replace it, we'll fail to
// add the route unless NextHop is set, but
// then the interface's IP won't be pingable.
continue
}
if route.IP.Is4() {
if route.Mask == 0 {
foundDefault4 = true
}
r.NextHop = *firstGateway4
} else if route.IP.Is6() {
if route.Mask == 0 {
foundDefault6 = true
}
r.NextHop = *firstGateway6
}
routes = append(routes, r)
}
err = iface.SyncAddresses(addresses)
@ -362,7 +359,7 @@ func configureInterface(m *wgcfg.Config, tun *tun.NativeTun, dns []wgcfg.IP, dns
}
var dnsIPs []net.IP
for _, ip := range dns {
for _, ip := range rs.DNS {
dnsIPs = append(dnsIPs, ip.IP())
}
err = iface.SetDNS(dnsIPs)

View File

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

View File

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

View File

@ -6,7 +6,7 @@
package router
// SetRoutesFunc applies the given route settings to the OS network
// SetRoutesFunc applies the given router settings to the OS network
// stack.
//
// This is logically part of the router_darwin.go implementation, and
@ -22,4 +22,4 @@
// 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
// both the darwin and linux builds. Hence this file and build tag.
var SetRoutesFunc func(rs RouteSettings) error
var SetRoutesFunc func(rs Settings) error

View File

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

View File

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

View File

@ -138,90 +138,59 @@ func (r *linuxRouter) Close() error {
return ret
}
func (r *linuxRouter) SetRoutes(rs RouteSettings) error {
// Set implements the Router interface.
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
)
for _, cidr := range new {
ret[cidr] = true
}
for cidr := range old {
if ret[cidr] {
continue
}
if err := del(cidr); err != nil {
r.logf("%s del failed: %v", kind, err)
if errq == nil {
errq = err
}
}
}
for cidr := range ret {
if old[cidr] {
continue
}
if err := add(cidr); err != nil {
r.logf("%s add failed: %v", kind, err)
if errq == nil {
errq = err
}
}
}
return ret, errq
}
var errq error
newAddrs := make(map[wgcfg.CIDR]bool)
for _, addr := range rs.LocalAddrs {
newAddrs[addr] = true
newAddrs, err := cidrDiff("addr", r.addrs, rs.LocalAddrs, r.addAddress, r.delAddress)
if err != nil && errq == nil {
errq = err
}
for addr := range r.addrs {
if newAddrs[addr] {
continue
}
if err := r.delAddress(addr); err != nil {
r.logf("addr del failed: %v", err)
if errq == nil {
errq = err
}
}
newRoutes, err := cidrDiff("route", r.routes, rs.Routes, r.addRoute, r.delRoute)
if err != nil && errq == nil {
errq = err
}
for addr := range newAddrs {
if r.addrs[addr] {
continue
}
if err := r.addAddress(addr); err != nil {
r.logf("addr add failed: %v", err)
if errq == nil {
errq = err
}
}
}
newRoutes := make(map[wgcfg.CIDR]bool)
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{}
for _, route := range rs.SubnetRoutes {
newSubnetRoutes[route] = true
}
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
}
}
}
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
}
}
newSubnetRoutes, err := cidrDiff("subnet rule", r.subnetRoutes, rs.SubnetRoutes, r.addSubnetRule, r.delSubnetRule)
if err != nil && errq == nil {
errq = err
}
r.addrs = newAddrs

View File

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

View File

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

View File

@ -58,7 +58,6 @@ type userspaceEngine struct {
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
lastReconfig string
lastCfg wgcfg.Config
lastRoutes string
mu sync.Mutex // guards following; see lock order comment below
filt *filter.Filter
@ -241,7 +240,7 @@ func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
e.wgdev.Close()
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()
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.
// 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
@ -346,13 +355,10 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, local
}
e.mu.Unlock()
// TODO(apenwarr): get rid of uapi stuff for in-process comms
uapi, err := cfg.ToUAPI()
rc, err := configSignature(cfg, dnsDomains, localRoutes)
if err != nil {
return err
}
rc := uapi + "\x00" + strings.Join(dnsDomains, "\x00")
if rc == e.lastReconfig {
return ErrNoChanges
}
@ -390,30 +396,18 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, local
})
}
rs := router.RouteSettings{
rs := router.Settings{
LocalAddrs: addrs,
Cfg: cfg,
DNS: cfg.DNS,
DNSDomains: dnsDomains,
SubnetRoutes: localRoutes,
}
for _, peer := range cfg.Peers {
rs.Routes = append(rs.Routes, peer.AllowedIPs...)
}
// TODO(apenwarr): all the parts of RouteSettings should be "relevant."
// 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
}
if err := e.router.Set(rs); err != nil {
return err
}
e.logf("wgengine: Reconfig done")