iptables.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux && !ts_omit_iptables
  4. package linuxfw
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "os"
  10. "os/exec"
  11. "strings"
  12. "unicode"
  13. "github.com/coreos/go-iptables/iptables"
  14. "tailscale.com/types/logger"
  15. "tailscale.com/version/distro"
  16. )
  17. func init() {
  18. isNotExistError = func(err error) bool {
  19. var e *iptables.Error
  20. return errors.As(err, &e) && e.IsNotExist()
  21. }
  22. }
  23. // DebugNetfilter prints debug information about iptables rules to the
  24. // provided log function.
  25. func DebugIptables(logf logger.Logf) error {
  26. // unused.
  27. return nil
  28. }
  29. // detectIptables returns the number of iptables rules that are present in the
  30. // system, ignoring the default "ACCEPT" rule present in the standard iptables
  31. // chains.
  32. //
  33. // It only returns an error when there is no iptables binary, or when iptables -S
  34. // fails. In all other cases, it returns the number of non-default rules.
  35. //
  36. // If the iptables binary is not found, it returns an underlying exec.ErrNotFound
  37. // error.
  38. func detectIptables() (int, error) {
  39. // run "iptables -S" to get the list of rules using iptables
  40. // exec.Command returns an error if the binary is not found
  41. cmd := exec.Command("iptables", "-S")
  42. output, err := cmd.Output()
  43. ip6cmd := exec.Command("ip6tables", "-S")
  44. ip6output, ip6err := ip6cmd.Output()
  45. var allLines []string
  46. outputStr := string(output)
  47. lines := strings.Split(outputStr, "\n")
  48. ip6outputStr := string(ip6output)
  49. ip6lines := strings.Split(ip6outputStr, "\n")
  50. switch {
  51. case err == nil && ip6err == nil:
  52. allLines = append(lines, ip6lines...)
  53. case err == nil && ip6err != nil:
  54. allLines = lines
  55. case err != nil && ip6err == nil:
  56. allLines = ip6lines
  57. default:
  58. return 0, FWModeNotSupportedError{
  59. Mode: FirewallModeIPTables,
  60. Err: fmt.Errorf("iptables command run fail: %w", errors.Join(err, ip6err)),
  61. }
  62. }
  63. // count the number of non-default rules
  64. count := 0
  65. for _, line := range allLines {
  66. trimmedLine := strings.TrimLeftFunc(line, unicode.IsSpace)
  67. if line != "" && strings.HasPrefix(trimmedLine, "-A") {
  68. // if the line is not empty and starts with "-A", it is a rule appended not default
  69. count++
  70. }
  71. }
  72. // return the count of non-default rules
  73. return count, nil
  74. }
  75. // newIPTablesRunner constructs a NetfilterRunner that programs iptables rules.
  76. // If the underlying iptables library fails to initialize, that error is
  77. // returned. The runner probes for IPv6 support once at initialization time and
  78. // if not found, no IPv6 rules will be modified for the lifetime of the runner.
  79. func newIPTablesRunner(logf logger.Logf) (*iptablesRunner, error) {
  80. ipt4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
  81. if err != nil {
  82. return nil, err
  83. }
  84. supportsV6, supportsV6NAT, supportsV6Filter := false, false, false
  85. v6err := CheckIPv6(logf)
  86. ip6terr := checkIP6TablesExists()
  87. var ipt6 *iptables.IPTables
  88. switch {
  89. case v6err != nil:
  90. logf("disabling tunneled IPv6 due to system IPv6 config: %v", v6err)
  91. case ip6terr != nil:
  92. logf("disabling tunneled IPv6 due to missing ip6tables: %v", ip6terr)
  93. default:
  94. supportsV6 = true
  95. ipt6, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
  96. if err != nil {
  97. return nil, err
  98. }
  99. supportsV6Filter = checkSupportsV6Filter(ipt6, logf)
  100. supportsV6NAT = checkSupportsV6NAT(ipt6, logf)
  101. logf("netfilter running in iptables mode v6 = %v, v6filter = %v, v6nat = %v", supportsV6, supportsV6Filter, supportsV6NAT)
  102. }
  103. return &iptablesRunner{
  104. ipt4: ipt4,
  105. ipt6: ipt6,
  106. v6Available: supportsV6,
  107. v6NATAvailable: supportsV6NAT,
  108. v6FilterAvailable: supportsV6Filter}, nil
  109. }
  110. // checkSupportsV6Filter returns whether the system has a "filter" table in the
  111. // IPv6 tables. Some container environments such as GitHub codespaces have
  112. // limited local IPv6 support, and containers containing ip6tables, but do not
  113. // have kernel support for IPv6 filtering.
  114. // We will not set ip6tables rules in these instances.
  115. func checkSupportsV6Filter(ipt *iptables.IPTables, logf logger.Logf) bool {
  116. if ipt == nil {
  117. return false
  118. }
  119. _, filterListErr := ipt.ListChains("filter")
  120. if filterListErr == nil {
  121. return true
  122. }
  123. logf("ip6tables filtering is not supported on this host: %v", filterListErr)
  124. return false
  125. }
  126. // checkSupportsV6NAT returns whether the system has a "nat" table in the
  127. // IPv6 netfilter stack.
  128. //
  129. // The nat table was added after the initial release of ipv6
  130. // netfilter, so some older distros ship a kernel that can't NAT IPv6
  131. // traffic.
  132. // ipt must be initialized for IPv6.
  133. func checkSupportsV6NAT(ipt *iptables.IPTables, logf logger.Logf) bool {
  134. if ipt == nil || ipt.Proto() != iptables.ProtocolIPv6 {
  135. return false
  136. }
  137. _, natListErr := ipt.ListChains("nat")
  138. if natListErr == nil {
  139. return true
  140. }
  141. // TODO (irbekrm): the following two checks were added before the check
  142. // above that verifies that nat chains can be listed. It is a
  143. // container-friendly check (see
  144. // https://github.com/tailscale/tailscale/issues/11344), but also should
  145. // be good enough on its own in other environments. If we never observe
  146. // it falsely succeed, let's remove the other two checks.
  147. bs, err := os.ReadFile("/proc/net/ip6_tables_names")
  148. if err != nil {
  149. return false
  150. }
  151. if bytes.Contains(bs, []byte("nat\n")) {
  152. logf("[unexpected] listing nat chains failed, but /proc/net/ip6_tables_name reports a nat table existing")
  153. return true
  154. }
  155. if exec.Command("modprobe", "ip6table_nat").Run() == nil {
  156. logf("[unexpected] listing nat chains failed, but modprobe ip6table_nat succeeded")
  157. return true
  158. }
  159. return false
  160. }
  161. func init() {
  162. hookIPTablesCleanup.Set(ipTablesCleanUp)
  163. }
  164. // ipTablesCleanUp removes all Tailscale added iptables rules.
  165. // Any errors that occur are logged to the provided logf.
  166. func ipTablesCleanUp(logf logger.Logf) {
  167. switch distro.Get() {
  168. case distro.Gokrazy, distro.JetKVM:
  169. // These use nftables and don't have the "iptables" command.
  170. // Avoid log spam on cleanup. (#12277)
  171. return
  172. }
  173. err := clearRules(iptables.ProtocolIPv4, logf)
  174. if err != nil {
  175. logf("linuxfw: clear iptables: %v", err)
  176. }
  177. err = clearRules(iptables.ProtocolIPv6, logf)
  178. if err != nil {
  179. logf("linuxfw: clear ip6tables: %v", err)
  180. }
  181. }
  182. // clearRules clears all the iptables rules created by Tailscale
  183. // for the given protocol. If error occurs, it's logged but not returned.
  184. func clearRules(proto iptables.Protocol, logf logger.Logf) error {
  185. ipt, err := iptables.NewWithProtocol(proto)
  186. if err != nil {
  187. return err
  188. }
  189. var errs []error
  190. if err := delTSHook(ipt, "filter", "INPUT", logf); err != nil {
  191. errs = append(errs, err)
  192. }
  193. if err := delTSHook(ipt, "filter", "FORWARD", logf); err != nil {
  194. errs = append(errs, err)
  195. }
  196. if err := delTSHook(ipt, "nat", "POSTROUTING", logf); err != nil {
  197. errs = append(errs, err)
  198. }
  199. if err := delChain(ipt, "filter", "ts-input"); err != nil {
  200. errs = append(errs, err)
  201. }
  202. if err := delChain(ipt, "filter", "ts-forward"); err != nil {
  203. errs = append(errs, err)
  204. }
  205. if err := delChain(ipt, "nat", "ts-postrouting"); err != nil {
  206. errs = append(errs, err)
  207. }
  208. return errors.Join(errs...)
  209. }