iptables_runner.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux
  4. package linuxfw
  5. import (
  6. "fmt"
  7. "log"
  8. "net/netip"
  9. "os/exec"
  10. "slices"
  11. "strconv"
  12. "strings"
  13. "tailscale.com/net/tsaddr"
  14. "tailscale.com/types/logger"
  15. )
  16. // isNotExistError needs to be overridden in tests that rely on distinguishing
  17. // this error, because we don't have a good way how to create a new
  18. // iptables.Error of that type.
  19. var isNotExistError = func(err error) bool { return false }
  20. type iptablesInterface interface {
  21. // Adding this interface for testing purposes so we can mock out
  22. // the iptables library, in reality this is a wrapper to *iptables.IPTables.
  23. Insert(table, chain string, pos int, args ...string) error
  24. Append(table, chain string, args ...string) error
  25. Exists(table, chain string, args ...string) (bool, error)
  26. Delete(table, chain string, args ...string) error
  27. List(table, chain string) ([]string, error)
  28. ClearChain(table, chain string) error
  29. NewChain(table, chain string) error
  30. DeleteChain(table, chain string) error
  31. }
  32. type iptablesRunner struct {
  33. ipt4 iptablesInterface
  34. ipt6 iptablesInterface
  35. v6Available bool
  36. v6NATAvailable bool
  37. v6FilterAvailable bool
  38. }
  39. func checkIP6TablesExists() error {
  40. // Some distros ship ip6tables separately from iptables.
  41. if _, err := exec.LookPath("ip6tables"); err != nil {
  42. return fmt.Errorf("path not found: %w", err)
  43. }
  44. return nil
  45. }
  46. // HasIPV6 reports true if the system supports IPv6.
  47. func (i *iptablesRunner) HasIPV6() bool {
  48. return i.v6Available
  49. }
  50. // HasIPV6Filter reports true if the system supports ip6tables filter table.
  51. func (i *iptablesRunner) HasIPV6Filter() bool {
  52. return i.v6FilterAvailable
  53. }
  54. // HasIPV6NAT reports true if the system supports IPv6 NAT.
  55. func (i *iptablesRunner) HasIPV6NAT() bool {
  56. return i.v6NATAvailable
  57. }
  58. // getIPTByAddr returns the iptablesInterface with correct IP family
  59. // that we will be using for the given address.
  60. func (i *iptablesRunner) getIPTByAddr(addr netip.Addr) iptablesInterface {
  61. nf := i.ipt4
  62. if addr.Is6() {
  63. nf = i.ipt6
  64. }
  65. return nf
  66. }
  67. // AddLoopbackRule adds an iptables rule to permit loopback traffic to
  68. // a local Tailscale IP.
  69. func (i *iptablesRunner) AddLoopbackRule(addr netip.Addr) error {
  70. if err := i.getIPTByAddr(addr).Insert("filter", "ts-input", 1, "-i", "lo", "-s", addr.String(), "-j", "ACCEPT"); err != nil {
  71. return fmt.Errorf("adding loopback allow rule for %q: %w", addr, err)
  72. }
  73. return nil
  74. }
  75. // tsChain returns the name of the tailscale sub-chain corresponding
  76. // to the given "parent" chain (e.g. INPUT, FORWARD, ...).
  77. func tsChain(chain string) string {
  78. return "ts-" + strings.ToLower(chain)
  79. }
  80. // DelLoopbackRule removes the iptables rule permitting loopback
  81. // traffic to a Tailscale IP.
  82. func (i *iptablesRunner) DelLoopbackRule(addr netip.Addr) error {
  83. if err := i.getIPTByAddr(addr).Delete("filter", "ts-input", "-i", "lo", "-s", addr.String(), "-j", "ACCEPT"); err != nil {
  84. return fmt.Errorf("deleting loopback allow rule for %q: %w", addr, err)
  85. }
  86. return nil
  87. }
  88. // getTables gets the available iptablesInterface in iptables runner.
  89. func (i *iptablesRunner) getTables() []iptablesInterface {
  90. if i.HasIPV6Filter() {
  91. return []iptablesInterface{i.ipt4, i.ipt6}
  92. }
  93. return []iptablesInterface{i.ipt4}
  94. }
  95. // getNATTables gets the available iptablesInterface in iptables runner.
  96. // If the system does not support IPv6 NAT, only the IPv4 iptablesInterface
  97. // is returned.
  98. func (i *iptablesRunner) getNATTables() []iptablesInterface {
  99. if i.HasIPV6NAT() {
  100. return i.getTables()
  101. }
  102. return []iptablesInterface{i.ipt4}
  103. }
  104. // AddHooks inserts calls to tailscale's netfilter chains in
  105. // the relevant main netfilter chains. The tailscale chains must
  106. // already exist. If they do not, an error is returned.
  107. func (i *iptablesRunner) AddHooks() error {
  108. // divert inserts a jump to the tailscale chain in the given table/chain.
  109. // If the jump already exists, it is a no-op.
  110. divert := func(ipt iptablesInterface, table, chain string) error {
  111. tsChain := tsChain(chain)
  112. args := []string{"-j", tsChain}
  113. exists, err := ipt.Exists(table, chain, args...)
  114. if err != nil {
  115. return fmt.Errorf("checking for %v in %s/%s: %w", args, table, chain, err)
  116. }
  117. if exists {
  118. return nil
  119. }
  120. if err := ipt.Insert(table, chain, 1, args...); err != nil {
  121. return fmt.Errorf("adding %v in %s/%s: %w", args, table, chain, err)
  122. }
  123. return nil
  124. }
  125. for _, ipt := range i.getTables() {
  126. if err := divert(ipt, "filter", "INPUT"); err != nil {
  127. return err
  128. }
  129. if err := divert(ipt, "filter", "FORWARD"); err != nil {
  130. return err
  131. }
  132. }
  133. for _, ipt := range i.getNATTables() {
  134. if err := divert(ipt, "nat", "POSTROUTING"); err != nil {
  135. return err
  136. }
  137. }
  138. return nil
  139. }
  140. // AddChains creates custom Tailscale chains in netfilter via iptables
  141. // if the ts-chain doesn't already exist.
  142. func (i *iptablesRunner) AddChains() error {
  143. // create creates a chain in the given table if it doesn't already exist.
  144. // If the chain already exists, it is a no-op.
  145. create := func(ipt iptablesInterface, table, chain string) error {
  146. err := ipt.ClearChain(table, chain)
  147. if isNotExistError(err) {
  148. // nonexistent chain. let's create it!
  149. return ipt.NewChain(table, chain)
  150. }
  151. if err != nil {
  152. return fmt.Errorf("setting up %s/%s: %w", table, chain, err)
  153. }
  154. return nil
  155. }
  156. for _, ipt := range i.getTables() {
  157. if err := create(ipt, "filter", "ts-input"); err != nil {
  158. return err
  159. }
  160. if err := create(ipt, "filter", "ts-forward"); err != nil {
  161. return err
  162. }
  163. }
  164. for _, ipt := range i.getNATTables() {
  165. if err := create(ipt, "nat", "ts-postrouting"); err != nil {
  166. return err
  167. }
  168. }
  169. return nil
  170. }
  171. // AddBase adds some basic processing rules to be supplemented by
  172. // later calls to other helpers.
  173. func (i *iptablesRunner) AddBase(tunname string) error {
  174. if err := i.addBase4(tunname); err != nil {
  175. return err
  176. }
  177. if i.HasIPV6Filter() {
  178. if err := i.addBase6(tunname); err != nil {
  179. return err
  180. }
  181. }
  182. return nil
  183. }
  184. // addBase4 adds some basic IPv4 processing rules to be
  185. // supplemented by later calls to other helpers.
  186. func (i *iptablesRunner) addBase4(tunname string) error {
  187. // Only allow CGNAT range traffic to come from tailscale0. There
  188. // is an exception carved out for ranges used by ChromeOS, for
  189. // which we fall out of the Tailscale chain.
  190. //
  191. // Note, this will definitely break nodes that end up using the
  192. // CGNAT range for other purposes :(.
  193. args := []string{"!", "-i", tunname, "-s", tsaddr.ChromeOSVMRange().String(), "-j", "RETURN"}
  194. if err := i.ipt4.Append("filter", "ts-input", args...); err != nil {
  195. return fmt.Errorf("adding %v in v4/filter/ts-input: %w", args, err)
  196. }
  197. args = []string{"!", "-i", tunname, "-s", tsaddr.CGNATRange().String(), "-j", "DROP"}
  198. if err := i.ipt4.Append("filter", "ts-input", args...); err != nil {
  199. return fmt.Errorf("adding %v in v4/filter/ts-input: %w", args, err)
  200. }
  201. // Explicitly allow all other inbound traffic to the tun interface
  202. args = []string{"-i", tunname, "-j", "ACCEPT"}
  203. if err := i.ipt4.Append("filter", "ts-input", args...); err != nil {
  204. return fmt.Errorf("adding %v in v4/filter/ts-input: %w", args, err)
  205. }
  206. // Forward all traffic from the Tailscale interface, and drop
  207. // traffic to the tailscale interface by default. We use packet
  208. // marks here so both filter/FORWARD and nat/POSTROUTING can match
  209. // on these packets of interest.
  210. //
  211. // In particular, we only want to apply SNAT rules in
  212. // nat/POSTROUTING to packets that originated from the Tailscale
  213. // interface, but we can't match on the inbound interface in
  214. // POSTROUTING. So instead, we match on the inbound interface in
  215. // filter/FORWARD, and set a packet mark that nat/POSTROUTING can
  216. // use to effectively run that same test again.
  217. args = []string{"-i", tunname, "-j", "MARK", "--set-mark", subnetRouteMark + "/" + fwmarkMask}
  218. if err := i.ipt4.Append("filter", "ts-forward", args...); err != nil {
  219. return fmt.Errorf("adding %v in v4/filter/ts-forward: %w", args, err)
  220. }
  221. args = []string{"-m", "mark", "--mark", subnetRouteMark + "/" + fwmarkMask, "-j", "ACCEPT"}
  222. if err := i.ipt4.Append("filter", "ts-forward", args...); err != nil {
  223. return fmt.Errorf("adding %v in v4/filter/ts-forward: %w", args, err)
  224. }
  225. args = []string{"-o", tunname, "-s", tsaddr.CGNATRange().String(), "-j", "DROP"}
  226. if err := i.ipt4.Append("filter", "ts-forward", args...); err != nil {
  227. return fmt.Errorf("adding %v in v4/filter/ts-forward: %w", args, err)
  228. }
  229. args = []string{"-o", tunname, "-j", "ACCEPT"}
  230. if err := i.ipt4.Append("filter", "ts-forward", args...); err != nil {
  231. return fmt.Errorf("adding %v in v4/filter/ts-forward: %w", args, err)
  232. }
  233. return nil
  234. }
  235. func (i *iptablesRunner) AddDNATRule(origDst, dst netip.Addr) error {
  236. table := i.getIPTByAddr(dst)
  237. return table.Insert("nat", "PREROUTING", 1, "--destination", origDst.String(), "-j", "DNAT", "--to-destination", dst.String())
  238. }
  239. // EnsureSNATForDst sets up firewall to ensure that all traffic aimed for dst, has its source ip set to src:
  240. // - creates a SNAT rule if not already present
  241. // - ensures that any no longer valid SNAT rules for the same dst are removed
  242. func (i *iptablesRunner) EnsureSNATForDst(src, dst netip.Addr) error {
  243. table := i.getIPTByAddr(dst)
  244. rules, err := table.List("nat", "POSTROUTING")
  245. if err != nil {
  246. return fmt.Errorf("error listing rules: %v", err)
  247. }
  248. // iptables accept either address or a CIDR value for the --destination flag, but converts an address to /32
  249. // CIDR. Explicitly passing a /32 CIDR made it possible to test this rule.
  250. dstPrefix, err := dst.Prefix(32)
  251. if err != nil {
  252. return fmt.Errorf("error calculating prefix of dst %v: %v", dst, err)
  253. }
  254. // wantsArgsPrefix is the prefix of the SNAT rule for the provided destination.
  255. // We should only have one POSTROUTING rule with this prefix.
  256. wantsArgsPrefix := fmt.Sprintf("-d %s -j SNAT --to-source", dstPrefix.String())
  257. // wantsArgs is the actual SNAT rule that we want.
  258. wantsArgs := fmt.Sprintf("%s %s", wantsArgsPrefix, src.String())
  259. for _, r := range rules {
  260. args := argsFromPostRoutingRule(r)
  261. if strings.HasPrefix(args, wantsArgsPrefix) {
  262. if strings.HasPrefix(args, wantsArgs) {
  263. return nil
  264. }
  265. // SNAT rule matching the destination, but for a different source - delete.
  266. if err := table.Delete("nat", "POSTROUTING", strings.Split(args, " ")...); err != nil {
  267. // If we failed to delete don't crash the node- the proxy should still be functioning.
  268. log.Printf("[unexpected] error deleting rule %s: %v, please report it.", r, err)
  269. }
  270. break
  271. }
  272. }
  273. return table.Insert("nat", "POSTROUTING", 1, "-d", dstPrefix.String(), "-j", "SNAT", "--to-source", src.String())
  274. }
  275. func (i *iptablesRunner) DNATNonTailscaleTraffic(tun string, dst netip.Addr) error {
  276. table := i.getIPTByAddr(dst)
  277. return table.Insert("nat", "PREROUTING", 1, "!", "-i", tun, "-j", "DNAT", "--to-destination", dst.String())
  278. }
  279. // DNATWithLoadBalancer adds iptables rules to forward all traffic received for
  280. // originDst to the backend dsts. Traffic will be load balanced using round robin.
  281. func (i *iptablesRunner) DNATWithLoadBalancer(origDst netip.Addr, dsts []netip.Addr) error {
  282. table := i.getIPTByAddr(dsts[0])
  283. if err := table.ClearChain("nat", "PREROUTING"); err != nil && !isNotExistError(err) {
  284. // If clearing the PREROUTING chain fails, fail the whole operation. This
  285. // rule is currently only used in Kubernetes containers where a
  286. // failed container gets restarted which should hopefully fix things.
  287. return fmt.Errorf("error clearing nat PREROUTING chain: %w", err)
  288. }
  289. // If dsts contain more than one address, for n := n in range(len(dsts)..2) route packets for every nth connection to dsts[n].
  290. for i := len(dsts); i >= 2; i-- {
  291. dst := dsts[i-1] // the order in which rules for addrs are installed does not matter
  292. if err := table.Append("nat", "PREROUTING", "--destination", origDst.String(), "-m", "statistic", "--mode", "nth", "--every", fmt.Sprint(i), "--packet", "0", "-j", "DNAT", "--to-destination", dst.String()); err != nil {
  293. return fmt.Errorf("error adding DNAT rule for %s: %w", dst.String(), err)
  294. }
  295. }
  296. // If the packet falls through to this rule, we route to the first destination in the list unconditionally.
  297. return table.Append("nat", "PREROUTING", "--destination", origDst.String(), "-j", "DNAT", "--to-destination", dsts[0].String())
  298. }
  299. func (i *iptablesRunner) ClampMSSToPMTU(tun string, addr netip.Addr) error {
  300. table := i.getIPTByAddr(addr)
  301. return table.Append("mangle", "FORWARD", "-o", tun, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu")
  302. }
  303. // addBase6 adds some basic IPv6 processing rules to be
  304. // supplemented by later calls to other helpers.
  305. func (i *iptablesRunner) addBase6(tunname string) error {
  306. // TODO: only allow traffic from Tailscale's ULA range to come
  307. // from tailscale0.
  308. // Explicitly allow all other inbound traffic to the tun interface
  309. args := []string{"-i", tunname, "-j", "ACCEPT"}
  310. if err := i.ipt6.Append("filter", "ts-input", args...); err != nil {
  311. return fmt.Errorf("adding %v in v6/filter/ts-input: %w", args, err)
  312. }
  313. args = []string{"-i", tunname, "-j", "MARK", "--set-mark", subnetRouteMark + "/" + fwmarkMask}
  314. if err := i.ipt6.Append("filter", "ts-forward", args...); err != nil {
  315. return fmt.Errorf("adding %v in v6/filter/ts-forward: %w", args, err)
  316. }
  317. args = []string{"-m", "mark", "--mark", subnetRouteMark + "/" + fwmarkMask, "-j", "ACCEPT"}
  318. if err := i.ipt6.Append("filter", "ts-forward", args...); err != nil {
  319. return fmt.Errorf("adding %v in v6/filter/ts-forward: %w", args, err)
  320. }
  321. // TODO: drop forwarded traffic to tailscale0 from tailscale's ULA
  322. // (see corresponding IPv4 CGNAT rule).
  323. args = []string{"-o", tunname, "-j", "ACCEPT"}
  324. if err := i.ipt6.Append("filter", "ts-forward", args...); err != nil {
  325. return fmt.Errorf("adding %v in v6/filter/ts-forward: %w", args, err)
  326. }
  327. return nil
  328. }
  329. // DelChains removes the custom Tailscale chains from netfilter via iptables.
  330. func (i *iptablesRunner) DelChains() error {
  331. for _, ipt := range i.getTables() {
  332. if err := delChain(ipt, "filter", "ts-input"); err != nil {
  333. return err
  334. }
  335. if err := delChain(ipt, "filter", "ts-forward"); err != nil {
  336. return err
  337. }
  338. }
  339. for _, ipt := range i.getNATTables() {
  340. if err := delChain(ipt, "nat", "ts-postrouting"); err != nil {
  341. return err
  342. }
  343. }
  344. return nil
  345. }
  346. // DelBase empties but does not remove custom Tailscale chains from
  347. // netfilter via iptables.
  348. func (i *iptablesRunner) DelBase() error {
  349. del := func(ipt iptablesInterface, table, chain string) error {
  350. if err := ipt.ClearChain(table, chain); err != nil {
  351. if isNotExistError(err) {
  352. // nonexistent chain. That's fine, since it's
  353. // the desired state anyway.
  354. return nil
  355. }
  356. return fmt.Errorf("flushing %s/%s: %w", table, chain, err)
  357. }
  358. return nil
  359. }
  360. for _, ipt := range i.getTables() {
  361. if err := del(ipt, "filter", "ts-input"); err != nil {
  362. return err
  363. }
  364. if err := del(ipt, "filter", "ts-forward"); err != nil {
  365. return err
  366. }
  367. }
  368. for _, ipt := range i.getNATTables() {
  369. if err := del(ipt, "nat", "ts-postrouting"); err != nil {
  370. return err
  371. }
  372. }
  373. return nil
  374. }
  375. // DelHooks deletes the calls to tailscale's netfilter chains
  376. // in the relevant main netfilter chains.
  377. func (i *iptablesRunner) DelHooks(logf logger.Logf) error {
  378. for _, ipt := range i.getTables() {
  379. if err := delTSHook(ipt, "filter", "INPUT", logf); err != nil {
  380. return err
  381. }
  382. if err := delTSHook(ipt, "filter", "FORWARD", logf); err != nil {
  383. return err
  384. }
  385. }
  386. for _, ipt := range i.getNATTables() {
  387. if err := delTSHook(ipt, "nat", "POSTROUTING", logf); err != nil {
  388. return err
  389. }
  390. }
  391. return nil
  392. }
  393. // AddSNATRule adds a netfilter rule to SNAT traffic destined for
  394. // local subnets.
  395. func (i *iptablesRunner) AddSNATRule() error {
  396. args := []string{"-m", "mark", "--mark", subnetRouteMark + "/" + fwmarkMask, "-j", "MASQUERADE"}
  397. for _, ipt := range i.getNATTables() {
  398. if err := ipt.Append("nat", "ts-postrouting", args...); err != nil {
  399. return fmt.Errorf("adding %v in nat/ts-postrouting: %w", args, err)
  400. }
  401. }
  402. return nil
  403. }
  404. // DelSNATRule removes the netfilter rule to SNAT traffic destined for
  405. // local subnets. An error is returned if the rule does not exist.
  406. func (i *iptablesRunner) DelSNATRule() error {
  407. args := []string{"-m", "mark", "--mark", subnetRouteMark + "/" + fwmarkMask, "-j", "MASQUERADE"}
  408. for _, ipt := range i.getNATTables() {
  409. if err := ipt.Delete("nat", "ts-postrouting", args...); err != nil {
  410. return fmt.Errorf("deleting %v in nat/ts-postrouting: %w", args, err)
  411. }
  412. }
  413. return nil
  414. }
  415. func statefulRuleArgs(tunname string) []string {
  416. return []string{"-o", tunname, "-m", "conntrack", "!", "--ctstate", "ESTABLISHED,RELATED", "-j", "DROP"}
  417. }
  418. // AddStatefulRule adds a netfilter rule for stateful packet filtering using
  419. // conntrack.
  420. func (i *iptablesRunner) AddStatefulRule(tunname string) error {
  421. // Drop packets that are destined for the tailscale interface if
  422. // they're a new connection, per conntrack, to prevent hosts on the
  423. // same subnet from being able to use this device as a way to forward
  424. // packets on to the Tailscale network.
  425. //
  426. // The conntrack states are:
  427. // NEW A packet which creates a new connection.
  428. // ESTABLISHED A packet which belongs to an existing connection
  429. // (i.e., a reply packet, or outgoing packet on a
  430. // connection which has seen replies).
  431. // RELATED A packet which is related to, but not part of, an
  432. // existing connection, such as an ICMP error.
  433. // INVALID A packet which could not be identified for some
  434. // reason: this includes running out of memory and ICMP
  435. // errors which don't correspond to any known
  436. // connection. Generally these packets should be
  437. // dropped.
  438. //
  439. // We drop NEW packets to prevent connections from coming "into"
  440. // Tailscale from other hosts on the same network segment; we drop
  441. // INVALID packets as well.
  442. args := statefulRuleArgs(tunname)
  443. for _, ipt := range i.getTables() {
  444. // First, find the final "accept" rule.
  445. rules, err := ipt.List("filter", "ts-forward")
  446. if err != nil {
  447. return fmt.Errorf("listing rules in filter/ts-forward: %w", err)
  448. }
  449. want := fmt.Sprintf("-A %s -o %s -j ACCEPT", "ts-forward", tunname)
  450. pos := slices.Index(rules, want)
  451. if pos < 0 {
  452. return fmt.Errorf("couldn't find final ACCEPT rule in filter/ts-forward")
  453. }
  454. if err := ipt.Insert("filter", "ts-forward", pos, args...); err != nil {
  455. return fmt.Errorf("adding %v in filter/ts-forward: %w", args, err)
  456. }
  457. }
  458. return nil
  459. }
  460. // DelStatefulRule removes the netfilter rule for stateful packet filtering
  461. // using conntrack.
  462. func (i *iptablesRunner) DelStatefulRule(tunname string) error {
  463. args := statefulRuleArgs(tunname)
  464. for _, ipt := range i.getTables() {
  465. if err := ipt.Delete("filter", "ts-forward", args...); err != nil {
  466. return fmt.Errorf("deleting %v in filter/ts-forward: %w", args, err)
  467. }
  468. }
  469. return nil
  470. }
  471. // buildMagicsockPortRule generates the string slice containing the arguments
  472. // to describe a rule accepting traffic on a particular port to iptables. It is
  473. // separated out here to avoid repetition in AddMagicsockPortRule and
  474. // RemoveMagicsockPortRule, since it is important that the same rule is passed
  475. // to Append() and Delete().
  476. func buildMagicsockPortRule(port uint16) []string {
  477. return []string{"-p", "udp", "--dport", strconv.FormatUint(uint64(port), 10), "-j", "ACCEPT"}
  478. }
  479. // AddMagicsockPortRule adds a rule to iptables to allow incoming traffic on
  480. // the specified UDP port, so magicsock can accept incoming connections.
  481. // network must be either "udp4" or "udp6" - this determines whether the rule
  482. // is added for IPv4 or IPv6.
  483. func (i *iptablesRunner) AddMagicsockPortRule(port uint16, network string) error {
  484. var ipt iptablesInterface
  485. switch network {
  486. case "udp4":
  487. ipt = i.ipt4
  488. case "udp6":
  489. ipt = i.ipt6
  490. default:
  491. return fmt.Errorf("unsupported network %s", network)
  492. }
  493. args := buildMagicsockPortRule(port)
  494. if err := ipt.Append("filter", "ts-input", args...); err != nil {
  495. return fmt.Errorf("adding %v in filter/ts-input: %w", args, err)
  496. }
  497. return nil
  498. }
  499. // DelMagicsockPortRule removes a rule added by AddMagicsockPortRule to accept
  500. // incoming traffic on a particular UDP port.
  501. // network must be either "udp4" or "udp6" - this determines whether the rule
  502. // is removed for IPv4 or IPv6.
  503. func (i *iptablesRunner) DelMagicsockPortRule(port uint16, network string) error {
  504. var ipt iptablesInterface
  505. switch network {
  506. case "udp4":
  507. ipt = i.ipt4
  508. case "udp6":
  509. ipt = i.ipt6
  510. default:
  511. return fmt.Errorf("unsupported network %s", network)
  512. }
  513. args := buildMagicsockPortRule(port)
  514. if err := ipt.Delete("filter", "ts-input", args...); err != nil {
  515. return fmt.Errorf("removing %v in filter/ts-input: %w", args, err)
  516. }
  517. return nil
  518. }
  519. // delTSHook deletes hook in a chain that jumps to a ts-chain. If the hook does not
  520. // exist, it's a no-op since the desired state is already achieved but we log the
  521. // error because error code from the iptables module resists unwrapping.
  522. func delTSHook(ipt iptablesInterface, table, chain string, logf logger.Logf) error {
  523. tsChain := tsChain(chain)
  524. args := []string{"-j", tsChain}
  525. if err := ipt.Delete(table, chain, args...); err != nil && !isNotExistError(err) {
  526. return fmt.Errorf("deleting %v in %s/%s: %v", args, table, chain, err)
  527. }
  528. return nil
  529. }
  530. // delChain flushes and deletes a chain. If the chain does not exist, it's a no-op
  531. // since the desired state is already achieved. otherwise, it returns an error.
  532. func delChain(ipt iptablesInterface, table, chain string) error {
  533. if err := ipt.ClearChain(table, chain); err != nil {
  534. if isNotExistError(err) {
  535. // nonexistent chain. nothing to do.
  536. return nil
  537. }
  538. return fmt.Errorf("flushing %s/%s: %w", table, chain, err)
  539. }
  540. if err := ipt.DeleteChain(table, chain); err != nil {
  541. return fmt.Errorf("deleting %s/%s: %w", table, chain, err)
  542. }
  543. return nil
  544. }
  545. // argsFromPostRoutingRule accepts a rule as returned by iptables.List and, if it is a rule from POSTROUTING chain,
  546. // returns the args part, else returns the original rule.
  547. func argsFromPostRoutingRule(r string) string {
  548. args, _ := strings.CutPrefix(r, "-A POSTROUTING ")
  549. return args
  550. }