firewall.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build windows
  4. // Package wf controls the Windows Filtering Platform to change Windows firewall rules.
  5. package wf
  6. import (
  7. "fmt"
  8. "net/netip"
  9. "os"
  10. "github.com/tailscale/wf"
  11. "golang.org/x/sys/windows"
  12. "tailscale.com/net/netaddr"
  13. )
  14. // Known addresses.
  15. var (
  16. linkLocalRange = netip.MustParsePrefix("fe80::/10")
  17. linkLocalDHCPMulticast = netip.MustParseAddr("ff02::1:2")
  18. siteLocalDHCPMulticast = netip.MustParseAddr("ff05::1:3")
  19. linkLocalRouterMulticast = netip.MustParseAddr("ff02::2")
  20. linkLocalMulticastIPv4Range = netip.MustParsePrefix("224.0.0.0/24")
  21. linkLocalMulticastIPv6Range = netip.MustParsePrefix("ff02::/16")
  22. )
  23. type direction int
  24. const (
  25. directionInbound direction = iota
  26. directionOutbound
  27. directionBoth
  28. )
  29. type protocol int
  30. const (
  31. protocolV4 protocol = iota
  32. protocolV6
  33. protocolAll
  34. )
  35. // getLayers returns the wf.LayerIDs where the rules should be added based
  36. // on the protocol and direction.
  37. func (p protocol) getLayers(d direction) []wf.LayerID {
  38. var layers []wf.LayerID
  39. if p == protocolAll || p == protocolV4 {
  40. if d == directionBoth || d == directionInbound {
  41. layers = append(layers, wf.LayerALEAuthRecvAcceptV4)
  42. }
  43. if d == directionBoth || d == directionOutbound {
  44. layers = append(layers, wf.LayerALEAuthConnectV4)
  45. }
  46. }
  47. if p == protocolAll || p == protocolV6 {
  48. if d == directionBoth || d == directionInbound {
  49. layers = append(layers, wf.LayerALEAuthRecvAcceptV6)
  50. }
  51. if d == directionBoth || d == directionOutbound {
  52. layers = append(layers, wf.LayerALEAuthConnectV6)
  53. }
  54. }
  55. return layers
  56. }
  57. func ruleName(action wf.Action, layerID wf.LayerID, name string) string {
  58. switch layerID {
  59. case wf.LayerALEAuthConnectV4:
  60. return fmt.Sprintf("%s outbound %s (IPv4)", action, name)
  61. case wf.LayerALEAuthConnectV6:
  62. return fmt.Sprintf("%s outbound %s (IPv6)", action, name)
  63. case wf.LayerALEAuthRecvAcceptV4:
  64. return fmt.Sprintf("%s inbound %s (IPv4)", action, name)
  65. case wf.LayerALEAuthRecvAcceptV6:
  66. return fmt.Sprintf("%s inbound %s (IPv6)", action, name)
  67. }
  68. return ""
  69. }
  70. // Firewall uses the Windows Filtering Platform to implement a network firewall.
  71. type Firewall struct {
  72. luid uint64
  73. providerID wf.ProviderID
  74. sublayerID wf.SublayerID
  75. session *wf.Session
  76. permittedRoutes map[netip.Prefix][]*wf.Rule
  77. }
  78. // New returns a new Firewall for the provided interface ID.
  79. func New(luid uint64) (*Firewall, error) {
  80. session, err := wf.New(&wf.Options{
  81. Name: "Tailscale firewall",
  82. Dynamic: true,
  83. })
  84. if err != nil {
  85. return nil, err
  86. }
  87. wguid, err := windows.GenerateGUID()
  88. if err != nil {
  89. return nil, err
  90. }
  91. providerID := wf.ProviderID(wguid)
  92. if err := session.AddProvider(&wf.Provider{
  93. ID: providerID,
  94. Name: "Tailscale provider",
  95. }); err != nil {
  96. return nil, err
  97. }
  98. wguid, err = windows.GenerateGUID()
  99. if err != nil {
  100. return nil, err
  101. }
  102. sublayerID := wf.SublayerID(wguid)
  103. if err := session.AddSublayer(&wf.Sublayer{
  104. ID: sublayerID,
  105. Name: "Tailscale permissive and blocking filters",
  106. Weight: 0,
  107. }); err != nil {
  108. return nil, err
  109. }
  110. f := &Firewall{
  111. luid: luid,
  112. session: session,
  113. providerID: providerID,
  114. sublayerID: sublayerID,
  115. permittedRoutes: make(map[netip.Prefix][]*wf.Rule),
  116. }
  117. if err := f.enable(); err != nil {
  118. return nil, err
  119. }
  120. return f, nil
  121. }
  122. type weight uint64
  123. const (
  124. weightTailscaleTraffic weight = 15
  125. weightKnownTraffic weight = 12
  126. weightCatchAll weight = 0
  127. )
  128. func (f *Firewall) enable() error {
  129. if err := f.permitTailscaleService(weightTailscaleTraffic); err != nil {
  130. return fmt.Errorf("permitTailscaleService failed: %w", err)
  131. }
  132. if err := f.permitTunInterface(weightTailscaleTraffic); err != nil {
  133. return fmt.Errorf("permitTunInterface failed: %w", err)
  134. }
  135. if err := f.permitDNS(weightTailscaleTraffic); err != nil {
  136. return fmt.Errorf("permitDNS failed: %w", err)
  137. }
  138. if err := f.permitLoopback(weightTailscaleTraffic); err != nil {
  139. return fmt.Errorf("permitLoopback failed: %w", err)
  140. }
  141. if err := f.permitDHCPv4(weightKnownTraffic); err != nil {
  142. return fmt.Errorf("permitDHCPv4 failed: %w", err)
  143. }
  144. if err := f.permitDHCPv6(weightKnownTraffic); err != nil {
  145. return fmt.Errorf("permitDHCPv6 failed: %w", err)
  146. }
  147. if err := f.permitNDP(weightKnownTraffic); err != nil {
  148. return fmt.Errorf("permitNDP failed: %w", err)
  149. }
  150. /* TODO: actually evaluate if this does anything and if we need this. It's layer 2; our other rules are layer 3.
  151. * In other words, if somebody complains, try enabling it. For now, keep it off.
  152. * TODO(maisem): implement this.
  153. err = permitHyperV(session, baseObjects, weightKnownTraffic)
  154. if err != nil {
  155. return wrapErr(err)
  156. }
  157. */
  158. if err := f.blockAll(weightCatchAll); err != nil {
  159. return fmt.Errorf("blockAll failed: %w", err)
  160. }
  161. return nil
  162. }
  163. // UpdatedPermittedRoutes adds rules to allow incoming and outgoing connections
  164. // from the provided prefixes. It will also remove rules for routes that were
  165. // previously added but have been removed.
  166. func (f *Firewall) UpdatePermittedRoutes(newRoutes []netip.Prefix) error {
  167. var routesToAdd []netip.Prefix
  168. routeMap := make(map[netip.Prefix]bool)
  169. for _, r := range newRoutes {
  170. routeMap[r] = true
  171. if _, ok := f.permittedRoutes[r]; !ok {
  172. routesToAdd = append(routesToAdd, r)
  173. }
  174. }
  175. var routesToRemove []netip.Prefix
  176. for r := range f.permittedRoutes {
  177. if !routeMap[r] {
  178. routesToRemove = append(routesToRemove, r)
  179. }
  180. }
  181. for _, r := range routesToRemove {
  182. for _, rule := range f.permittedRoutes[r] {
  183. if err := f.session.DeleteRule(rule.ID); err != nil {
  184. return err
  185. }
  186. }
  187. delete(f.permittedRoutes, r)
  188. }
  189. for _, r := range routesToAdd {
  190. conditions := []*wf.Match{
  191. {
  192. Field: wf.FieldIPRemoteAddress,
  193. Op: wf.MatchTypeEqual,
  194. Value: r,
  195. },
  196. }
  197. var p protocol
  198. if r.Addr().Is4() {
  199. p = protocolV4
  200. } else {
  201. p = protocolV6
  202. }
  203. name := "local route - " + r.String()
  204. rules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionBoth)
  205. if err != nil {
  206. return err
  207. }
  208. name = "link-local multicast - " + r.String()
  209. conditions = matchLinkLocalMulticast(r, false)
  210. multicastRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound)
  211. if err != nil {
  212. return err
  213. }
  214. rules = append(rules, multicastRules...)
  215. conditions = matchLinkLocalMulticast(r, true)
  216. multicastRules, err = f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound)
  217. if err != nil {
  218. return err
  219. }
  220. rules = append(rules, multicastRules...)
  221. f.permittedRoutes[r] = rules
  222. }
  223. return nil
  224. }
  225. // matchLinkLocalMulticast returns a list of conditions that match
  226. // outbound or inbound link-local multicast traffic to or from the
  227. // specified network.
  228. func matchLinkLocalMulticast(pfx netip.Prefix, inbound bool) []*wf.Match {
  229. var linkLocalMulticastRange netip.Prefix
  230. if pfx.Addr().Is4() {
  231. linkLocalMulticastRange = linkLocalMulticastIPv4Range
  232. } else {
  233. linkLocalMulticastRange = linkLocalMulticastIPv6Range
  234. }
  235. var localAddr, remoteAddr netip.Prefix
  236. if inbound {
  237. localAddr, remoteAddr = linkLocalMulticastRange, pfx
  238. } else {
  239. localAddr, remoteAddr = pfx, linkLocalMulticastRange
  240. }
  241. return []*wf.Match{
  242. {
  243. Field: wf.FieldIPProtocol,
  244. Op: wf.MatchTypeEqual,
  245. Value: wf.IPProtoUDP,
  246. },
  247. {
  248. Field: wf.FieldIPLocalAddress,
  249. Op: wf.MatchTypeEqual,
  250. Value: localAddr,
  251. },
  252. {
  253. Field: wf.FieldIPRemoteAddress,
  254. Op: wf.MatchTypeEqual,
  255. Value: remoteAddr,
  256. },
  257. }
  258. }
  259. func (f *Firewall) newRule(name string, w weight, layer wf.LayerID, conditions []*wf.Match, action wf.Action) (*wf.Rule, error) {
  260. id, err := windows.GenerateGUID()
  261. if err != nil {
  262. return nil, err
  263. }
  264. return &wf.Rule{
  265. Name: ruleName(action, layer, name),
  266. ID: wf.RuleID(id),
  267. Provider: f.providerID,
  268. Sublayer: f.sublayerID,
  269. Layer: layer,
  270. Weight: uint64(w),
  271. Conditions: conditions,
  272. Action: action,
  273. }, nil
  274. }
  275. func (f *Firewall) addRules(name string, w weight, conditions []*wf.Match, action wf.Action, p protocol, d direction) ([]*wf.Rule, error) {
  276. var rules []*wf.Rule
  277. for _, layer := range p.getLayers(d) {
  278. r, err := f.newRule(name, w, layer, conditions, action)
  279. if err != nil {
  280. return nil, err
  281. }
  282. if err := f.session.AddRule(r); err != nil {
  283. return nil, err
  284. }
  285. rules = append(rules, r)
  286. }
  287. return rules, nil
  288. }
  289. func (f *Firewall) blockAll(w weight) error {
  290. _, err := f.addRules("all", w, nil, wf.ActionBlock, protocolAll, directionBoth)
  291. return err
  292. }
  293. func (f *Firewall) permitNDP(w weight) error {
  294. // These are aliased according to:
  295. // https://social.msdn.microsoft.com/Forums/azure/en-US/eb2aa3cd-5f1c-4461-af86-61e7d43ccc23/filtering-icmp-by-type-code?forum=wfp
  296. fieldICMPType := wf.FieldIPLocalPort
  297. fieldICMPCode := wf.FieldIPRemotePort
  298. var icmpConditions = func(t, c uint16, remoteAddress any) []*wf.Match {
  299. conditions := []*wf.Match{
  300. {
  301. Field: wf.FieldIPProtocol,
  302. Op: wf.MatchTypeEqual,
  303. Value: wf.IPProtoICMPV6,
  304. },
  305. {
  306. Field: fieldICMPType,
  307. Op: wf.MatchTypeEqual,
  308. Value: t,
  309. },
  310. {
  311. Field: fieldICMPCode,
  312. Op: wf.MatchTypeEqual,
  313. Value: c,
  314. },
  315. }
  316. if remoteAddress != nil {
  317. conditions = append(conditions, &wf.Match{
  318. Field: wf.FieldIPRemoteAddress,
  319. Op: wf.MatchTypeEqual,
  320. Value: linkLocalRouterMulticast,
  321. })
  322. }
  323. return conditions
  324. }
  325. /* TODO: actually handle the hop limit somehow! The rules should vaguely be:
  326. * - icmpv6 133: must be outgoing, dst must be FF02::2/128, hop limit must be 255
  327. * - icmpv6 134: must be incoming, src must be FE80::/10, hop limit must be 255
  328. * - icmpv6 135: either incoming or outgoing, hop limit must be 255
  329. * - icmpv6 136: either incoming or outgoing, hop limit must be 255
  330. * - icmpv6 137: must be incoming, src must be FE80::/10, hop limit must be 255
  331. */
  332. //
  333. // Router Solicitation Message
  334. // ICMP type 133, code 0. Outgoing.
  335. //
  336. conditions := icmpConditions(133, 0, linkLocalRouterMulticast)
  337. if _, err := f.addRules("NDP type 133", w, conditions, wf.ActionPermit, protocolV6, directionOutbound); err != nil {
  338. return err
  339. }
  340. //
  341. // Router Advertisement Message
  342. // ICMP type 134, code 0. Incoming.
  343. //
  344. conditions = icmpConditions(134, 0, linkLocalRange)
  345. if _, err := f.addRules("NDP type 134", w, conditions, wf.ActionPermit, protocolV6, directionInbound); err != nil {
  346. return err
  347. }
  348. //
  349. // Neighbor Solicitation Message
  350. // ICMP type 135, code 0. Bi-directional.
  351. //
  352. conditions = icmpConditions(135, 0, nil)
  353. if _, err := f.addRules("NDP type 135", w, conditions, wf.ActionPermit, protocolV6, directionBoth); err != nil {
  354. return err
  355. }
  356. //
  357. // Neighbor Advertisement Message
  358. // ICMP type 136, code 0. Bi-directional.
  359. //
  360. conditions = icmpConditions(136, 0, nil)
  361. if _, err := f.addRules("NDP type 136", w, conditions, wf.ActionPermit, protocolV6, directionBoth); err != nil {
  362. return err
  363. }
  364. //
  365. // Redirect Message
  366. // ICMP type 137, code 0. Incoming.
  367. //
  368. conditions = icmpConditions(137, 0, linkLocalRange)
  369. if _, err := f.addRules("NDP type 137", w, conditions, wf.ActionPermit, protocolV6, directionInbound); err != nil {
  370. return err
  371. }
  372. return nil
  373. }
  374. func (f *Firewall) permitDHCPv6(w weight) error {
  375. var dhcpConditions = func(remoteAddrs ...any) []*wf.Match {
  376. conditions := []*wf.Match{
  377. {
  378. Field: wf.FieldIPProtocol,
  379. Op: wf.MatchTypeEqual,
  380. Value: wf.IPProtoUDP,
  381. },
  382. {
  383. Field: wf.FieldIPLocalAddress,
  384. Op: wf.MatchTypeEqual,
  385. Value: linkLocalRange,
  386. },
  387. {
  388. Field: wf.FieldIPLocalPort,
  389. Op: wf.MatchTypeEqual,
  390. Value: uint16(546),
  391. },
  392. {
  393. Field: wf.FieldIPRemotePort,
  394. Op: wf.MatchTypeEqual,
  395. Value: uint16(547),
  396. },
  397. }
  398. for _, a := range remoteAddrs {
  399. conditions = append(conditions, &wf.Match{
  400. Field: wf.FieldIPRemoteAddress,
  401. Op: wf.MatchTypeEqual,
  402. Value: a,
  403. })
  404. }
  405. return conditions
  406. }
  407. conditions := dhcpConditions(linkLocalDHCPMulticast, siteLocalDHCPMulticast)
  408. if _, err := f.addRules("DHCP request", w, conditions, wf.ActionPermit, protocolV6, directionOutbound); err != nil {
  409. return err
  410. }
  411. conditions = dhcpConditions(linkLocalRange)
  412. if _, err := f.addRules("DHCP response", w, conditions, wf.ActionPermit, protocolV6, directionInbound); err != nil {
  413. return err
  414. }
  415. return nil
  416. }
  417. func (f *Firewall) permitDHCPv4(w weight) error {
  418. var dhcpConditions = func(remoteAddrs ...any) []*wf.Match {
  419. conditions := []*wf.Match{
  420. {
  421. Field: wf.FieldIPProtocol,
  422. Op: wf.MatchTypeEqual,
  423. Value: wf.IPProtoUDP,
  424. },
  425. {
  426. Field: wf.FieldIPLocalPort,
  427. Op: wf.MatchTypeEqual,
  428. Value: uint16(68),
  429. },
  430. {
  431. Field: wf.FieldIPRemotePort,
  432. Op: wf.MatchTypeEqual,
  433. Value: uint16(67),
  434. },
  435. }
  436. for _, a := range remoteAddrs {
  437. conditions = append(conditions, &wf.Match{
  438. Field: wf.FieldIPRemoteAddress,
  439. Op: wf.MatchTypeEqual,
  440. Value: a,
  441. })
  442. }
  443. return conditions
  444. }
  445. conditions := dhcpConditions(netaddr.IPv4(255, 255, 255, 255))
  446. if _, err := f.addRules("DHCP request", w, conditions, wf.ActionPermit, protocolV4, directionOutbound); err != nil {
  447. return err
  448. }
  449. conditions = dhcpConditions()
  450. if _, err := f.addRules("DHCP response", w, conditions, wf.ActionPermit, protocolV4, directionInbound); err != nil {
  451. return err
  452. }
  453. return nil
  454. }
  455. func (f *Firewall) permitTunInterface(w weight) error {
  456. condition := []*wf.Match{
  457. {
  458. Field: wf.FieldIPLocalInterface,
  459. Op: wf.MatchTypeEqual,
  460. Value: f.luid,
  461. },
  462. }
  463. _, err := f.addRules("on TUN", w, condition, wf.ActionPermit, protocolAll, directionBoth)
  464. return err
  465. }
  466. func (f *Firewall) permitLoopback(w weight) error {
  467. condition := []*wf.Match{
  468. {
  469. Field: wf.FieldFlags,
  470. Op: wf.MatchTypeFlagsAllSet,
  471. Value: wf.ConditionFlagIsLoopback,
  472. },
  473. }
  474. _, err := f.addRules("on loopback", w, condition, wf.ActionPermit, protocolAll, directionBoth)
  475. return err
  476. }
  477. func (f *Firewall) permitDNS(w weight) error {
  478. conditions := []*wf.Match{
  479. {
  480. Field: wf.FieldIPRemotePort,
  481. Op: wf.MatchTypeEqual,
  482. Value: uint16(53),
  483. },
  484. // Repeat the condition type for logical OR.
  485. {
  486. Field: wf.FieldIPProtocol,
  487. Op: wf.MatchTypeEqual,
  488. Value: wf.IPProtoUDP,
  489. },
  490. {
  491. Field: wf.FieldIPProtocol,
  492. Op: wf.MatchTypeEqual,
  493. Value: wf.IPProtoTCP,
  494. },
  495. }
  496. _, err := f.addRules("DNS", w, conditions, wf.ActionPermit, protocolAll, directionBoth)
  497. return err
  498. }
  499. func (f *Firewall) permitTailscaleService(w weight) error {
  500. currentFile, err := os.Executable()
  501. if err != nil {
  502. return err
  503. }
  504. appID, err := wf.AppID(currentFile)
  505. if err != nil {
  506. return fmt.Errorf("could not get app id for %q: %w", currentFile, err)
  507. }
  508. conditions := []*wf.Match{
  509. {
  510. Field: wf.FieldALEAppID,
  511. Op: wf.MatchTypeEqual,
  512. Value: appID,
  513. },
  514. }
  515. _, err = f.addRules("unrestricted traffic for Tailscale service", w, conditions, wf.ActionPermit, protocolAll, directionBoth)
  516. return err
  517. }