wgengine/router: add a setting to disable SNAT for subnet routes.
Part of #320. Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
@@ -43,4 +43,5 @@ type Settings struct {
|
||||
DNSDomains []string
|
||||
Routes []netaddr.IPPrefix // routes to point into the Tailscale interface
|
||||
SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes
|
||||
NoSNAT bool // don't SNAT traffic to local subnets
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ type linuxRouter struct {
|
||||
addrs map[netaddr.IPPrefix]bool
|
||||
routes map[netaddr.IPPrefix]bool
|
||||
subnetRoutes map[netaddr.IPPrefix]bool
|
||||
noSNAT bool
|
||||
|
||||
ipt4 *iptables.IPTables
|
||||
}
|
||||
@@ -72,6 +73,7 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
|
||||
return &linuxRouter{
|
||||
logf: logf,
|
||||
tunname: tunname,
|
||||
noSNAT: true,
|
||||
ipt4: ipt4,
|
||||
}, nil
|
||||
}
|
||||
@@ -193,9 +195,23 @@ func (r *linuxRouter) Set(rs Settings) error {
|
||||
errq = err
|
||||
}
|
||||
|
||||
switch {
|
||||
case rs.NoSNAT == r.noSNAT:
|
||||
// state already correct, nothing to do.
|
||||
case rs.NoSNAT:
|
||||
if err := r.delSNATRule(); err != nil && errq == nil {
|
||||
errq = err
|
||||
}
|
||||
default:
|
||||
if err := r.addSNATRule(); err != nil && errq == nil {
|
||||
errq = err
|
||||
}
|
||||
}
|
||||
|
||||
r.addrs = newAddrs
|
||||
r.routes = newRoutes
|
||||
r.subnetRoutes = newSubnetRoutes
|
||||
r.noSNAT = rs.NoSNAT
|
||||
|
||||
// TODO: this:
|
||||
if false {
|
||||
@@ -558,13 +574,12 @@ func (r *linuxRouter) addBaseNetfilter4() error {
|
||||
return fmt.Errorf("adding %v in filter/ts-input: %v", args, err)
|
||||
}
|
||||
|
||||
// Forward and masquerade packets that have the Tailscale subnet
|
||||
// route bit set. The bit gets set by rules inserted into
|
||||
// filter/FORWARD later on. We use packet marks here so both
|
||||
// filter/FORWARD and nat/POSTROUTING can match on these packets
|
||||
// of interest.
|
||||
// Forward and mark packets that have the Tailscale subnet route
|
||||
// bit set. The bit gets set by rules inserted into filter/FORWARD
|
||||
// later on. We use packet marks here so both filter/FORWARD and
|
||||
// nat/POSTROUTING can match on these packets of interest.
|
||||
//
|
||||
// In particular, we only want to apply masquerading in
|
||||
// In particular, we only want to apply SNAT rules in
|
||||
// nat/POSTROUTING to packets that originated from the Tailscale
|
||||
// interface, but we can't match on the inbound interface in
|
||||
// POSTROUTING. So instead, we match on the inbound interface and
|
||||
@@ -579,11 +594,26 @@ func (r *linuxRouter) addBaseNetfilter4() error {
|
||||
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
||||
return fmt.Errorf("adding %v in filter/ts-forward: %v", args, err)
|
||||
}
|
||||
// TODO(danderson): this should be optional.
|
||||
args = []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
||||
if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil {
|
||||
return fmt.Errorf("adding %v in nat/ts-postrouting: %v", args, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addSNATRule adds a netfilter rule to SNAT traffic destined for
|
||||
// local subnets.
|
||||
func (r *linuxRouter) addSNATRule() error {
|
||||
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
||||
if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil {
|
||||
return fmt.Errorf("adding %v in nat/ts-postrouting: %v", args, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// delSNATRule removes the netfilter rule to SNAT traffic destined for
|
||||
// local subnets. Fails if the rule does not exist.
|
||||
func (r *linuxRouter) delSNATRule() error {
|
||||
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
||||
if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil {
|
||||
return fmt.Errorf("deleting %v in nat/ts-postrouting: %v", args, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -242,6 +242,8 @@ func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
|
||||
e.wgdev.Close()
|
||||
return nil, err
|
||||
}
|
||||
// TODO(danderson): we should delete this. It's pointless to apply
|
||||
// a no-op settings here.
|
||||
if err := e.router.Set(router.Settings{}); err != nil {
|
||||
e.wgdev.Close()
|
||||
return nil, err
|
||||
@@ -325,14 +327,14 @@ func (e *userspaceEngine) pinger(peerKey wgcfg.Key, ips []wgcfg.IP) {
|
||||
}
|
||||
}
|
||||
|
||||
func configSignature(cfg *wgcfg.Config, dnsDomains []string, localRoutes []wgcfg.CIDR) (string, error) {
|
||||
func configSignature(cfg *wgcfg.Config, dnsDomains []string, localRoutes []wgcfg.CIDR, noSNAT bool) (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
|
||||
return fmt.Sprintf("%s %v %v %v", uapi, dnsDomains, localRoutes, noSNAT), nil
|
||||
}
|
||||
|
||||
// TODO(apenwarr): dnsDomains really ought to be in wgcfg.Config.
|
||||
@@ -344,7 +346,7 @@ func configSignature(cfg *wgcfg.Config, dnsDomains []string, localRoutes []wgcfg
|
||||
// hand. Feels like we either need a wgengine.Config type, or make
|
||||
// router and wgengine siblings of each other that interact via glue
|
||||
// in ipn.
|
||||
func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, localRoutes []wgcfg.CIDR) error {
|
||||
func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, localRoutes []wgcfg.CIDR, noSNAT bool) error {
|
||||
e.wgLock.Lock()
|
||||
defer e.wgLock.Unlock()
|
||||
|
||||
@@ -357,7 +359,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, local
|
||||
}
|
||||
e.mu.Unlock()
|
||||
|
||||
rc, err := configSignature(cfg, dnsDomains, localRoutes)
|
||||
rc, err := configSignature(cfg, dnsDomains, localRoutes, noSNAT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -403,6 +405,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, local
|
||||
DNS: wgIPToNetaddr(cfg.DNS),
|
||||
DNSDomains: dnsDomains,
|
||||
SubnetRoutes: wgCIDRToNetaddr(localRoutes),
|
||||
NoSNAT: noSNAT,
|
||||
}
|
||||
for _, peer := range cfg.Peers {
|
||||
rs.Routes = append(rs.Routes, wgCIDRToNetaddr(peer.AllowedIPs)...)
|
||||
|
||||
@@ -61,8 +61,8 @@ func (e *watchdogEngine) watchdog(name string, fn func()) {
|
||||
})
|
||||
}
|
||||
|
||||
func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, localRoutes []wgcfg.CIDR) error {
|
||||
return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, dnsDomains, localRoutes) })
|
||||
func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string, localRoutes []wgcfg.CIDR, noSNAT bool) error {
|
||||
return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, dnsDomains, localRoutes, noSNAT) })
|
||||
}
|
||||
func (e *watchdogEngine) GetFilter() *filter.Filter {
|
||||
var x *filter.Filter
|
||||
|
||||
@@ -59,7 +59,7 @@ type Engine interface {
|
||||
// sends an updated network map.
|
||||
//
|
||||
// The returned error is ErrNoChanges if no changes were made.
|
||||
Reconfig(cfg *wgcfg.Config, dnsDomains []string, localSubnets []wgcfg.CIDR) error
|
||||
Reconfig(cfg *wgcfg.Config, dnsDomains []string, localSubnets []wgcfg.CIDR, noSNAT bool) error
|
||||
|
||||
// GetFilter returns the current packet filter, if any.
|
||||
GetFilter() *filter.Filter
|
||||
|
||||
Reference in New Issue
Block a user