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>
This commit is contained in:
Nick Khyl
2026-01-23 17:53:00 -06:00
committed by Nick Khyl
parent 3ec5be3f51
commit 2a69f48541
2 changed files with 79 additions and 6 deletions
+76 -6
View File
@@ -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 {