detector.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux
  4. package linuxfw
  5. import (
  6. "errors"
  7. "os/exec"
  8. "tailscale.com/envknob"
  9. "tailscale.com/hostinfo"
  10. "tailscale.com/types/logger"
  11. "tailscale.com/version/distro"
  12. )
  13. func detectFirewallMode(logf logger.Logf, prefHint string) FirewallMode {
  14. if distro.Get() == distro.Gokrazy {
  15. // Reduce startup logging on gokrazy. There's no way to do iptables on
  16. // gokrazy anyway.
  17. logf("GoKrazy should use nftables.")
  18. hostinfo.SetFirewallMode("nft-gokrazy")
  19. return FirewallModeNfTables
  20. }
  21. mode := envknob.String("TS_DEBUG_FIREWALL_MODE")
  22. // If the envknob isn't set, fall back to the pref suggested by c2n or
  23. // nodeattrs.
  24. if mode == "" {
  25. mode = prefHint
  26. logf("using firewall mode pref %s", prefHint)
  27. } else if prefHint != "" {
  28. logf("TS_DEBUG_FIREWALL_MODE set, overriding firewall mode from %s to %s", prefHint, mode)
  29. }
  30. var det linuxFWDetector
  31. if mode == "" {
  32. // We have no preference, so check if `iptables` is even available.
  33. _, err := det.iptDetect()
  34. if err != nil && errors.Is(err, exec.ErrNotFound) {
  35. logf("iptables not found: %v; falling back to nftables", err)
  36. mode = "nftables"
  37. }
  38. }
  39. // We now use iptables as default and have "auto" and "nftables" as
  40. // options for people to test further.
  41. switch mode {
  42. case "auto":
  43. return pickFirewallModeFromInstalledRules(logf, det)
  44. case "nftables":
  45. hostinfo.SetFirewallMode("nft-forced")
  46. return FirewallModeNfTables
  47. case "iptables":
  48. hostinfo.SetFirewallMode("ipt-forced")
  49. default:
  50. logf("default choosing iptables")
  51. hostinfo.SetFirewallMode("ipt-default")
  52. }
  53. return FirewallModeIPTables
  54. }
  55. // tableDetector abstracts helpers to detect the firewall mode.
  56. // It is implemented for testing purposes.
  57. type tableDetector interface {
  58. iptDetect() (int, error)
  59. nftDetect() (int, error)
  60. }
  61. type linuxFWDetector struct{}
  62. // iptDetect returns the number of iptables rules in the current namespace.
  63. func (l linuxFWDetector) iptDetect() (int, error) {
  64. return detectIptables()
  65. }
  66. // nftDetect returns the number of nftables rules in the current namespace.
  67. func (l linuxFWDetector) nftDetect() (int, error) {
  68. return detectNetfilter()
  69. }
  70. // pickFirewallModeFromInstalledRules returns the firewall mode to use based on
  71. // the environment and the system's capabilities.
  72. func pickFirewallModeFromInstalledRules(logf logger.Logf, det tableDetector) FirewallMode {
  73. if distro.Get() == distro.Gokrazy {
  74. // Reduce startup logging on gokrazy. There's no way to do iptables on
  75. // gokrazy anyway.
  76. return FirewallModeNfTables
  77. }
  78. iptAva, nftAva := true, true
  79. iptRuleCount, err := det.iptDetect()
  80. if err != nil {
  81. logf("detect iptables rule: %v", err)
  82. iptAva = false
  83. }
  84. nftRuleCount, err := det.nftDetect()
  85. if err != nil {
  86. logf("detect nftables rule: %v", err)
  87. nftAva = false
  88. }
  89. logf("nftables rule count: %d, iptables rule count: %d", nftRuleCount, iptRuleCount)
  90. switch {
  91. case nftRuleCount > 0 && iptRuleCount == 0:
  92. logf("nftables is currently in use")
  93. hostinfo.SetFirewallMode("nft-inuse")
  94. return FirewallModeNfTables
  95. case iptRuleCount > 0 && nftRuleCount == 0:
  96. logf("iptables is currently in use")
  97. hostinfo.SetFirewallMode("ipt-inuse")
  98. return FirewallModeIPTables
  99. case nftAva:
  100. // if both iptables and nftables are available but
  101. // neither/both are currently used, use nftables.
  102. logf("nftables is available")
  103. hostinfo.SetFirewallMode("nft")
  104. return FirewallModeNfTables
  105. case iptAva:
  106. logf("iptables is available")
  107. hostinfo.SetFirewallMode("ipt")
  108. return FirewallModeIPTables
  109. default:
  110. // if neither iptables nor nftables are available, use iptablesRunner as a dummy
  111. // runner which exists but won't do anything. Creating iptablesRunner errors only
  112. // if the iptables command is missing or doesn’t support "--version", as long as it
  113. // can determine a version then it’ll carry on.
  114. hostinfo.SetFirewallMode("ipt-fb")
  115. return FirewallModeIPTables
  116. }
  117. }