wgengine/router: support multiple levels of netfilter involvement.
Signed-off-by: David Anderson <danderson@tailscale.com>pull/395/head
parent
cff53c6e6d
commit
292606a975
|
@ -54,10 +54,14 @@ func main() {
|
||||||
upf.BoolVar(&upArgs.acceptRoutes, "accept-routes", false, "accept routes advertised by other Tailscale nodes")
|
upf.BoolVar(&upArgs.acceptRoutes, "accept-routes", false, "accept routes advertised by other Tailscale nodes")
|
||||||
upf.BoolVar(&upArgs.noSingleRoutes, "no-single-routes", false, "don't install routes to single nodes")
|
upf.BoolVar(&upArgs.noSingleRoutes, "no-single-routes", false, "don't install routes to single nodes")
|
||||||
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
|
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
|
||||||
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. 10.0.0.0/8,192.168.0.0/24)")
|
|
||||||
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "ACL tags to request (comma-separated, e.g. eng,montreal,ssh)")
|
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "ACL tags to request (comma-separated, e.g. eng,montreal,ssh)")
|
||||||
upf.BoolVar(&upArgs.noSNAT, "no-snat", false, "disable SNAT of traffic to local routes advertised with -advertise-routes")
|
|
||||||
upf.StringVar(&upArgs.authKey, "authkey", "", "node authorization key")
|
upf.StringVar(&upArgs.authKey, "authkey", "", "node authorization key")
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. 10.0.0.0/8,192.168.0.0/24)")
|
||||||
|
upf.BoolVar(&upArgs.noSNAT, "no-snat", false, "disable SNAT of traffic to local routes advertised with -advertise-routes")
|
||||||
|
upf.BoolVar(&upArgs.noNetfilterCalls, "no-netfilter-calls", false, "don't call Tailscale netfilter chains from the main netfilter chains")
|
||||||
|
upf.BoolVar(&upArgs.noNetfilter, "no-netfilter", false, "disable all netfilter rule management")
|
||||||
|
}
|
||||||
upCmd := &ffcli.Command{
|
upCmd := &ffcli.Command{
|
||||||
Name: "up",
|
Name: "up",
|
||||||
ShortUsage: "up [flags]",
|
ShortUsage: "up [flags]",
|
||||||
|
@ -107,6 +111,8 @@ var upArgs struct {
|
||||||
advertiseRoutes string
|
advertiseRoutes string
|
||||||
advertiseTags string
|
advertiseTags string
|
||||||
noSNAT bool
|
noSNAT bool
|
||||||
|
noNetfilterCalls bool
|
||||||
|
noNetfilter bool
|
||||||
authKey string
|
authKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +200,8 @@ func runUp(ctx context.Context, args []string) error {
|
||||||
prefs.AdvertiseRoutes = routes
|
prefs.AdvertiseRoutes = routes
|
||||||
prefs.AdvertiseTags = tags
|
prefs.AdvertiseTags = tags
|
||||||
prefs.NoSNAT = upArgs.noSNAT
|
prefs.NoSNAT = upArgs.noSNAT
|
||||||
|
prefs.NoNetfilter = upArgs.noNetfilter
|
||||||
|
prefs.NoNetfilterCalls = upArgs.noNetfilterCalls
|
||||||
|
|
||||||
c, bc, ctx, cancel := connect(ctx)
|
c, bc, ctx, cancel := connect(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
10
ipn/local.go
10
ipn/local.go
|
@ -738,7 +738,15 @@ func routerConfig(cfg *wgcfg.Config, prefs *Prefs, dnsDomains []string) *router.
|
||||||
DNS: wgIPToNetaddr(cfg.DNS),
|
DNS: wgIPToNetaddr(cfg.DNS),
|
||||||
DNSDomains: dnsDomains,
|
DNSDomains: dnsDomains,
|
||||||
SubnetRoutes: wgCIDRToNetaddr(prefs.AdvertiseRoutes),
|
SubnetRoutes: wgCIDRToNetaddr(prefs.AdvertiseRoutes),
|
||||||
NoSNAT: prefs.NoSNAT,
|
SNATSubnetRoutes: !prefs.NoSNAT,
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case prefs.NoNetfilter:
|
||||||
|
rs.NetfilterMode = router.NetfilterOff
|
||||||
|
case prefs.NoNetfilterCalls:
|
||||||
|
rs.NetfilterMode = router.NetfilterNoDivert
|
||||||
|
default:
|
||||||
|
rs.NetfilterMode = router.NetfilterOn
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peer := range cfg.Peers {
|
for _, peer := range cfg.Peers {
|
||||||
|
|
46
ipn/prefs.go
46
ipn/prefs.go
|
@ -44,24 +44,12 @@ type Prefs struct {
|
||||||
// use the packet filter as provided. If true, we block incoming
|
// use the packet filter as provided. If true, we block incoming
|
||||||
// connections.
|
// connections.
|
||||||
ShieldsUp bool
|
ShieldsUp bool
|
||||||
// AdvertiseRoutes specifies CIDR prefixes to advertise into the
|
|
||||||
// Tailscale network as reachable through the current node.
|
|
||||||
AdvertiseRoutes []wgcfg.CIDR
|
|
||||||
// AdvertiseTags specifies groups that this node wants to join, for
|
// AdvertiseTags specifies groups that this node wants to join, for
|
||||||
// purposes of ACL enforcement. These can be referenced from the ACL
|
// purposes of ACL enforcement. These can be referenced from the ACL
|
||||||
// security policy. Note that advertising a tag doesn't guarantee that
|
// security policy. Note that advertising a tag doesn't guarantee that
|
||||||
// the control server will allow you to take on the rights for that
|
// the control server will allow you to take on the rights for that
|
||||||
// tag.
|
// tag.
|
||||||
AdvertiseTags []string
|
AdvertiseTags []string
|
||||||
// NoSNAT specifies whether to source NAT traffic going to
|
|
||||||
// destinations in AdvertiseRoutes. The default is to apply source
|
|
||||||
// NAT, which makes the traffic appear to come from the router
|
|
||||||
// machine rather than the peer's Tailscale IP.
|
|
||||||
//
|
|
||||||
// Disabling SNAT requires additional manual configuration in your
|
|
||||||
// network to route Tailscale traffic back to the subnet relay
|
|
||||||
// machine.
|
|
||||||
NoSNAT bool
|
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -74,6 +62,34 @@ type Prefs struct {
|
||||||
// DisableDERP prevents DERP from being used.
|
// DisableDERP prevents DERP from being used.
|
||||||
DisableDERP bool
|
DisableDERP bool
|
||||||
|
|
||||||
|
// The following block of options only have an effect on Linux.
|
||||||
|
|
||||||
|
// AdvertiseRoutes specifies CIDR prefixes to advertise into the
|
||||||
|
// Tailscale network as reachable through the current
|
||||||
|
// node.
|
||||||
|
AdvertiseRoutes []wgcfg.CIDR
|
||||||
|
// NoSNAT specifies whether to source NAT traffic going to
|
||||||
|
// destinations in AdvertiseRoutes. The default is to apply source
|
||||||
|
// NAT, which makes the traffic appear to come from the router
|
||||||
|
// machine rather than the peer's Tailscale IP.
|
||||||
|
//
|
||||||
|
// Disabling SNAT requires additional manual configuration in your
|
||||||
|
// network to route Tailscale traffic back to the subnet relay
|
||||||
|
// machine.
|
||||||
|
//
|
||||||
|
// Linux-only.
|
||||||
|
NoSNAT bool
|
||||||
|
// NoNetfilter, if set, disables all management of firewall rules
|
||||||
|
// for Tailscale traffic. The resulting configuration is not
|
||||||
|
// secure, and it is the user's responsibility to correct that.
|
||||||
|
NoNetfilter bool
|
||||||
|
// NoNetfilterDivert, if set, disables calling Tailscale netfilter
|
||||||
|
// chains from the main netfilter chains, but still manages the
|
||||||
|
// contents of the Tailscale chains. The resulting configuration
|
||||||
|
// is not secure, and it is the user's responsibility to insert
|
||||||
|
// calls to Tailscale's chains at the right place.
|
||||||
|
NoNetfilterCalls bool
|
||||||
|
|
||||||
// The Persist field is named 'Config' in the file for backward
|
// The Persist field is named 'Config' in the file for backward
|
||||||
// compatibility with earlier versions.
|
// compatibility with earlier versions.
|
||||||
// TODO(apenwarr): We should move this out of here, it's not a pref.
|
// TODO(apenwarr): We should move this out of here, it's not a pref.
|
||||||
|
@ -92,9 +108,9 @@ func (p *Prefs) Pretty() string {
|
||||||
} else {
|
} else {
|
||||||
pp = "Persist=nil"
|
pp = "Persist=nil"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("Prefs{ra=%v mesh=%v dns=%v want=%v notepad=%v derp=%v shields=%v routes=%v snat=%v %v}",
|
return fmt.Sprintf("Prefs{ra=%v mesh=%v dns=%v want=%v notepad=%v derp=%v shields=%v routes=%v snat=%v nf=%v nfd=%v %v}",
|
||||||
p.RouteAll, p.AllowSingleHosts, p.CorpDNS, p.WantRunning,
|
p.RouteAll, p.AllowSingleHosts, p.CorpDNS, p.WantRunning,
|
||||||
p.NotepadURLs, !p.DisableDERP, p.ShieldsUp, p.AdvertiseRoutes, !p.NoSNAT, pp)
|
p.NotepadURLs, !p.DisableDERP, p.ShieldsUp, p.AdvertiseRoutes, !p.NoSNAT, !p.NoNetfilter, !p.NoNetfilterCalls, pp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Prefs) ToBytes() []byte {
|
func (p *Prefs) ToBytes() []byte {
|
||||||
|
@ -123,6 +139,8 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
|
||||||
p.DisableDERP == p2.DisableDERP &&
|
p.DisableDERP == p2.DisableDERP &&
|
||||||
p.ShieldsUp == p2.ShieldsUp &&
|
p.ShieldsUp == p2.ShieldsUp &&
|
||||||
p.NoSNAT == p2.NoSNAT &&
|
p.NoSNAT == p2.NoSNAT &&
|
||||||
|
p.NoNetfilter == p2.NoNetfilter &&
|
||||||
|
p.NoNetfilterCalls == p2.NoNetfilterCalls &&
|
||||||
compareIPNets(p.AdvertiseRoutes, p2.AdvertiseRoutes) &&
|
compareIPNets(p.AdvertiseRoutes, p2.AdvertiseRoutes) &&
|
||||||
compareStrings(p.AdvertiseTags, p2.AdvertiseTags) &&
|
compareStrings(p.AdvertiseTags, p2.AdvertiseTags) &&
|
||||||
p.Persist.Equals(p2.Persist)
|
p.Persist.Equals(p2.Persist)
|
||||||
|
|
|
@ -23,7 +23,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
|
||||||
func TestPrefsEqual(t *testing.T) {
|
func TestPrefsEqual(t *testing.T) {
|
||||||
tstest.PanicOnLog()
|
tstest.PanicOnLog()
|
||||||
|
|
||||||
prefsHandles := []string{"ControlURL", "RouteAll", "AllowSingleHosts", "CorpDNS", "WantRunning", "ShieldsUp", "AdvertiseRoutes", "AdvertiseTags", "NoSNAT", "NotepadURLs", "DisableDERP", "Persist"}
|
prefsHandles := []string{"ControlURL", "RouteAll", "AllowSingleHosts", "CorpDNS", "WantRunning", "ShieldsUp", "AdvertiseTags", "NotepadURLs", "DisableDERP", "AdvertiseRoutes", "NoSNAT", "NoNetfilter", "NoNetfilterCalls", "Persist"}
|
||||||
if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) {
|
if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) {
|
||||||
t.Errorf("Prefs.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
t.Errorf("Prefs.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
||||||
have, prefsHandles)
|
have, prefsHandles)
|
||||||
|
@ -173,6 +173,28 @@ func TestPrefsEqual(t *testing.T) {
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
&Prefs{NoNetfilter: true},
|
||||||
|
&Prefs{NoNetfilter: false},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&Prefs{NoNetfilter: true},
|
||||||
|
&Prefs{NoNetfilter: true},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
&Prefs{NoNetfilterCalls: true},
|
||||||
|
&Prefs{NoNetfilterCalls: false},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&Prefs{NoNetfilterCalls: true},
|
||||||
|
&Prefs{NoNetfilterCalls: true},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
&Prefs{Persist: &controlclient.Persist{}},
|
&Prefs{Persist: &controlclient.Persist{}},
|
||||||
&Prefs{Persist: &controlclient.Persist{LoginName: "dave"}},
|
&Prefs{Persist: &controlclient.Persist{LoginName: "dave"}},
|
||||||
|
|
|
@ -35,6 +35,14 @@ func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, err
|
||||||
return newUserspaceRouter(logf, wgdev, tundev)
|
return newUserspaceRouter(logf, wgdev, tundev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NetfilterMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NetfilterOff NetfilterMode = iota // remove all tailscale netfilter state
|
||||||
|
NetfilterNoDivert // manage tailscale chains, but don't call them
|
||||||
|
NetfilterOn // manage tailscale chains and call them from main chains
|
||||||
|
)
|
||||||
|
|
||||||
// Config is the subset of Tailscale configuration that is relevant to
|
// Config is the subset of Tailscale configuration that is relevant to
|
||||||
// the OS's network stack.
|
// the OS's network stack.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -42,15 +50,15 @@ type Config struct {
|
||||||
DNS []netaddr.IP
|
DNS []netaddr.IP
|
||||||
DNSDomains []string
|
DNSDomains []string
|
||||||
Routes []netaddr.IPPrefix // routes to point into the Tailscale interface
|
Routes []netaddr.IPPrefix // routes to point into the Tailscale interface
|
||||||
|
|
||||||
|
// Linux-only things below, ignored on other platforms.
|
||||||
|
|
||||||
SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes
|
SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes
|
||||||
NoSNAT bool // don't SNAT traffic to local subnets
|
SNATSubnetRoutes bool // SNAT traffic to local subnets
|
||||||
|
NetfilterMode NetfilterMode // how much to manage netfilter rules
|
||||||
}
|
}
|
||||||
|
|
||||||
// shutdownConfig is a routing configuration that removes all router
|
// shutdownConfig is a routing configuration that removes all router
|
||||||
// state from the OS. It's the config used when callers pass in a nil
|
// state from the OS. It's the config used when callers pass in a nil
|
||||||
// Config.
|
// Config.
|
||||||
var shutdownConfig = Config{
|
var shutdownConfig = Config{}
|
||||||
// TODO(danderson): set more things in here to disable all
|
|
||||||
// firewall rules and routing overrides when nil.
|
|
||||||
NoSNAT: true,
|
|
||||||
}
|
|
||||||
|
|
|
@ -48,13 +48,19 @@ const (
|
||||||
tailscaleBypassMark = "0x20000/0x20000"
|
tailscaleBypassMark = "0x20000/0x20000"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// chromeOSVMRange is the subset of the CGNAT IPv4 range used by
|
||||||
|
// ChromeOS to interconnect the host OS to containers and VMs. We
|
||||||
|
// avoid allocating Tailscale IPs from it, to avoid conflicts.
|
||||||
|
const chromeOSVMRange = "100.115.92.0/23"
|
||||||
|
|
||||||
type linuxRouter struct {
|
type linuxRouter struct {
|
||||||
logf func(fmt string, args ...interface{})
|
logf func(fmt string, args ...interface{})
|
||||||
tunname string
|
tunname string
|
||||||
addrs map[netaddr.IPPrefix]bool
|
addrs map[netaddr.IPPrefix]bool
|
||||||
routes map[netaddr.IPPrefix]bool
|
routes map[netaddr.IPPrefix]bool
|
||||||
subnetRoutes map[netaddr.IPPrefix]bool
|
subnetRoutes map[netaddr.IPPrefix]bool
|
||||||
noSNAT bool
|
snatSubnetRoutes bool
|
||||||
|
netfilterMode NetfilterMode
|
||||||
|
|
||||||
ipt4 *iptables.IPTables
|
ipt4 *iptables.IPTables
|
||||||
}
|
}
|
||||||
|
@ -73,7 +79,7 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
|
||||||
return &linuxRouter{
|
return &linuxRouter{
|
||||||
logf: logf,
|
logf: logf,
|
||||||
tunname: tunname,
|
tunname: tunname,
|
||||||
noSNAT: true,
|
netfilterMode: NetfilterOff,
|
||||||
ipt4: ipt4,
|
ipt4: ipt4,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -92,12 +98,16 @@ func cmd(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *linuxRouter) Up() error {
|
func (r *linuxRouter) Up() error {
|
||||||
if err := r.deleteLegacyNetfilter(); err != nil {
|
if err := r.delLegacyNetfilter(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := r.addBaseNetfilter4(); err != nil {
|
if err := r.delNetfilterHooks(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := r.delNetfilterBase(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.addBypassRule(); err != nil {
|
if err := r.addBypassRule(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -115,7 +125,10 @@ func (r *linuxRouter) down() error {
|
||||||
if err := r.delBypassRule(); err != nil {
|
if err := r.delBypassRule(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := r.delNetfilter4(); err != nil {
|
if err := r.delNetfilterHooks(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.delNetfilterBase(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,84 +159,129 @@ func (r *linuxRouter) Set(cfg *Config) error {
|
||||||
cfg = &shutdownConfig
|
cfg = &shutdownConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// cidrDiff calls add and del as needed to make the set of prefixes in
|
if err := r.setNetfilterMode(cfg.NetfilterMode); err != nil {
|
||||||
// old and new match. Returns a map version of new, and the first
|
return err
|
||||||
// error encountered while reconfiguring, if any.
|
|
||||||
cidrDiff := func(kind string, old map[netaddr.IPPrefix]bool, new []netaddr.IPPrefix, add, del func(netaddr.IPPrefix) error) (map[netaddr.IPPrefix]bool, error) {
|
|
||||||
var (
|
|
||||||
ret = make(map[netaddr.IPPrefix]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
|
newAddrs, err := cidrDiff("addr", r.addrs, cfg.LocalAddrs, r.addAddress, r.delAddress, r.logf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
r.addrs = newAddrs
|
||||||
|
|
||||||
var errq error
|
newRoutes, err := cidrDiff("route", r.routes, cfg.Routes, r.addRoute, r.delRoute, r.logf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.routes = newRoutes
|
||||||
|
|
||||||
newAddrs, err := cidrDiff("addr", r.addrs, cfg.LocalAddrs, r.addAddress, r.delAddress)
|
newSubnetRoutes, err := cidrDiff("subnet rule", r.subnetRoutes, cfg.SubnetRoutes, r.addSubnetRule, r.delSubnetRule, r.logf)
|
||||||
if err != nil && errq == nil {
|
if err != nil {
|
||||||
errq = err
|
return err
|
||||||
}
|
|
||||||
newRoutes, err := cidrDiff("route", r.routes, cfg.Routes, r.addRoute, r.delRoute)
|
|
||||||
if err != nil && errq == nil {
|
|
||||||
errq = err
|
|
||||||
}
|
|
||||||
newSubnetRoutes, err := cidrDiff("subnet rule", r.subnetRoutes, cfg.SubnetRoutes, r.addSubnetRule, r.delSubnetRule)
|
|
||||||
if err != nil && errq == nil {
|
|
||||||
errq = err
|
|
||||||
}
|
}
|
||||||
|
r.subnetRoutes = newSubnetRoutes
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case cfg.NoSNAT == r.noSNAT:
|
case cfg.SNATSubnetRoutes == r.snatSubnetRoutes:
|
||||||
// state already correct, nothing to do.
|
// state already correct, nothing to do.
|
||||||
case cfg.NoSNAT:
|
case cfg.SNATSubnetRoutes:
|
||||||
if err := r.delSNATRule(); err != nil && errq == nil {
|
if err := r.addSNATRule(); err != nil {
|
||||||
errq = err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if err := r.addSNATRule(); err != nil && errq == nil {
|
if err := r.delSNATRule(); err != nil {
|
||||||
errq = err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
r.snatSubnetRoutes = cfg.SNATSubnetRoutes
|
||||||
r.addrs = newAddrs
|
|
||||||
r.routes = newRoutes
|
|
||||||
r.subnetRoutes = newSubnetRoutes
|
|
||||||
r.noSNAT = cfg.NoSNAT
|
|
||||||
|
|
||||||
// TODO: this:
|
// TODO: this:
|
||||||
if false {
|
if false {
|
||||||
if err := r.replaceResolvConf(cfg.DNS, cfg.DNSDomains); err != nil {
|
if err := r.replaceResolvConf(cfg.DNS, cfg.DNSDomains); err != nil {
|
||||||
errq = fmt.Errorf("replacing resolv.conf failed: %v", err)
|
return fmt.Errorf("replacing resolv.conf failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errq
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setNetfilterMode switches the router to the given netfilter
|
||||||
|
// mode. Netfilter state is created or deleted appropriately to
|
||||||
|
// reflect the new mode, and r.snatSubnetRoutes is updated to reflect
|
||||||
|
// the current state of subnet SNATing.
|
||||||
|
func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error {
|
||||||
|
if r.netfilterMode == mode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depending on the netfilter mode we switch from and to, we may
|
||||||
|
// have created the Tailscale netfilter chains. If so, we have to
|
||||||
|
// go back through existing router state, and add the netfilter
|
||||||
|
// rules for that state.
|
||||||
|
//
|
||||||
|
// This bool keeps track of whether the current state transition
|
||||||
|
// is one that requires adding rules of existing state.
|
||||||
|
reprocess := false
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case NetfilterOff:
|
||||||
|
if err := r.delNetfilterHooks(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.delNetfilterBase(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.snatSubnetRoutes = false
|
||||||
|
case NetfilterNoDivert:
|
||||||
|
switch r.netfilterMode {
|
||||||
|
case NetfilterOff:
|
||||||
|
reprocess = true
|
||||||
|
if err := r.addNetfilterBase(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.snatSubnetRoutes = false
|
||||||
|
case NetfilterOn:
|
||||||
|
if err := r.delNetfilterHooks(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NetfilterOn:
|
||||||
|
switch r.netfilterMode {
|
||||||
|
case NetfilterOff:
|
||||||
|
reprocess = true
|
||||||
|
if err := r.addNetfilterBase(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.addNetfilterHooks(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.snatSubnetRoutes = false
|
||||||
|
case NetfilterNoDivert:
|
||||||
|
if err := r.addNetfilterHooks(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unhandled netfilter mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.netfilterMode = mode
|
||||||
|
|
||||||
|
if !reprocess {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for cidr := range r.addrs {
|
||||||
|
if err := r.addLoopbackRule(cidr.IP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for cidr := range r.subnetRoutes {
|
||||||
|
if err := r.addSubnetRule(cidr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -322,38 +380,54 @@ func (r *linuxRouter) restoreResolvConf() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addAddress adds an IP/mask to the tunnel interface, and firewall
|
// addAddress adds an IP/mask to the tunnel interface. Fails if the
|
||||||
// rules to permit loopback traffic. Fails if the address is already
|
// address is already assigned to the interface, or if the addition
|
||||||
// assigned to the interface, or if the addition fails.
|
// fails.
|
||||||
func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
|
func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
|
||||||
if err := cmd("ip", "addr", "add", addr.String(), "dev", r.tunname); err != nil {
|
if err := cmd("ip", "addr", "add", addr.String(), "dev", r.tunname); err != nil {
|
||||||
return err
|
return fmt.Errorf("adding address %q to tunnel interface: %v", addr, err)
|
||||||
}
|
}
|
||||||
if err := r.ipt4.Insert("filter", "ts-input", 1, "-i", "lo", "-s", addr.IP.String(), "-j", "ACCEPT"); err != nil {
|
if err := r.addLoopbackRule(addr.IP); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// delAddress removes an IP/mask from the tunnel interface, and
|
// delAddress removes an IP/mask from the tunnel interface. Fails if
|
||||||
// firewall rules permitting loopback traffic. Fails if the address is
|
// the address is not assigned to the interface, or if the removal
|
||||||
// not assigned to the interface, or if the removal fails.
|
// fails.
|
||||||
func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error {
|
func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error {
|
||||||
if err := r.ipt4.Delete("filter", "ts-input", "-i", "lo", "-s", addr.IP.String(), "-j", "ACCEPT"); err != nil {
|
if err := r.delLoopbackRule(addr.IP); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := cmd("ip", "addr", "del", addr.String(), "dev", r.tunname); err != nil {
|
if err := cmd("ip", "addr", "del", addr.String(), "dev", r.tunname); err != nil {
|
||||||
return err
|
return fmt.Errorf("deleting address %q from tunnel interface: %v", addr, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeCIDR returns cidr as an ip/mask string, with the host bits
|
// addLoopbackRule adds a firewall rule to permit loopback traffic to
|
||||||
// of the IP address zeroed out.
|
// a local Tailscale IP.
|
||||||
func normalizeCIDR(cidr netaddr.IPPrefix) string {
|
func (r *linuxRouter) addLoopbackRule(addr netaddr.IP) error {
|
||||||
ncidr := cidr.IPNet()
|
if r.netfilterMode == NetfilterOff {
|
||||||
nip := ncidr.IP.Mask(ncidr.Mask)
|
return nil
|
||||||
return fmt.Sprintf("%s/%d", nip, cidr.Bits)
|
}
|
||||||
|
if err := r.ipt4.Insert("filter", "ts-input", 1, "-i", "lo", "-s", addr.String(), "-j", "ACCEPT"); err != nil {
|
||||||
|
return fmt.Errorf("adding loopback allow rule for %q: %v", addr, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// delLoopbackRule removes the firewall rule permitting loopback
|
||||||
|
// traffic to a Tailscale IP.
|
||||||
|
func (r *linuxRouter) delLoopbackRule(addr netaddr.IP) error {
|
||||||
|
if r.netfilterMode == NetfilterOff {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := r.ipt4.Delete("filter", "ts-input", "-i", "lo", "-s", addr.String(), "-j", "ACCEPT"); err != nil {
|
||||||
|
return fmt.Errorf("deleting loopback allow rule for %q: %v", addr, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addRoute adds a route for cidr, pointing to the tunnel
|
// addRoute adds a route for cidr, pointing to the tunnel
|
||||||
|
@ -371,9 +445,12 @@ func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addSubnetRule adds a netfilter rule that allows traffic to flow
|
// addSubnetRule adds a netfilter rule that allows traffic to flow
|
||||||
// from Tailscale to cidr. Fails if the rule already exists, or if
|
// from Tailscale to cidr.
|
||||||
// adding the route fails.
|
|
||||||
func (r *linuxRouter) addSubnetRule(cidr netaddr.IPPrefix) error {
|
func (r *linuxRouter) addSubnetRule(cidr netaddr.IPPrefix) error {
|
||||||
|
if r.netfilterMode == NetfilterOff {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.ipt4.Insert("filter", "ts-forward", 1, "-i", r.tunname, "-d", normalizeCIDR(cidr), "-j", "MARK", "--set-mark", tailscaleSubnetRouteMark); err != nil {
|
if err := r.ipt4.Insert("filter", "ts-forward", 1, "-i", r.tunname, "-d", normalizeCIDR(cidr), "-j", "MARK", "--set-mark", tailscaleSubnetRouteMark); err != nil {
|
||||||
return fmt.Errorf("adding subnet mark rule for %q: %v", cidr, err)
|
return fmt.Errorf("adding subnet mark rule for %q: %v", cidr, err)
|
||||||
}
|
}
|
||||||
|
@ -384,9 +461,13 @@ func (r *linuxRouter) addSubnetRule(cidr netaddr.IPPrefix) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// delSubnetRule deletes the netfilter subnet forwarding rule for
|
// delSubnetRule deletes the netfilter subnet forwarding rule for
|
||||||
// cidr. Fails if the rule doesn't exist, or if removing the rule
|
// cidr. Fails if the rule doesn't exist, or if removing the route
|
||||||
// fails.
|
// fails.
|
||||||
func (r *linuxRouter) delSubnetRule(cidr netaddr.IPPrefix) error {
|
func (r *linuxRouter) delSubnetRule(cidr netaddr.IPPrefix) error {
|
||||||
|
if r.netfilterMode == NetfilterOff {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.ipt4.Delete("filter", "ts-forward", "-i", r.tunname, "-d", normalizeCIDR(cidr), "-j", "MARK", "--set-mark", tailscaleSubnetRouteMark); err != nil {
|
if err := r.ipt4.Delete("filter", "ts-forward", "-i", r.tunname, "-d", normalizeCIDR(cidr), "-j", "MARK", "--set-mark", tailscaleSubnetRouteMark); err != nil {
|
||||||
return fmt.Errorf("deleting subnet mark rule for %q: %v", cidr, err)
|
return fmt.Errorf("deleting subnet mark rule for %q: %v", cidr, err)
|
||||||
}
|
}
|
||||||
|
@ -445,133 +526,39 @@ func (r *linuxRouter) delBypassRule() error {
|
||||||
return cmd("ip", "rule", "del", "priority", "10000")
|
return cmd("ip", "rule", "del", "priority", "10000")
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteLegacyNetfilter removes the netfilter rules installed by
|
// addNetfilterBase adds custom Tailscale chains to netfilter, along
|
||||||
// older versions of Tailscale, if they exist.
|
// with some basic processing rules to be supplemented by later calls
|
||||||
func (r *linuxRouter) deleteLegacyNetfilter() error {
|
// to other helpers.
|
||||||
del := func(table, chain string, args ...string) error {
|
func (r *linuxRouter) addNetfilterBase() error {
|
||||||
exists, err := r.ipt4.Exists(table, chain, args...)
|
create := func(table, chain string) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("checking for %v in %s/%s: %v", args, table, chain, err)
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
if err := r.ipt4.Delete(table, chain, args...); err != nil {
|
|
||||||
return fmt.Errorf("deleting %v in %s/%s: %v", args, table, chain, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := del("filter", "FORWARD", "-m", "comment", "--comment", "tailscale", "-i", r.tunname, "-j", "ACCEPT"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := del("nat", "POSTROUTING", "-m", "comment", "--comment", "tailscale", "-o", "eth0", "-j", "MASQUERADE"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteNetfilter4 removes custom Tailscale chains and processing
|
|
||||||
// hooks from netfilter.
|
|
||||||
func (r *linuxRouter) delNetfilter4() error {
|
|
||||||
del := func(table, chain string) error {
|
|
||||||
tsChain := "ts-" + strings.ToLower(chain)
|
|
||||||
|
|
||||||
args := []string{"-j", tsChain}
|
|
||||||
exists, err := r.ipt4.Exists(table, chain, args...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("checking for %v in %s/%s: %v", args, table, chain, err)
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
if err := r.ipt4.Delete(table, chain, "-j", tsChain); err != nil {
|
|
||||||
return fmt.Errorf("deleting %v in %s/%s: %v", args, table, chain, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chains, err := r.ipt4.ListChains(table)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listing iptables chains: %v", err)
|
|
||||||
}
|
|
||||||
for _, chain := range chains {
|
|
||||||
if chain == tsChain {
|
|
||||||
if err := r.ipt4.DeleteChain(table, tsChain); err != nil {
|
|
||||||
return fmt.Errorf("deleting %s/%s: %v", table, tsChain, err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := del("filter", "INPUT"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := del("filter", "FORWARD"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := del("nat", "POSTROUTING"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// chromeOSVMRange is the subset of the CGNAT IPv4 range used by
|
|
||||||
// ChromeOS to interconnect the host OS to containers and VMs. We
|
|
||||||
// avoid allocating Tailscale IPs from it, to avoid conflicts.
|
|
||||||
const chromeOSVMRange = "100.115.92.0/23"
|
|
||||||
|
|
||||||
// addBaseNetfilter4 installs the basic IPv4 netfilter framework for
|
|
||||||
// Tailscale, in preparation for inserting more rules later.
|
|
||||||
func (r *linuxRouter) addBaseNetfilter4() error {
|
|
||||||
// Create our own filtering chains, and hook them into the head of
|
|
||||||
// various main tables. If the hooks already exist, we don't try
|
|
||||||
// to fight for first place, because other software does the
|
|
||||||
// same. We're happy with "someplace up before most other stuff".
|
|
||||||
divert := func(table, chain string) error {
|
|
||||||
tsChain := "ts-" + strings.ToLower(chain)
|
|
||||||
|
|
||||||
chains, err := r.ipt4.ListChains(table)
|
chains, err := r.ipt4.ListChains(table)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listing iptables chains: %v", err)
|
return fmt.Errorf("listing iptables chains: %v", err)
|
||||||
}
|
}
|
||||||
found := false
|
found := false
|
||||||
for _, chain := range chains {
|
for _, ch := range chains {
|
||||||
if chain == tsChain {
|
if ch == chain {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
err = r.ipt4.ClearChain(table, tsChain)
|
err = r.ipt4.ClearChain(table, chain)
|
||||||
} else {
|
} else {
|
||||||
err = r.ipt4.NewChain(table, tsChain)
|
err = r.ipt4.NewChain(table, chain)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setting up %s/%s: %v", table, tsChain, err)
|
return fmt.Errorf("setting up %s/%s: %v", table, chain, err)
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{"-j", tsChain}
|
|
||||||
exists, err := r.ipt4.Exists(table, chain, args...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("checking for %v in %s/%s: %v", args, table, chain, err)
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := r.ipt4.Insert(table, chain, 1, args...); err != nil {
|
|
||||||
return fmt.Errorf("adding %v in %s/%s: %v", args, table, chain, err)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := divert("filter", "INPUT"); err != nil {
|
if err := create("filter", "ts-input"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := divert("filter", "FORWARD"); err != nil {
|
if err := create("filter", "ts-forward"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := divert("nat", "POSTROUTING"); err != nil {
|
if err := create("nat", "ts-postrouting"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,9 +601,127 @@ func (r *linuxRouter) addBaseNetfilter4() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delNetfilterBase removes custom Tailscale chains from netfilter.
|
||||||
|
func (r *linuxRouter) delNetfilterBase() error {
|
||||||
|
del := func(table, chain string) error {
|
||||||
|
chains, err := r.ipt4.ListChains(table)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listing iptables chains: %v", err)
|
||||||
|
}
|
||||||
|
for _, ch := range chains {
|
||||||
|
if ch == chain {
|
||||||
|
if err := r.ipt4.ClearChain(table, chain); err != nil {
|
||||||
|
return fmt.Errorf("flushing %s/%s: %v", table, chain, err)
|
||||||
|
}
|
||||||
|
if err := r.ipt4.DeleteChain(table, chain); err != nil {
|
||||||
|
return fmt.Errorf("deleting %s/%s: %v", table, chain, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := del("filter", "ts-input"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := del("filter", "ts-forward"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := del("nat", "ts-postrouting"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNetfilterHooks inserts calls to tailscale's netfilter chains in
|
||||||
|
// the relevant main netfilter chains. The tailscale chains must
|
||||||
|
// already exist.
|
||||||
|
func (r *linuxRouter) addNetfilterHooks() error {
|
||||||
|
divert := func(table, chain string) error {
|
||||||
|
tsChain := tsChain(chain)
|
||||||
|
|
||||||
|
chains, err := r.ipt4.ListChains(table)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listing iptables chains: %v", err)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, chain := range chains {
|
||||||
|
if chain == tsChain {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("chain %q does not exist, cannot divert to it", tsChain)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"-j", tsChain}
|
||||||
|
exists, err := r.ipt4.Exists(table, chain, args...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("checking for %v in %s/%s: %v", args, table, chain, err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := r.ipt4.Insert(table, chain, 1, args...); err != nil {
|
||||||
|
return fmt.Errorf("adding %v in %s/%s: %v", args, table, chain, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := divert("filter", "INPUT"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := divert("filter", "FORWARD"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := divert("nat", "POSTROUTING"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// delNetfilterHooks deletes the calls to tailscale's netfilter chains
|
||||||
|
// in the relevant main netfilter chains.
|
||||||
|
func (r *linuxRouter) delNetfilterHooks() error {
|
||||||
|
del := func(table, chain string) error {
|
||||||
|
tsChain := tsChain(chain)
|
||||||
|
|
||||||
|
args := []string{"-j", tsChain}
|
||||||
|
exists, err := r.ipt4.Exists(table, chain, args...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("checking for %v in %s/%s: %v", args, table, chain, err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := r.ipt4.Delete(table, chain, args...); err != nil {
|
||||||
|
return fmt.Errorf("deleting %v in %s/%s: %v", args, table, chain, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := del("filter", "INPUT"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := del("filter", "FORWARD"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := del("nat", "POSTROUTING"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// addSNATRule adds a netfilter rule to SNAT traffic destined for
|
// addSNATRule adds a netfilter rule to SNAT traffic destined for
|
||||||
// local subnets.
|
// local subnets.
|
||||||
func (r *linuxRouter) addSNATRule() error {
|
func (r *linuxRouter) addSNATRule() error {
|
||||||
|
if r.netfilterMode == NetfilterOff {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
||||||
if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil {
|
if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in nat/ts-postrouting: %v", args, err)
|
return fmt.Errorf("adding %v in nat/ts-postrouting: %v", args, err)
|
||||||
|
@ -627,9 +732,93 @@ func (r *linuxRouter) addSNATRule() error {
|
||||||
// delSNATRule removes the netfilter rule to SNAT traffic destined for
|
// delSNATRule removes the netfilter rule to SNAT traffic destined for
|
||||||
// local subnets. Fails if the rule does not exist.
|
// local subnets. Fails if the rule does not exist.
|
||||||
func (r *linuxRouter) delSNATRule() error {
|
func (r *linuxRouter) delSNATRule() error {
|
||||||
|
if r.netfilterMode == NetfilterOff {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
||||||
if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil {
|
if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil {
|
||||||
return fmt.Errorf("deleting %v in nat/ts-postrouting: %v", args, err)
|
return fmt.Errorf("deleting %v in nat/ts-postrouting: %v", args, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *linuxRouter) delLegacyNetfilter() error {
|
||||||
|
del := func(table, chain string, args ...string) error {
|
||||||
|
exists, err := r.ipt4.Exists(table, chain, args...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("checking for %v in %s/%s: %v", args, table, chain, err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
if err := r.ipt4.Delete(table, chain, args...); err != nil {
|
||||||
|
return fmt.Errorf("deleting %v in %s/%s: %v", args, table, chain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := del("filter", "FORWARD", "-m", "comment", "--comment", "tailscale", "-i", r.tunname, "-j", "ACCEPT"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := del("nat", "POSTROUTING", "-m", "comment", "--comment", "tailscale", "-o", "eth0", "-j", "MASQUERADE"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cidrDiff calls add and del as needed to make the set of prefixes in
|
||||||
|
// old and new match. Returns a map reflecting the actual new state
|
||||||
|
// (which may be somewhere in between old and new if some commands
|
||||||
|
// failed), and any error encountered while reconfiguring.
|
||||||
|
func cidrDiff(kind string, old map[netaddr.IPPrefix]bool, new []netaddr.IPPrefix, add, del func(netaddr.IPPrefix) error, logf logger.Logf) (map[netaddr.IPPrefix]bool, error) {
|
||||||
|
newMap := make(map[netaddr.IPPrefix]bool, len(new))
|
||||||
|
for _, cidr := range new {
|
||||||
|
newMap[cidr] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ret starts out as a copy of old, and updates as we
|
||||||
|
// add/delete. That way we can always return it and have it be the
|
||||||
|
// true state of what we've done so far.
|
||||||
|
ret := make(map[netaddr.IPPrefix]bool, len(old))
|
||||||
|
for cidr := range old {
|
||||||
|
ret[cidr] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for cidr := range old {
|
||||||
|
if newMap[cidr] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := del(cidr); err != nil {
|
||||||
|
logf("%s del failed: %v", kind, err)
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
delete(ret, cidr)
|
||||||
|
}
|
||||||
|
for cidr := range newMap {
|
||||||
|
if old[cidr] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := add(cidr); err != nil {
|
||||||
|
logf("%s add failed: %v", kind, err)
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
ret[cidr] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tsChain returns the name of the tailscale sub-chain corresponding
|
||||||
|
// to the given "parent" chain (e.g. INPUT, FORWARD, ...).
|
||||||
|
func tsChain(chain string) string {
|
||||||
|
return "ts-" + strings.ToLower(chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeCIDR returns cidr as an ip/mask string, with the host bits
|
||||||
|
// of the IP address zeroed out.
|
||||||
|
func normalizeCIDR(cidr netaddr.IPPrefix) string {
|
||||||
|
ncidr := cidr.IPNet()
|
||||||
|
nip := ncidr.IP.Mask(ncidr.Mask)
|
||||||
|
return fmt.Sprintf("%s/%d", nip, cidr.Bits)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue