linuxfw,wgengine/route,ipn: add c2n and nodeattrs to control linux netfilter

Updates tailscale/corp#14029.

Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
Naman Sood
2023-12-04 12:08:56 -05:00
parent 215f657a5e
commit 0a59754eda
15 changed files with 171 additions and 12 deletions

View File

@@ -55,6 +55,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
AutoUpdate AutoUpdatePrefs
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
Persist *persist.Persist
}{})

View File

@@ -90,6 +90,7 @@ func (v PrefsView) ProfileName() string { return v.ж.ProfileN
func (v PrefsView) AutoUpdate() AutoUpdatePrefs { return v.ж.AutoUpdate }
func (v PrefsView) AppConnector() AppConnectorPrefs { return v.ж.AppConnector }
func (v PrefsView) PostureChecking() bool { return v.ж.PostureChecking }
func (v PrefsView) NetfilterKind() string { return v.ж.NetfilterKind }
func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
@@ -119,6 +120,7 @@ var _PrefsViewNeedsRegeneration = Prefs(struct {
AutoUpdate AutoUpdatePrefs
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
Persist *persist.Persist
}{})

View File

@@ -69,6 +69,9 @@ var c2nHandlers = map[methodAndPath]c2nHandler{
// App Connectors.
req("GET /appconnector/routes"): handleC2NAppConnectorDomainRoutesGet,
// Linux netfilter.
req("POST /netfilter-kind"): handleC2NSetNetfilterKind,
}
type c2nHandler func(*LocalBackend, http.ResponseWriter, *http.Request)
@@ -222,6 +225,32 @@ func handleC2NAppConnectorDomainRoutesGet(b *LocalBackend, w http.ResponseWriter
json.NewEncoder(w).Encode(res)
}
func handleC2NSetNetfilterKind(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
b.logf("c2n: POST /netfilter-kind received")
if version.OS() != "linux" {
http.Error(w, "netfilter kind only settable on linux", http.StatusNotImplemented)
}
kind := r.FormValue("kind")
b.logf("c2n: switching netfilter to %s", kind)
_, err := b.EditPrefs(&ipn.MaskedPrefs{
NetfilterKindSet: true,
Prefs: ipn.Prefs{
NetfilterKind: kind,
},
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
b.authReconfig()
w.WriteHeader(http.StatusNoContent)
}
func handleC2NUpdateGet(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
b.logf("c2n: GET /update received")

View File

@@ -271,6 +271,9 @@ type LocalBackend struct {
currentUser ipnauth.WindowsToken
selfUpdateProgress []ipnstate.UpdateProgress
lastSelfUpdateState ipnstate.SelfUpdateStatus
// capForcedNetfilter is the netfilter that control instructs Linux clients
// to use, unless overridden locally.
capForcedNetfilter string
// ServeConfig fields. (also guarded by mu)
lastServeConfJSON mem.RO // last JSON that was parsed into serveConfig
@@ -3901,12 +3904,21 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs ipn.PrefsView, oneC
singleRouteThreshold = 1
}
netfilterKind := b.capForcedNetfilter
if prefs.NetfilterKind() != "" {
if b.capForcedNetfilter != "" {
b.logf("nodeattr netfilter preference %s overridden by c2n pref %s", b.capForcedNetfilter, prefs.NetfilterKind())
}
netfilterKind = prefs.NetfilterKind()
}
rs := &router.Config{
LocalAddrs: unmapIPPrefixes(cfg.Addresses),
SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes().AsSlice()),
SNATSubnetRoutes: !prefs.NoSNAT(),
NetfilterMode: prefs.NetfilterMode(),
Routes: peerRoutes(b.logf, cfg.Peers, singleRouteThreshold),
NetfilterKind: netfilterKind,
}
if distro.Get() == distro.Synology {
@@ -4416,6 +4428,14 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
}
b.capFileSharing = fs
if hasCapability(nm, tailcfg.NodeAttrLinuxMustUseIPTables) {
b.capForcedNetfilter = "iptables"
} else if hasCapability(nm, tailcfg.NodeAttrLinuxMustUseNfTables) {
b.capForcedNetfilter = "nftables"
} else {
b.capForcedNetfilter = "" // empty string means client can auto-detect
}
b.MagicConn().SetSilentDisco(b.ControlKnobs().SilentDisco.Load())
b.setDebugLogsByCapabilityLocked(nm)

View File

@@ -45,6 +45,8 @@ func IsLoginServerSynonym(val any) bool {
}
// Prefs are the user modifiable settings of the Tailscale node agent.
// When you add a Pref to this struct, remember to add a corresponding
// field in MaskedPrefs, and check your field for equality in Prefs.Equals().
type Prefs struct {
// ControlURL is the URL of the control server to use.
//
@@ -213,6 +215,11 @@ type Prefs struct {
// posture checks.
PostureChecking bool
// NetfilterKind specifies what netfilter implementation to use.
//
// Linux-only.
NetfilterKind string
// The Persist field is named 'Config' in the file for backward
// compatibility with earlier versions.
// TODO(apenwarr): We should move this out of here, it's not a pref.
@@ -241,6 +248,9 @@ type AppConnectorPrefs struct {
}
// MaskedPrefs is a Prefs with an associated bitmask of which fields are set.
// Make sure that the bool you add here maintains the same ordering of fields
// as the Prefs struct, because the ApplyEdits() function below relies on this
// ordering to be the same.
type MaskedPrefs struct {
Prefs
@@ -269,6 +279,7 @@ type MaskedPrefs struct {
AutoUpdateSet bool `json:",omitempty"`
AppConnectorSet bool `json:",omitempty"`
PostureCheckingSet bool `json:",omitempty"`
NetfilterKindSet bool `json:",omitempty"`
}
// ApplyEdits mutates p, assigning fields from m.Prefs for each MaskedPrefs
@@ -409,6 +420,9 @@ func (p *Prefs) pretty(goos string) string {
if p.OperatorUser != "" {
fmt.Fprintf(&sb, "op=%q ", p.OperatorUser)
}
if p.NetfilterKind != "" {
fmt.Fprintf(&sb, "netfilterKind=%s ", p.NetfilterKind)
}
sb.WriteString(p.AutoUpdate.Pretty())
sb.WriteString(p.AppConnector.Pretty())
if p.Persist != nil {
@@ -468,7 +482,8 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.ProfileName == p2.ProfileName &&
p.AutoUpdate == p2.AutoUpdate &&
p.AppConnector == p2.AppConnector &&
p.PostureChecking == p2.PostureChecking
p.PostureChecking == p2.PostureChecking &&
p.NetfilterKind == p2.NetfilterKind
}
func (au AutoUpdatePrefs) Pretty() string {

View File

@@ -60,6 +60,7 @@ func TestPrefsEqual(t *testing.T) {
"AutoUpdate",
"AppConnector",
"PostureChecking",
"NetfilterKind",
"Persist",
}
if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) {
@@ -327,6 +328,16 @@ func TestPrefsEqual(t *testing.T) {
&Prefs{PostureChecking: false},
false,
},
{
&Prefs{NetfilterKind: "iptables"},
&Prefs{NetfilterKind: "iptables"},
true,
},
{
&Prefs{NetfilterKind: "nftables"},
&Prefs{NetfilterKind: ""},
false,
},
}
for i, tt := range tests {
got := tt.a.Equals(tt.b)
@@ -545,6 +556,20 @@ func TestPrefsPretty(t *testing.T) {
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
},
{
Prefs{
NetfilterKind: "iptables",
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off netfilterKind=iptables update=off Persist=nil}`,
},
{
Prefs{
NetfilterKind: "",
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
},
}
for i, tt := range tests {
got := tt.p.pretty(tt.os)