mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-01 17:49:02 +00:00
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:
@@ -290,6 +290,10 @@ type Conn struct {
|
||||
|
||||
// wgPinger is the WireGuard only pinger used for latency measurements.
|
||||
wgPinger lazy.SyncValue[*ping.Pinger]
|
||||
|
||||
// onPortUpdate is called with the new port when magicsock rebinds to
|
||||
// a new port.
|
||||
onPortUpdate func(port uint16, network string)
|
||||
}
|
||||
|
||||
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
|
||||
@@ -355,6 +359,10 @@ type Options struct {
|
||||
// ControlKnobs are the set of control knobs to use.
|
||||
// If nil, they're ignored and not updated.
|
||||
ControlKnobs *controlknobs.Knobs
|
||||
|
||||
// OnPortUpdate is called with the new port when magicsock rebinds to
|
||||
// a new port.
|
||||
OnPortUpdate func(port uint16, network string)
|
||||
}
|
||||
|
||||
func (o *Options) logf() logger.Logf {
|
||||
@@ -427,6 +435,7 @@ func NewConn(opts Options) (*Conn, error) {
|
||||
c.portMapper.SetGatewayLookupFunc(opts.NetMon.GatewayAndSelfIP)
|
||||
}
|
||||
c.netMon = opts.NetMon
|
||||
c.onPortUpdate = opts.OnPortUpdate
|
||||
|
||||
if err := c.rebind(keepCurrentPort); err != nil {
|
||||
return nil, err
|
||||
@@ -2342,6 +2351,19 @@ func (c *Conn) bindSocket(ruc *RebindingUDPConn, network string, curPortFate cur
|
||||
c.logf("magicsock: unable to bind %v port %d: %v", network, port, err)
|
||||
continue
|
||||
}
|
||||
if c.onPortUpdate != nil {
|
||||
_, gotPortStr, err := net.SplitHostPort(pconn.LocalAddr().String())
|
||||
if err != nil {
|
||||
c.logf("could not parse port from %s: %w", pconn.LocalAddr().String(), err)
|
||||
} else {
|
||||
gotPort, err := strconv.ParseUint(gotPortStr, 10, 16)
|
||||
if err != nil {
|
||||
c.logf("could not parse port from %s: %w", gotPort, err)
|
||||
} else {
|
||||
c.onPortUpdate(uint16(gotPort), network)
|
||||
}
|
||||
}
|
||||
}
|
||||
trySetSocketBuffer(pconn, c.logf)
|
||||
|
||||
// Success.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -324,6 +324,13 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
|
||||
e.RequestStatus()
|
||||
}
|
||||
onPortUpdate := func(port uint16, network string) {
|
||||
e.logf("onPortUpdate(port=%v, network=%s)", port, network)
|
||||
|
||||
if err := e.router.UpdateMagicsockPort(port, network); err != nil {
|
||||
e.logf("UpdateMagicsockPort(port=%v, network=%s) failed: %w", port, network, err)
|
||||
}
|
||||
}
|
||||
magicsockOpts := magicsock.Options{
|
||||
Logf: logf,
|
||||
Port: conf.ListenPort,
|
||||
@@ -333,6 +340,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
NoteRecvActivity: e.noteRecvActivity,
|
||||
NetMon: e.netMon,
|
||||
ControlKnobs: conf.ControlKnobs,
|
||||
OnPortUpdate: onPortUpdate,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
Reference in New Issue
Block a user