firewall.go 13 KB

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