summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNick Khyl <nickk@tailscale.com>2026-01-23 17:53:00 -0600
committerNick Khyl <1761190+nickkhyl@users.noreply.github.com>2026-01-23 18:30:38 -0600
commit2a69f48541e0ed7fdf81fc88b079474331eeee76 (patch)
tree0130796978d0f049ba009050a6fafc962a0be210
parent3ec5be3f510f74738179c1023468343a62a7e00f (diff)
downloadtailscale-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.md3
-rw-r--r--wf/firewall.go82
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 {