util/linuxfw, wgengine: allow ingress to magicsock UDP port on Linux (#10370)

* util/linuxfw, wgengine: allow ingress to magicsock UDP port on Linux

Updates #9084.

Currently, we have to tell users to manually open UDP ports on Linux when
certain firewalls (like ufw) are enabled. This change automates the process of
adding and updating those firewall rules as magicsock changes what port it
listens on.

Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
Naman Sood
2023-12-05 18:12:02 -05:00
committed by GitHub
parent aad5fb28b1
commit d46a4eced5
11 changed files with 385 additions and 2 deletions

View File

@@ -27,6 +27,14 @@ type Router interface {
// implementation should handle gracefully.
Set(*Config) error
// UpdateMagicsockPort tells the OS network stack what port magicsock
// is currently listening on, so it can be threaded through firewalls
// and such. This is distinct from Set() since magicsock may rebind
// ports independently from the Config changing.
//
// network should be either "udp4" or "udp6".
UpdateMagicsockPort(port uint16, network string) error
// Close closes the router.
Close() error
}

View File

@@ -27,6 +27,11 @@ func (r fakeRouter) Set(cfg *Config) error {
return nil
}
func (r fakeRouter) UpdateMagicsockPort(_ uint16, _ string) error {
r.logf("[v1] warning: fakeRouter.UpdateMagicsockPort: not implemented.")
return nil
}
func (r fakeRouter) Close() error {
r.logf("[v1] warning: fakeRouter.Close: not implemented.")
return nil

View File

@@ -63,6 +63,9 @@ type linuxRouter struct {
cmd commandRunner
nfr linuxfw.NetfilterRunner
magicsockPortV4 uint16
magicsockPortV6 uint16
}
func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Monitor) (Router, error) {
@@ -400,6 +403,53 @@ func (r *linuxRouter) Set(cfg *Config) error {
return multierr.New(errs...)
}
// UpdateMagicsockPort implements the Router interface.
func (r *linuxRouter) UpdateMagicsockPort(port uint16, network string) error {
if r.nfr == nil {
if err := r.setupNetfilter(r.netfilterKind); err != nil {
return fmt.Errorf("could not setup netfilter: %w", err)
}
}
var magicsockPort *uint16
switch network {
case "udp4":
magicsockPort = &r.magicsockPortV4
case "udp6":
if !r.nfr.HasIPV6() {
return nil
}
magicsockPort = &r.magicsockPortV6
default:
return fmt.Errorf("unsupported network %s", network)
}
// set the port, we'll make the firewall rule when netfilter turns back on
if r.netfilterMode == netfilterOff {
*magicsockPort = port
return nil
}
if *magicsockPort == port {
return nil
}
if *magicsockPort != 0 {
if err := r.nfr.DelMagicsockPortRule(*magicsockPort, network); err != nil {
return fmt.Errorf("del magicsock port rule: %w", err)
}
}
if port != 0 {
if err := r.nfr.AddMagicsockPortRule(*magicsockPort, network); err != nil {
return fmt.Errorf("add magicsock port rule: %w", err)
}
}
*magicsockPort = port
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
@@ -468,6 +518,16 @@ func (r *linuxRouter) setNetfilterMode(mode preftype.NetfilterMode) error {
if err := r.nfr.AddBase(r.tunname); err != nil {
return err
}
if r.magicsockPortV4 != 0 {
if err := r.nfr.AddMagicsockPortRule(r.magicsockPortV4, "udp4"); err != nil {
return fmt.Errorf("could not add magicsock port rule v4: %w", err)
}
}
if r.magicsockPortV6 != 0 && r.nfr.HasIPV6() {
if err := r.nfr.AddMagicsockPortRule(r.magicsockPortV6, "udp6"); err != nil {
return fmt.Errorf("could not add magicsock port rule v6: %w", err)
}
}
r.snatSubnetRoutes = false
case netfilterOn:
if err := r.nfr.DelHooks(r.logf); err != nil {
@@ -498,6 +558,16 @@ func (r *linuxRouter) setNetfilterMode(mode preftype.NetfilterMode) error {
if err := r.nfr.AddBase(r.tunname); err != nil {
return err
}
if r.magicsockPortV4 != 0 {
if err := r.nfr.AddMagicsockPortRule(r.magicsockPortV4, "udp4"); err != nil {
return fmt.Errorf("could not add magicsock port rule v4: %w", err)
}
}
if r.magicsockPortV6 != 0 && r.nfr.HasIPV6() {
if err := r.nfr.AddMagicsockPortRule(r.magicsockPortV6, "udp6"); err != nil {
return fmt.Errorf("could not add magicsock port rule v6: %w", err)
}
}
r.snatSubnetRoutes = false
case netfilterNoDivert:
reprocess = true

View File

@@ -607,6 +607,58 @@ func (n *fakeIPTablesRunner) DelSNATRule() error {
return nil
}
// buildMagicsockPortRule builds a fake rule to use in AddMagicsockPortRule and
// DelMagicsockPortRule below.
func buildMagicsockPortRule(port uint16) string {
return fmt.Sprintf("-p udp --dport %v -j ACCEPT", port)
}
// AddMagicsockPortRule implements the NetfilterRunner interface, but stores
// rules in fakeIPTablesRunner's internal maps rather than actually calling out
// to iptables. This is mainly to test the linux router implementation.
func (n *fakeIPTablesRunner) AddMagicsockPortRule(port uint16, network string) error {
var ipt map[string][]string
switch network {
case "udp4":
ipt = n.ipt4
case "udp6":
ipt = n.ipt6
default:
return fmt.Errorf("unsupported network %s", network)
}
rule := buildMagicsockPortRule(port)
if err := appendRule(n, ipt, "filter/ts-input", rule); err != nil {
return err
}
return nil
}
// DelMagicsockPortRule implements the NetfilterRunner interface, but removes
// rules from fakeIPTablesRunner's internal maps rather than actually calling
// out to iptables. This is mainly to test the linux router implementation.
func (n *fakeIPTablesRunner) DelMagicsockPortRule(port uint16, network string) error {
var ipt map[string][]string
switch network {
case "udp4":
ipt = n.ipt4
case "udp6":
ipt = n.ipt6
default:
return fmt.Errorf("unsupported network %s", network)
}
rule := buildMagicsockPortRule(port)
if err := deleteRule(n, ipt, "filter/ts-input", rule); err != nil {
return err
}
return nil
}
func (n *fakeIPTablesRunner) HasIPV6() bool { return true }
func (n *fakeIPTablesRunner) HasIPV6NAT() bool { return true }

View File

@@ -228,6 +228,13 @@ func (r *openbsdRouter) Set(cfg *Config) error {
return errq
}
// UpdateMagicsockPort implements the Router interface. This implementation
// does nothing and returns nil because this router does not currently need
// to know what the magicsock UDP port is.
func (r *openbsdRouter) UpdateMagicsockPort(_ uint16, _ string) error {
return nil
}
func (r *openbsdRouter) Close() error {
cleanup(r.logf, r.tunname)
return nil

View File

@@ -196,6 +196,13 @@ func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) {
return reterr
}
// UpdateMagicsockPort implements the Router interface. This implementation
// does nothing and returns nil because this router does not currently need
// to know what the magicsock UDP port is.
func (r *userspaceBSDRouter) UpdateMagicsockPort(_ uint16, _ string) error {
return nil
}
func (r *userspaceBSDRouter) Close() error {
return nil
}

View File

@@ -102,6 +102,13 @@ func hasDefaultRoute(routes []netip.Prefix) bool {
return false
}
// UpdateMagicsockPort implements the Router interface. This implementation
// does nothing and returns nil because this router does not currently need
// to know what the magicsock UDP port is.
func (r *winRouter) UpdateMagicsockPort(_ uint16, _ string) error {
return nil
}
func (r *winRouter) Close() error {
r.firewall.clear()