From 7ed3681cbeb95b381a1dd83291b4c9abdc52987a Mon Sep 17 00:00:00 2001 From: KevinLiang10 Date: Tue, 15 Aug 2023 21:52:04 +0000 Subject: [PATCH] tailcfg: Add FirewallMode to NetInfo to record wether host using iptables or nftables To record wether user is using iptables or nftables after we add support to nftables on linux, we are adding a field FirewallMode to NetInfo in HostInfo to reflect what firewall mode the host is running, and form metrics. The information is gained from a global constant in hostinfo.go. We set it when selection heuristic made the decision, and magicsock reports this to control. Updates: tailscale/corp#13943 Signed-off-by: KevinLiang10 --- hostinfo/hostinfo.go | 11 +++++++++++ tailcfg/tailcfg.go | 12 +++++++++--- tailcfg/tailcfg_clone.go | 1 + tailcfg/tailcfg_test.go | 1 + tailcfg/tailcfg_view.go | 2 ++ wgengine/magicsock/magicsock.go | 1 + wgengine/router/router_linux.go | 8 ++++++++ 7 files changed, 33 insertions(+), 3 deletions(-) diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go index ebe0ed440..0ae72f295 100644 --- a/hostinfo/hostinfo.go +++ b/hostinfo/hostinfo.go @@ -171,6 +171,7 @@ var ( desktopAtomic atomic.Value // of opt.Bool packagingType atomic.Value // of string appType atomic.Value // of string + firewallMode atomic.Value // of string ) // SetPushDeviceToken sets the device token for use in Hostinfo updates. @@ -182,6 +183,9 @@ func SetDeviceModel(model string) { deviceModelAtomic.Store(model) } // SetOSVersion sets the OS version. func SetOSVersion(v string) { osVersionAtomic.Store(v) } +// SetFirewallMode sets the firewall mode for the app. +func SetFirewallMode(v string) { firewallMode.Store(v) } + // SetPackage sets the packaging type for the app. // // As of 2022-03-25, this is used by Android ("nogoogle" for the @@ -203,6 +207,13 @@ func pushDeviceToken() string { return s } +// FirewallMode returns the firewall mode for the app. +// It is empty if unset. +func FirewallMode() string { + s, _ := firewallMode.Load().(string) + return s +} + func desktop() (ret opt.Bool) { if runtime.GOOS != "linux" { return opt.Bool("") diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 96d18e3de..7633cc17a 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -734,6 +734,11 @@ type NetInfo struct { // the control plane. DERPLatency map[string]float64 `json:",omitempty"` + // FirewallMode is the current firewall utility in use by router (iptables, nftables). + // FirewallMode ipt means iptables, nft means nftables. When it's empty user is not using + // our netfilter runners to manage firewall rules. + FirewallMode string `json:",omitempty"` + // Update BasicallyEqual when adding fields. } @@ -741,10 +746,10 @@ func (ni *NetInfo) String() string { if ni == nil { return "NetInfo(nil)" } - return fmt.Sprintf("NetInfo{varies=%v hairpin=%v ipv6=%v ipv6os=%v udp=%v icmpv4=%v derp=#%v portmap=%v link=%q}", + return fmt.Sprintf("NetInfo{varies=%v hairpin=%v ipv6=%v ipv6os=%v udp=%v icmpv4=%v derp=#%v portmap=%v link=%q firewallmode=%q}", ni.MappingVariesByDestIP, ni.HairPinning, ni.WorkingIPv6, ni.OSHasIPv6, ni.WorkingUDP, ni.WorkingICMPv4, - ni.PreferredDERP, ni.portMapSummary(), ni.LinkType) + ni.PreferredDERP, ni.portMapSummary(), ni.LinkType, ni.FirewallMode) } func (ni *NetInfo) portMapSummary() string { @@ -792,7 +797,8 @@ func (ni *NetInfo) BasicallyEqual(ni2 *NetInfo) bool { ni.PMP == ni2.PMP && ni.PCP == ni2.PCP && ni.PreferredDERP == ni2.PreferredDERP && - ni.LinkType == ni2.LinkType + ni.LinkType == ni2.LinkType && + ni.FirewallMode == ni2.FirewallMode } // Equal reports whether h and h2 are equal. diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index a2eee5bbd..4f1b49881 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -195,6 +195,7 @@ var _NetInfoCloneNeedsRegeneration = NetInfo(struct { PreferredDERP int LinkType string DERPLatency map[string]float64 + FirewallMode string }{}) // Clone makes a deep copy of Login. diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index e6f4c1e8d..77598982a 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -571,6 +571,7 @@ func TestNetInfoFields(t *testing.T) { "PreferredDERP", "LinkType", "DERPLatency", + "FirewallMode", } if have := fieldsOf(reflect.TypeOf(NetInfo{})); !reflect.DeepEqual(have, handled) { t.Errorf("NetInfo.Clone/BasicallyEqually check might be out of sync\nfields: %q\nhandled: %q\n", diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go index 60a86c8f6..5884a8a36 100644 --- a/tailcfg/tailcfg_view.go +++ b/tailcfg/tailcfg_view.go @@ -408,6 +408,7 @@ func (v NetInfoView) PreferredDERP() int { return v.ж.PreferredDER func (v NetInfoView) LinkType() string { return v.ж.LinkType } func (v NetInfoView) DERPLatency() views.Map[string, float64] { return views.MapOf(v.ж.DERPLatency) } +func (v NetInfoView) FirewallMode() string { return v.ж.FirewallMode } func (v NetInfoView) String() string { return v.ж.String() } // A compilation failure here means this code must be regenerated, with the command at the top of this file. @@ -425,6 +426,7 @@ var _NetInfoViewNeedsRegeneration = NetInfo(struct { PreferredDERP int LinkType string DERPLatency map[string]float64 + FirewallMode string }{}) // View returns a readonly view of Login. diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 3f63547ae..33f20154d 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -640,6 +640,7 @@ func (c *Conn) updateNetInfo(ctx context.Context) (*netcheck.Report, error) { if !c.setNearestDERP(ni.PreferredDERP) { ni.PreferredDERP = 0 } + ni.FirewallMode = hostinfo.FirewallMode() c.callNetInfoCallback(ni) return report, nil diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index 2317f2a9b..710e9cfe0 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -22,6 +22,7 @@ import ( "golang.org/x/sys/unix" "golang.org/x/time/rate" "tailscale.com/envknob" + "tailscale.com/hostinfo" "tailscale.com/net/netmon" "tailscale.com/types/logger" "tailscale.com/types/preftype" @@ -97,29 +98,36 @@ func chooseFireWallMode(logf logger.Logf, det tableDetector) linuxfw.FirewallMod case envknob.String("TS_DEBUG_FIREWALL_MODE") == "nftables": // TODO(KevinLiang10): Updates to a flag logf("router: envknob TS_DEBUG_FIREWALL_MODE=nftables set") + hostinfo.SetFirewallMode("nft-forced") return linuxfw.FirewallModeNfTables case envknob.String("TS_DEBUG_FIREWALL_MODE") == "iptables": logf("router: envknob TS_DEBUG_FIREWALL_MODE=iptables set") + hostinfo.SetFirewallMode("ipt-forced") return linuxfw.FirewallModeIPTables case nftRuleCount > 0 && iptRuleCount == 0: logf("router: nftables is currently in use") + hostinfo.SetFirewallMode("nft-inuse") return linuxfw.FirewallModeNfTables case iptRuleCount > 0 && nftRuleCount == 0: logf("router: iptables is currently in use") + hostinfo.SetFirewallMode("ipt-inuse") return linuxfw.FirewallModeIPTables case nftAva: // if both iptables and nftables are available but // neither/both are currently used, use nftables. logf("router: nftables is available") + hostinfo.SetFirewallMode("nft") return linuxfw.FirewallModeNfTables case iptAva: logf("router: iptables is available") + hostinfo.SetFirewallMode("ipt") return linuxfw.FirewallModeIPTables default: // if neither iptables nor nftables are available, use iptablesRunner as a dummy // runner which exists but won't do anything. Creating iptablesRunner errors only // if the iptables command is missing or doesn’t support "--version", as long as it // can determine a version then it’ll carry on. + hostinfo.SetFirewallMode("ipt-fb") return linuxfw.FirewallModeIPTables } }