diff options
| author | Nick Khyl <nickk@tailscale.com> | 2026-01-23 17:53:00 -0600 |
|---|---|---|
| committer | Nick Khyl <1761190+nickkhyl@users.noreply.github.com> | 2026-01-23 18:30:38 -0600 |
| commit | 2a69f48541e0ed7fdf81fc88b079474331eeee76 (patch) | |
| tree | 0130796978d0f049ba009050a6fafc962a0be210 | |
| parent | 3ec5be3f510f74738179c1023468343a62a7e00f (diff) | |
| download | tailscale-2a69f48541e0ed7fdf81fc88b079474331eeee76.tar.xz tailscale-2a69f48541e0ed7fdf81fc88b079474331eeee76.zip | |
wf: allow limited broadcast to/from permitted interfaces when using an exit node on Windows
Similarly to allowing link-local multicast in #13661, we should also allow broadcast traffic
on permitted interfaces when the killswitch is enabled due to exit node usage on Windows.
This always includes internal interfaces, such as Hyper-V/WSL2, and also the LAN when
"Allow local network access" is enabled in the client.
Updates #18504
Signed-off-by: Nick Khyl <nickk@tailscale.com>
| -rw-r--r-- | tstest/test-wishlist.md | 3 | ||||
| -rw-r--r-- | wf/firewall.go | 82 |
2 files changed, 79 insertions, 6 deletions
diff --git a/tstest/test-wishlist.md b/tstest/test-wishlist.md index eb4601b92..39b4da6c0 100644 --- a/tstest/test-wishlist.md +++ b/tstest/test-wishlist.md @@ -18,3 +18,6 @@ reference to an issue or PR about the feature. When the option is disabled, we should still permit it for internal interfaces, such as Hyper-V/WSL2 on Windows. +- Inbound and outbound broadcasts when an exit node is used, both with and without + the "Allow local network access" option enabled. When the option is disabled, + we should still permit traffic on internal interfaces, such as Hyper-V/WSL2 on Windows.
\ No newline at end of file diff --git a/wf/firewall.go b/wf/firewall.go index 5209c2293..995a60c3e 100644 --- a/wf/firewall.go +++ b/wf/firewall.go @@ -25,6 +25,8 @@ var ( linkLocalMulticastIPv4Range = netip.MustParsePrefix("224.0.0.0/24") linkLocalMulticastIPv6Range = netip.MustParsePrefix("ff02::/16") + + limitedBroadcast = netip.MustParsePrefix("255.255.255.255/32") ) type direction int @@ -233,26 +235,41 @@ func (f *Firewall) UpdatePermittedRoutes(newRoutes []netip.Prefix) error { return err } - name = "link-local multicast - " + r.String() - conditions = matchLinkLocalMulticast(r, false) - multicastRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + multicastRules, err := f.addLinkLocalMulticastRules(p, r) if err != nil { return err } rules = append(rules, multicastRules...) - conditions = matchLinkLocalMulticast(r, true) - multicastRules, err = f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + broadcastRules, err := f.addLimitedBroadcastRules(p, r) if err != nil { return err } - rules = append(rules, multicastRules...) + rules = append(rules, broadcastRules...) f.permittedRoutes[r] = rules } return nil } +// addLinkLocalMulticastRules adds rules to allow inbound and outbound +// link-local multicast traffic to or from the specified network. +// It returns the added rules, or an error. +func (f *Firewall) addLinkLocalMulticastRules(p protocol, r netip.Prefix) ([]*wf.Rule, error) { + name := "link-local multicast - " + r.String() + conditions := matchLinkLocalMulticast(r, false) + outboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + if err != nil { + return nil, err + } + conditions = matchLinkLocalMulticast(r, true) + inboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + if err != nil { + return nil, err + } + return append(outboundRules, inboundRules...), nil +} + // matchLinkLocalMulticast returns a list of conditions that match // outbound or inbound link-local multicast traffic to or from the // specified network. @@ -288,6 +305,59 @@ func matchLinkLocalMulticast(pfx netip.Prefix, inbound bool) []*wf.Match { } } +// addLimitedBroadcastRules adds rules to allow inbound and outbound +// limited broadcast traffic to or from the specified network, +// if the network is IPv4. It returns the added rules, or an error. +func (f *Firewall) addLimitedBroadcastRules(p protocol, r netip.Prefix) ([]*wf.Rule, error) { + if !r.Addr().Is4() { + return nil, nil + } + name := "broadcast - " + r.String() + conditions := matchLimitedBroadcast(r, false) + outboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + if err != nil { + return nil, err + } + conditions = matchLimitedBroadcast(r, true) + inboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + if err != nil { + return nil, err + } + return append(outboundRules, inboundRules...), nil +} + +// matchLimitedBroadcast returns a list of conditions that match +// outbound or inbound limited broadcast traffic to or from the +// specified network. It panics if the pfx is not IPv4. +func matchLimitedBroadcast(pfx netip.Prefix, inbound bool) []*wf.Match { + if !pfx.Addr().Is4() { + panic("limited broadcast is only applicable to IPv4") + } + var localAddr, remoteAddr netip.Prefix + if inbound { + localAddr, remoteAddr = limitedBroadcast, pfx + } else { + localAddr, remoteAddr = pfx, limitedBroadcast + } + return []*wf.Match{ + { + Field: wf.FieldIPProtocol, + Op: wf.MatchTypeEqual, + Value: wf.IPProtoUDP, + }, + { + Field: wf.FieldIPLocalAddress, + Op: wf.MatchTypeEqual, + Value: localAddr, + }, + { + Field: wf.FieldIPRemoteAddress, + Op: wf.MatchTypeEqual, + Value: remoteAddr, + }, + } +} + func (f *Firewall) newRule(name string, w weight, layer wf.LayerID, conditions []*wf.Match, action wf.Action) (*wf.Rule, error) { id, err := windows.GenerateGUID() if err != nil { |
