iptables.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. package iptables
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "os/exec"
  7. "regexp"
  8. "strconv"
  9. "strings"
  10. "sync"
  11. "time"
  12. "github.com/sirupsen/logrus"
  13. )
  14. // Action signifies the iptable action.
  15. type Action string
  16. // Policy is the default iptable policies
  17. type Policy string
  18. // Table refers to Nat, Filter or Mangle.
  19. type Table string
  20. // IPVersion refers to IP version, v4 or v6
  21. type IPVersion string
  22. const (
  23. // Append appends the rule at the end of the chain.
  24. Append Action = "-A"
  25. // Delete deletes the rule from the chain.
  26. Delete Action = "-D"
  27. // Insert inserts the rule at the top of the chain.
  28. Insert Action = "-I"
  29. // Nat table is used for nat translation rules.
  30. Nat Table = "nat"
  31. // Filter table is used for filter rules.
  32. Filter Table = "filter"
  33. // Mangle table is used for mangling the packet.
  34. Mangle Table = "mangle"
  35. // Drop is the default iptables DROP policy
  36. Drop Policy = "DROP"
  37. // Accept is the default iptables ACCEPT policy
  38. Accept Policy = "ACCEPT"
  39. // IPv4 is version 4
  40. IPv4 IPVersion = "IPV4"
  41. // IPv6 is version 6
  42. IPv6 IPVersion = "IPV6"
  43. )
  44. var (
  45. iptablesPath string
  46. ip6tablesPath string
  47. supportsXlock = false
  48. supportsCOpt = false
  49. xLockWaitMsg = "Another app is currently holding the xtables lock"
  50. // used to lock iptables commands if xtables lock is not supported
  51. bestEffortLock sync.Mutex
  52. // ErrIptablesNotFound is returned when the rule is not found.
  53. ErrIptablesNotFound = errors.New("Iptables not found")
  54. initOnce sync.Once
  55. )
  56. // IPTable defines struct with IPVersion
  57. type IPTable struct {
  58. Version IPVersion
  59. }
  60. // ChainInfo defines the iptables chain.
  61. type ChainInfo struct {
  62. Name string
  63. Table Table
  64. HairpinMode bool
  65. IPTable IPTable
  66. }
  67. // ChainError is returned to represent errors during ip table operation.
  68. type ChainError struct {
  69. Chain string
  70. Output []byte
  71. }
  72. func (e ChainError) Error() string {
  73. return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
  74. }
  75. func probe() {
  76. path, err := exec.LookPath("iptables")
  77. if err != nil {
  78. logrus.Warnf("Failed to find iptables: %v", err)
  79. return
  80. }
  81. if out, err := exec.Command(path, "--wait", "-t", "nat", "-L", "-n").CombinedOutput(); err != nil {
  82. logrus.Warnf("Running iptables --wait -t nat -L -n failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
  83. }
  84. _, err = exec.LookPath("ip6tables")
  85. if err != nil {
  86. logrus.Warnf("Failed to find ip6tables: %v", err)
  87. return
  88. }
  89. }
  90. func initFirewalld() {
  91. if err := FirewalldInit(); err != nil {
  92. logrus.Debugf("Fail to initialize firewalld: %v, using raw iptables instead", err)
  93. }
  94. }
  95. func detectIptables() {
  96. path, err := exec.LookPath("iptables")
  97. if err != nil {
  98. return
  99. }
  100. iptablesPath = path
  101. path, err = exec.LookPath("ip6tables")
  102. if err != nil {
  103. return
  104. }
  105. ip6tablesPath = path
  106. supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
  107. mj, mn, mc, err := GetVersion()
  108. if err != nil {
  109. logrus.Warnf("Failed to read iptables version: %v", err)
  110. return
  111. }
  112. supportsCOpt = supportsCOption(mj, mn, mc)
  113. }
  114. func initDependencies() {
  115. probe()
  116. initFirewalld()
  117. detectIptables()
  118. }
  119. func initCheck() error {
  120. initOnce.Do(initDependencies)
  121. if iptablesPath == "" {
  122. return ErrIptablesNotFound
  123. }
  124. return nil
  125. }
  126. // GetIptable returns an instance of IPTable with specified version
  127. func GetIptable(version IPVersion) *IPTable {
  128. return &IPTable{Version: version}
  129. }
  130. // NewChain adds a new chain to ip table.
  131. func (iptable IPTable) NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) {
  132. c := &ChainInfo{
  133. Name: name,
  134. Table: table,
  135. HairpinMode: hairpinMode,
  136. IPTable: iptable,
  137. }
  138. if string(c.Table) == "" {
  139. c.Table = Filter
  140. }
  141. // Add chain if it doesn't exist
  142. if _, err := iptable.Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
  143. if output, err := iptable.Raw("-t", string(c.Table), "-N", c.Name); err != nil {
  144. return nil, err
  145. } else if len(output) != 0 {
  146. return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
  147. }
  148. }
  149. return c, nil
  150. }
  151. // LoopbackByVersion returns loopback address by version
  152. func (iptable IPTable) LoopbackByVersion() string {
  153. if iptable.Version == IPv6 {
  154. return "::1/128"
  155. }
  156. return "127.0.0.0/8"
  157. }
  158. // ProgramChain is used to add rules to a chain
  159. func (iptable IPTable) ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error {
  160. if c.Name == "" {
  161. return errors.New("Could not program chain, missing chain name")
  162. }
  163. // Either add or remove the interface from the firewalld zone
  164. if firewalldRunning {
  165. if enable {
  166. if err := AddInterfaceFirewalld(bridgeName); err != nil {
  167. return err
  168. }
  169. } else {
  170. if err := DelInterfaceFirewalld(bridgeName); err != nil {
  171. return err
  172. }
  173. }
  174. }
  175. switch c.Table {
  176. case Nat:
  177. preroute := []string{
  178. "-m", "addrtype",
  179. "--dst-type", "LOCAL",
  180. "-j", c.Name}
  181. if !iptable.Exists(Nat, "PREROUTING", preroute...) && enable {
  182. if err := c.Prerouting(Append, preroute...); err != nil {
  183. return fmt.Errorf("Failed to inject %s in PREROUTING chain: %s", c.Name, err)
  184. }
  185. } else if iptable.Exists(Nat, "PREROUTING", preroute...) && !enable {
  186. if err := c.Prerouting(Delete, preroute...); err != nil {
  187. return fmt.Errorf("Failed to remove %s in PREROUTING chain: %s", c.Name, err)
  188. }
  189. }
  190. output := []string{
  191. "-m", "addrtype",
  192. "--dst-type", "LOCAL",
  193. "-j", c.Name}
  194. if !hairpinMode {
  195. output = append(output, "!", "--dst", iptable.LoopbackByVersion())
  196. }
  197. if !iptable.Exists(Nat, "OUTPUT", output...) && enable {
  198. if err := c.Output(Append, output...); err != nil {
  199. return fmt.Errorf("Failed to inject %s in OUTPUT chain: %s", c.Name, err)
  200. }
  201. } else if iptable.Exists(Nat, "OUTPUT", output...) && !enable {
  202. if err := c.Output(Delete, output...); err != nil {
  203. return fmt.Errorf("Failed to inject %s in OUTPUT chain: %s", c.Name, err)
  204. }
  205. }
  206. case Filter:
  207. if bridgeName == "" {
  208. return fmt.Errorf("Could not program chain %s/%s, missing bridge name",
  209. c.Table, c.Name)
  210. }
  211. link := []string{
  212. "-o", bridgeName,
  213. "-j", c.Name}
  214. if !iptable.Exists(Filter, "FORWARD", link...) && enable {
  215. insert := append([]string{string(Insert), "FORWARD"}, link...)
  216. if output, err := iptable.Raw(insert...); err != nil {
  217. return err
  218. } else if len(output) != 0 {
  219. return fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
  220. }
  221. } else if iptable.Exists(Filter, "FORWARD", link...) && !enable {
  222. del := append([]string{string(Delete), "FORWARD"}, link...)
  223. if output, err := iptable.Raw(del...); err != nil {
  224. return err
  225. } else if len(output) != 0 {
  226. return fmt.Errorf("Could not delete linking rule from %s/%s: %s", c.Table, c.Name, output)
  227. }
  228. }
  229. establish := []string{
  230. "-o", bridgeName,
  231. "-m", "conntrack",
  232. "--ctstate", "RELATED,ESTABLISHED",
  233. "-j", "ACCEPT"}
  234. if !iptable.Exists(Filter, "FORWARD", establish...) && enable {
  235. insert := append([]string{string(Insert), "FORWARD"}, establish...)
  236. if output, err := iptable.Raw(insert...); err != nil {
  237. return err
  238. } else if len(output) != 0 {
  239. return fmt.Errorf("Could not create establish rule to %s: %s", c.Table, output)
  240. }
  241. } else if iptable.Exists(Filter, "FORWARD", establish...) && !enable {
  242. del := append([]string{string(Delete), "FORWARD"}, establish...)
  243. if output, err := iptable.Raw(del...); err != nil {
  244. return err
  245. } else if len(output) != 0 {
  246. return fmt.Errorf("Could not delete establish rule from %s: %s", c.Table, output)
  247. }
  248. }
  249. }
  250. return nil
  251. }
  252. // RemoveExistingChain removes existing chain from the table.
  253. func (iptable IPTable) RemoveExistingChain(name string, table Table) error {
  254. c := &ChainInfo{
  255. Name: name,
  256. Table: table,
  257. IPTable: iptable,
  258. }
  259. if string(c.Table) == "" {
  260. c.Table = Filter
  261. }
  262. return c.Remove()
  263. }
  264. // Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table.
  265. func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int, bridgeName string) error {
  266. iptable := GetIptable(c.IPTable.Version)
  267. daddr := ip.String()
  268. if ip.IsUnspecified() {
  269. // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
  270. // want "0.0.0.0/0". "0/0" is correctly interpreted as "any
  271. // value" by both iptables and ip6tables.
  272. daddr = "0/0"
  273. }
  274. args := []string{
  275. "-p", proto,
  276. "-d", daddr,
  277. "--dport", strconv.Itoa(port),
  278. "-j", "DNAT",
  279. "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))}
  280. if !c.HairpinMode {
  281. args = append(args, "!", "-i", bridgeName)
  282. }
  283. if err := iptable.ProgramRule(Nat, c.Name, action, args); err != nil {
  284. return err
  285. }
  286. args = []string{
  287. "!", "-i", bridgeName,
  288. "-o", bridgeName,
  289. "-p", proto,
  290. "-d", destAddr,
  291. "--dport", strconv.Itoa(destPort),
  292. "-j", "ACCEPT",
  293. }
  294. if err := iptable.ProgramRule(Filter, c.Name, action, args); err != nil {
  295. return err
  296. }
  297. args = []string{
  298. "-p", proto,
  299. "-s", destAddr,
  300. "-d", destAddr,
  301. "--dport", strconv.Itoa(destPort),
  302. "-j", "MASQUERADE",
  303. }
  304. if err := iptable.ProgramRule(Nat, "POSTROUTING", action, args); err != nil {
  305. return err
  306. }
  307. if proto == "sctp" {
  308. // Linux kernel v4.9 and below enables NETIF_F_SCTP_CRC for veth by
  309. // the following commit.
  310. // This introduces a problem when conbined with a physical NIC without
  311. // NETIF_F_SCTP_CRC. As for a workaround, here we add an iptables entry
  312. // to fill the checksum.
  313. //
  314. // https://github.com/torvalds/linux/commit/c80fafbbb59ef9924962f83aac85531039395b18
  315. args = []string{
  316. "-p", proto,
  317. "--sport", strconv.Itoa(destPort),
  318. "-j", "CHECKSUM",
  319. "--checksum-fill",
  320. }
  321. if err := iptable.ProgramRule(Mangle, "POSTROUTING", action, args); err != nil {
  322. return err
  323. }
  324. }
  325. return nil
  326. }
  327. // Link adds reciprocal ACCEPT rule for two supplied IP addresses.
  328. // Traffic is allowed from ip1 to ip2 and vice-versa
  329. func (c *ChainInfo) Link(action Action, ip1, ip2 net.IP, port int, proto string, bridgeName string) error {
  330. iptable := GetIptable(c.IPTable.Version)
  331. // forward
  332. args := []string{
  333. "-i", bridgeName, "-o", bridgeName,
  334. "-p", proto,
  335. "-s", ip1.String(),
  336. "-d", ip2.String(),
  337. "--dport", strconv.Itoa(port),
  338. "-j", "ACCEPT",
  339. }
  340. if err := iptable.ProgramRule(Filter, c.Name, action, args); err != nil {
  341. return err
  342. }
  343. // reverse
  344. args[7], args[9] = args[9], args[7]
  345. args[10] = "--sport"
  346. return iptable.ProgramRule(Filter, c.Name, action, args)
  347. }
  348. // ProgramRule adds the rule specified by args only if the
  349. // rule is not already present in the chain. Reciprocally,
  350. // it removes the rule only if present.
  351. func (iptable IPTable) ProgramRule(table Table, chain string, action Action, args []string) error {
  352. if iptable.Exists(table, chain, args...) != (action == Delete) {
  353. return nil
  354. }
  355. return iptable.RawCombinedOutput(append([]string{"-t", string(table), string(action), chain}, args...)...)
  356. }
  357. // Prerouting adds linking rule to nat/PREROUTING chain.
  358. func (c *ChainInfo) Prerouting(action Action, args ...string) error {
  359. iptable := GetIptable(c.IPTable.Version)
  360. a := []string{"-t", string(Nat), string(action), "PREROUTING"}
  361. if len(args) > 0 {
  362. a = append(a, args...)
  363. }
  364. if output, err := iptable.Raw(a...); err != nil {
  365. return err
  366. } else if len(output) != 0 {
  367. return ChainError{Chain: "PREROUTING", Output: output}
  368. }
  369. return nil
  370. }
  371. // Output adds linking rule to an OUTPUT chain.
  372. func (c *ChainInfo) Output(action Action, args ...string) error {
  373. iptable := GetIptable(c.IPTable.Version)
  374. a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
  375. if len(args) > 0 {
  376. a = append(a, args...)
  377. }
  378. if output, err := iptable.Raw(a...); err != nil {
  379. return err
  380. } else if len(output) != 0 {
  381. return ChainError{Chain: "OUTPUT", Output: output}
  382. }
  383. return nil
  384. }
  385. // Remove removes the chain.
  386. func (c *ChainInfo) Remove() error {
  387. iptable := GetIptable(c.IPTable.Version)
  388. // Ignore errors - This could mean the chains were never set up
  389. if c.Table == Nat {
  390. c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name)
  391. c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", iptable.LoopbackByVersion(), "-j", c.Name)
  392. c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) // Created in versions <= 0.1.6
  393. c.Prerouting(Delete)
  394. c.Output(Delete)
  395. }
  396. iptable.Raw("-t", string(c.Table), "-F", c.Name)
  397. iptable.Raw("-t", string(c.Table), "-X", c.Name)
  398. return nil
  399. }
  400. // Exists checks if a rule exists
  401. func (iptable IPTable) Exists(table Table, chain string, rule ...string) bool {
  402. return iptable.exists(false, table, chain, rule...)
  403. }
  404. // ExistsNative behaves as Exists with the difference it
  405. // will always invoke `iptables` binary.
  406. func (iptable IPTable) ExistsNative(table Table, chain string, rule ...string) bool {
  407. return iptable.exists(true, table, chain, rule...)
  408. }
  409. func (iptable IPTable) exists(native bool, table Table, chain string, rule ...string) bool {
  410. f := iptable.Raw
  411. if native {
  412. f = iptable.raw
  413. }
  414. if string(table) == "" {
  415. table = Filter
  416. }
  417. if err := initCheck(); err != nil {
  418. // The exists() signature does not allow us to return an error, but at least
  419. // we can skip the (likely invalid) exec invocation.
  420. return false
  421. }
  422. if supportsCOpt {
  423. // if exit status is 0 then return true, the rule exists
  424. _, err := f(append([]string{"-t", string(table), "-C", chain}, rule...)...)
  425. return err == nil
  426. }
  427. // parse "iptables -S" for the rule (it checks rules in a specific chain
  428. // in a specific table and it is very unreliable)
  429. return iptable.existsRaw(table, chain, rule...)
  430. }
  431. func (iptable IPTable) existsRaw(table Table, chain string, rule ...string) bool {
  432. path := iptablesPath
  433. if iptable.Version == IPv6 {
  434. path = ip6tablesPath
  435. }
  436. ruleString := fmt.Sprintf("%s %s\n", chain, strings.Join(rule, " "))
  437. existingRules, _ := exec.Command(path, "-t", string(table), "-S", chain).Output()
  438. return strings.Contains(string(existingRules), ruleString)
  439. }
  440. // Maximum duration that an iptables operation can take
  441. // before flagging a warning.
  442. const opWarnTime = 2 * time.Second
  443. func filterOutput(start time.Time, output []byte, args ...string) []byte {
  444. // Flag operations that have taken a long time to complete
  445. opTime := time.Since(start)
  446. if opTime > opWarnTime {
  447. logrus.Warnf("xtables contention detected while running [%s]: Waited for %.2f seconds and received %q", strings.Join(args, " "), float64(opTime)/float64(time.Second), string(output))
  448. }
  449. // ignore iptables' message about xtables lock:
  450. // it is a warning, not an error.
  451. if strings.Contains(string(output), xLockWaitMsg) {
  452. output = []byte("")
  453. }
  454. // Put further filters here if desired
  455. return output
  456. }
  457. // Raw calls 'iptables' system command, passing supplied arguments.
  458. func (iptable IPTable) Raw(args ...string) ([]byte, error) {
  459. if firewalldRunning {
  460. startTime := time.Now()
  461. output, err := Passthrough(Iptables, args...)
  462. if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
  463. return filterOutput(startTime, output, args...), err
  464. }
  465. }
  466. return iptable.raw(args...)
  467. }
  468. func (iptable IPTable) raw(args ...string) ([]byte, error) {
  469. if err := initCheck(); err != nil {
  470. return nil, err
  471. }
  472. if supportsXlock {
  473. args = append([]string{"--wait"}, args...)
  474. } else {
  475. bestEffortLock.Lock()
  476. defer bestEffortLock.Unlock()
  477. }
  478. path := iptablesPath
  479. if iptable.Version == IPv6 {
  480. path = ip6tablesPath
  481. }
  482. logrus.Debugf("%s, %v", path, args)
  483. startTime := time.Now()
  484. output, err := exec.Command(path, args...).CombinedOutput()
  485. if err != nil {
  486. return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
  487. }
  488. return filterOutput(startTime, output, args...), err
  489. }
  490. // RawCombinedOutput internally calls the Raw function and returns a non nil
  491. // error if Raw returned a non nil error or a non empty output
  492. func (iptable IPTable) RawCombinedOutput(args ...string) error {
  493. if output, err := iptable.Raw(args...); err != nil || len(output) != 0 {
  494. return fmt.Errorf("%s (%v)", string(output), err)
  495. }
  496. return nil
  497. }
  498. // RawCombinedOutputNative behave as RawCombinedOutput with the difference it
  499. // will always invoke `iptables` binary
  500. func (iptable IPTable) RawCombinedOutputNative(args ...string) error {
  501. if output, err := iptable.raw(args...); err != nil || len(output) != 0 {
  502. return fmt.Errorf("%s (%v)", string(output), err)
  503. }
  504. return nil
  505. }
  506. // ExistChain checks if a chain exists
  507. func (iptable IPTable) ExistChain(chain string, table Table) bool {
  508. if _, err := iptable.Raw("-t", string(table), "-nL", chain); err == nil {
  509. return true
  510. }
  511. return false
  512. }
  513. // GetVersion reads the iptables version numbers during initialization
  514. func GetVersion() (major, minor, micro int, err error) {
  515. out, err := exec.Command(iptablesPath, "--version").CombinedOutput()
  516. if err == nil {
  517. major, minor, micro = parseVersionNumbers(string(out))
  518. }
  519. return
  520. }
  521. // SetDefaultPolicy sets the passed default policy for the table/chain
  522. func (iptable IPTable) SetDefaultPolicy(table Table, chain string, policy Policy) error {
  523. if err := iptable.RawCombinedOutput("-t", string(table), "-P", chain, string(policy)); err != nil {
  524. return fmt.Errorf("setting default policy to %v in %v chain failed: %v", policy, chain, err)
  525. }
  526. return nil
  527. }
  528. func parseVersionNumbers(input string) (major, minor, micro int) {
  529. re := regexp.MustCompile(`v\d*.\d*.\d*`)
  530. line := re.FindString(input)
  531. fmt.Sscanf(line, "v%d.%d.%d", &major, &minor, &micro)
  532. return
  533. }
  534. // iptables -C, --check option was added in v.1.4.11
  535. // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
  536. func supportsCOption(mj, mn, mc int) bool {
  537. return mj > 1 || (mj == 1 && (mn > 4 || (mn == 4 && mc >= 11)))
  538. }
  539. // AddReturnRule adds a return rule for the chain in the filter table
  540. func (iptable IPTable) AddReturnRule(chain string) error {
  541. var (
  542. table = Filter
  543. args = []string{"-j", "RETURN"}
  544. )
  545. if iptable.Exists(table, chain, args...) {
  546. return nil
  547. }
  548. err := iptable.RawCombinedOutput(append([]string{"-A", chain}, args...)...)
  549. if err != nil {
  550. return fmt.Errorf("unable to add return rule in %s chain: %s", chain, err.Error())
  551. }
  552. return nil
  553. }
  554. // EnsureJumpRule ensures the jump rule is on top
  555. func (iptable IPTable) EnsureJumpRule(fromChain, toChain string) error {
  556. var (
  557. table = Filter
  558. args = []string{"-j", toChain}
  559. )
  560. if iptable.Exists(table, fromChain, args...) {
  561. err := iptable.RawCombinedOutput(append([]string{"-D", fromChain}, args...)...)
  562. if err != nil {
  563. return fmt.Errorf("unable to remove jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
  564. }
  565. }
  566. err := iptable.RawCombinedOutput(append([]string{"-I", fromChain}, args...)...)
  567. if err != nil {
  568. return fmt.Errorf("unable to insert jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
  569. }
  570. return nil
  571. }