summaryrefslogtreecommitdiffhomepage
path: root/wgengine/router/osrouter/router_linux.go
diff options
context:
space:
mode:
Diffstat (limited to 'wgengine/router/osrouter/router_linux.go')
-rw-r--r--wgengine/router/osrouter/router_linux.go56
1 files changed, 56 insertions, 0 deletions
diff --git a/wgengine/router/osrouter/router_linux.go b/wgengine/router/osrouter/router_linux.go
index 3c261c912..f44416c55 100644
--- a/wgengine/router/osrouter/router_linux.go
+++ b/wgengine/router/osrouter/router_linux.go
@@ -89,6 +89,7 @@ type linuxRouter struct {
connmarkEnabled bool // whether connmark rules are currently enabled
netfilterMode preftype.NetfilterMode
netfilterKind string
+ cgnatMode linuxfw.CGNATMode
magicsockPortV4 uint16
magicsockPortV6 uint16
}
@@ -521,9 +522,50 @@ func (r *linuxRouter) Set(cfg *router.Config) error {
r.enableIPForwarding()
}
+ // Remove the rule to drop off-tailnet CGNAT traffic, if asked.
+ if netfilterOn || cfg.NetfilterMode == netfilterNoDivert {
+ var cgnatMode linuxfw.CGNATMode
+ if cfg.RemoveCGNATDropRule {
+ cgnatMode = linuxfw.CGNATModeReturn
+ } else {
+ cgnatMode = linuxfw.CGNATModeDrop
+ }
+ err := r.setCGNATDropModeLocked(cgnatMode)
+ if err != nil {
+ errs = append(errs, fmt.Errorf("set cgnat mode: %w", err))
+ }
+ }
+
return errors.Join(errs...)
}
+// setCGNATDropModeLocked clears old rules and add new rules for the desired
+// behavior for incoming non-Tailscale CGNAT packets.
+// [linuxRouter.mu] must be held.
+func (r *linuxRouter) setCGNATDropModeLocked(want linuxfw.CGNATMode) error {
+ if want == r.cgnatMode {
+ return nil
+ }
+ // r.cgnatMode is empty at initial startup, before this function has been
+ // called for the first time. In that case, we can skip deleting old
+ // rules, because there aren't any.
+ if r.cgnatMode != "" {
+ err := r.nfr.DelExternalCGNATRules(r.cgnatMode, r.tunname)
+ if err != nil {
+ return fmt.Errorf("clear old cgnat rules: %w", err)
+ }
+ }
+ err := r.nfr.AddExternalCGNATRules(want, r.tunname)
+ if err != nil {
+ // We currently have no rules set, so change the state to reflect that
+ // so we might try again on a future Router update.
+ r.cgnatMode = ""
+ return fmt.Errorf("add new cgnat rules: %w", err)
+ }
+ r.cgnatMode = want
+ return nil
+}
+
var dockerStatefulFilteringWarnable = health.Register(&health.Warnable{
Code: "docker-stateful-filtering",
Title: "Docker with stateful filtering",
@@ -772,6 +814,20 @@ func (r *linuxRouter) setNetfilterModeLocked(mode preftype.NetfilterMode) error
}
}
+ // Re-add the CGNAT rules if we had any set.
+ // This does not call [linuxRouter.setCGNATDropModeLocked] because that
+ // function assumes that [linuxRouter.cgnatMode] accurately represents the
+ // current state in the firewall. This would not be true when we hit this
+ // code path, and is what we're fixing up here.
+ if r.cgnatMode != "" {
+ if err := r.nfr.AddExternalCGNATRules(r.cgnatMode, r.tunname); err != nil {
+ // We currently have no rules set, so change the state to reflect that
+ // so we might try again on a future Router update.
+ r.cgnatMode = ""
+ return fmt.Errorf("add cgnat rules: %w", err)
+ }
+ }
+
return nil
}