Browse Source

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 <[email protected]>
Nick Khyl 1 month ago
parent
commit
2a69f48541
2 changed files with 79 additions and 6 deletions
  1. 3 0
      tstest/test-wishlist.md
  2. 76 6
      wf/firewall.go

+ 3 - 0
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.

+ 76 - 6
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 {