diff --git a/tstest/test-wishlist.md b/tstest/test-wishlist.md index fc5266119..eb4601b92 100644 --- a/tstest/test-wishlist.md +++ b/tstest/test-wishlist.md @@ -13,5 +13,8 @@ reference to an issue or PR about the feature. # The list -- ... +- Link-local multicast, and mDNS/LLMNR specifically, 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 it for internal interfaces, + such as Hyper-V/WSL2 on Windows. diff --git a/wf/firewall.go b/wf/firewall.go index 730fa3d15..076944c8d 100644 --- a/wf/firewall.go +++ b/wf/firewall.go @@ -22,6 +22,9 @@ var ( linkLocalDHCPMulticast = netip.MustParseAddr("ff02::1:2") siteLocalDHCPMulticast = netip.MustParseAddr("ff05::1:3") linkLocalRouterMulticast = netip.MustParseAddr("ff02::2") + + linkLocalMulticastIPv4Range = netip.MustParsePrefix("224.0.0.0/24") + linkLocalMulticastIPv6Range = netip.MustParsePrefix("ff02::/16") ) type direction int @@ -224,15 +227,67 @@ func (f *Firewall) UpdatePermittedRoutes(newRoutes []netip.Prefix) error { } else { p = protocolV6 } - rules, err := f.addRules("local route", weightKnownTraffic, conditions, wf.ActionPermit, p, directionBoth) + name := "local route - " + r.String() + rules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionBoth) + if err != nil { + return err + } + + name = "link-local multicast - " + r.String() + conditions = matchLinkLocalMulticast(r, false) + multicastRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + if err != nil { + return err + } + rules = append(rules, multicastRules...) + + conditions = matchLinkLocalMulticast(r, true) + multicastRules, err = f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) if err != nil { return err } + rules = append(rules, multicastRules...) + f.permittedRoutes[r] = rules } return nil } +// matchLinkLocalMulticast returns a list of conditions that match +// outbound or inbound link-local multicast traffic to or from the +// specified network. +func matchLinkLocalMulticast(pfx netip.Prefix, inbound bool) []*wf.Match { + var linkLocalMulticastRange netip.Prefix + if pfx.Addr().Is4() { + linkLocalMulticastRange = linkLocalMulticastIPv4Range + } else { + linkLocalMulticastRange = linkLocalMulticastIPv6Range + } + var localAddr, remoteAddr netip.Prefix + if inbound { + localAddr, remoteAddr = linkLocalMulticastRange, pfx + } else { + localAddr, remoteAddr = pfx, linkLocalMulticastRange + } + 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 {