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 } }