iptables.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. package iptables
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "os/exec"
  7. "regexp"
  8. "strconv"
  9. "strings"
  10. "sync"
  11. "github.com/Sirupsen/logrus"
  12. )
  13. // Action signifies the iptable action.
  14. type Action string
  15. // Table refers to Nat, Filter or Mangle.
  16. type Table string
  17. const (
  18. // Append appends the rule at the end of the chain.
  19. Append Action = "-A"
  20. // Delete deletes the rule from the chain.
  21. Delete Action = "-D"
  22. // Insert inserts the rule at the top of the chain.
  23. Insert Action = "-I"
  24. // Nat table is used for nat translation rules.
  25. Nat Table = "nat"
  26. // Filter table is used for filter rules.
  27. Filter Table = "filter"
  28. // Mangle table is used for mangling the packet.
  29. Mangle Table = "mangle"
  30. )
  31. var (
  32. iptablesPath string
  33. supportsXlock = false
  34. supportsCOpt = false
  35. // used to lock iptables commands if xtables lock is not supported
  36. bestEffortLock sync.Mutex
  37. // ErrIptablesNotFound is returned when the rule is not found.
  38. ErrIptablesNotFound = errors.New("Iptables not found")
  39. probeOnce sync.Once
  40. firewalldOnce sync.Once
  41. )
  42. // ChainInfo defines the iptables chain.
  43. type ChainInfo struct {
  44. Name string
  45. Table Table
  46. HairpinMode bool
  47. }
  48. // ChainError is returned to represent errors during ip table operation.
  49. type ChainError struct {
  50. Chain string
  51. Output []byte
  52. }
  53. func (e ChainError) Error() string {
  54. return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
  55. }
  56. func probe() {
  57. if out, err := exec.Command("modprobe", "-va", "nf_nat").CombinedOutput(); err != nil {
  58. logrus.Warnf("Running modprobe nf_nat failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
  59. }
  60. if out, err := exec.Command("modprobe", "-va", "xt_conntrack").CombinedOutput(); err != nil {
  61. logrus.Warnf("Running modprobe xt_conntrack failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
  62. }
  63. }
  64. func initFirewalld() {
  65. if err := FirewalldInit(); err != nil {
  66. logrus.Debugf("Fail to initialize firewalld: %v, using raw iptables instead", err)
  67. }
  68. }
  69. func initCheck() error {
  70. if iptablesPath == "" {
  71. probeOnce.Do(probe)
  72. firewalldOnce.Do(initFirewalld)
  73. path, err := exec.LookPath("iptables")
  74. if err != nil {
  75. return ErrIptablesNotFound
  76. }
  77. iptablesPath = path
  78. supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
  79. mj, mn, mc, err := GetVersion()
  80. if err != nil {
  81. logrus.Warnf("Failed to read iptables version: %v", err)
  82. return nil
  83. }
  84. supportsCOpt = supportsCOption(mj, mn, mc)
  85. }
  86. return nil
  87. }
  88. // NewChain adds a new chain to ip table.
  89. func NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) {
  90. c := &ChainInfo{
  91. Name: name,
  92. Table: table,
  93. HairpinMode: hairpinMode,
  94. }
  95. if string(c.Table) == "" {
  96. c.Table = Filter
  97. }
  98. // Add chain if it doesn't exist
  99. if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
  100. if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil {
  101. return nil, err
  102. } else if len(output) != 0 {
  103. return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
  104. }
  105. }
  106. return c, nil
  107. }
  108. // ProgramChain is used to add rules to a chain
  109. func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error {
  110. if c.Name == "" {
  111. return fmt.Errorf("Could not program chain, missing chain name.")
  112. }
  113. switch c.Table {
  114. case Nat:
  115. preroute := []string{
  116. "-m", "addrtype",
  117. "--dst-type", "LOCAL",
  118. "-j", c.Name}
  119. if !Exists(Nat, "PREROUTING", preroute...) && enable {
  120. if err := c.Prerouting(Append, preroute...); err != nil {
  121. return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
  122. }
  123. } else if Exists(Nat, "PREROUTING", preroute...) && !enable {
  124. if err := c.Prerouting(Delete, preroute...); err != nil {
  125. return fmt.Errorf("Failed to remove docker in PREROUTING chain: %s", err)
  126. }
  127. }
  128. output := []string{
  129. "-m", "addrtype",
  130. "--dst-type", "LOCAL",
  131. "-j", c.Name}
  132. if !hairpinMode {
  133. output = append(output, "!", "--dst", "127.0.0.0/8")
  134. }
  135. if !Exists(Nat, "OUTPUT", output...) && enable {
  136. if err := c.Output(Append, output...); err != nil {
  137. return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
  138. }
  139. } else if Exists(Nat, "OUTPUT", output...) && !enable {
  140. if err := c.Output(Delete, output...); err != nil {
  141. return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
  142. }
  143. }
  144. case Filter:
  145. if bridgeName == "" {
  146. return fmt.Errorf("Could not program chain %s/%s, missing bridge name.",
  147. c.Table, c.Name)
  148. }
  149. link := []string{
  150. "-o", bridgeName,
  151. "-j", c.Name}
  152. if !Exists(Filter, "FORWARD", link...) && enable {
  153. insert := append([]string{string(Insert), "FORWARD"}, link...)
  154. if output, err := Raw(insert...); err != nil {
  155. return err
  156. } else if len(output) != 0 {
  157. return fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
  158. }
  159. } else if Exists(Filter, "FORWARD", link...) && !enable {
  160. del := append([]string{string(Delete), "FORWARD"}, link...)
  161. if output, err := Raw(del...); err != nil {
  162. return err
  163. } else if len(output) != 0 {
  164. return fmt.Errorf("Could not delete linking rule from %s/%s: %s", c.Table, c.Name, output)
  165. }
  166. }
  167. }
  168. return nil
  169. }
  170. // RemoveExistingChain removes existing chain from the table.
  171. func RemoveExistingChain(name string, table Table) error {
  172. c := &ChainInfo{
  173. Name: name,
  174. Table: table,
  175. }
  176. if string(c.Table) == "" {
  177. c.Table = Filter
  178. }
  179. return c.Remove()
  180. }
  181. // Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table.
  182. func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int, bridgeName string) error {
  183. daddr := ip.String()
  184. if ip.IsUnspecified() {
  185. // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
  186. // want "0.0.0.0/0". "0/0" is correctly interpreted as "any
  187. // value" by both iptables and ip6tables.
  188. daddr = "0/0"
  189. }
  190. args := []string{
  191. "-p", proto,
  192. "-d", daddr,
  193. "--dport", strconv.Itoa(port),
  194. "-j", "DNAT",
  195. "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))}
  196. if !c.HairpinMode {
  197. args = append(args, "!", "-i", bridgeName)
  198. }
  199. if err := ProgramRule(Nat, c.Name, action, args); err != nil {
  200. return err
  201. }
  202. args = []string{
  203. "!", "-i", bridgeName,
  204. "-o", bridgeName,
  205. "-p", proto,
  206. "-d", destAddr,
  207. "--dport", strconv.Itoa(destPort),
  208. "-j", "ACCEPT",
  209. }
  210. if err := ProgramRule(Filter, c.Name, action, args); err != nil {
  211. return err
  212. }
  213. args = []string{
  214. "-p", proto,
  215. "-s", destAddr,
  216. "-d", destAddr,
  217. "--dport", strconv.Itoa(destPort),
  218. "-j", "MASQUERADE",
  219. }
  220. if err := ProgramRule(Nat, "POSTROUTING", action, args); err != nil {
  221. return err
  222. }
  223. return nil
  224. }
  225. // Link adds reciprocal ACCEPT rule for two supplied IP addresses.
  226. // Traffic is allowed from ip1 to ip2 and vice-versa
  227. func (c *ChainInfo) Link(action Action, ip1, ip2 net.IP, port int, proto string, bridgeName string) error {
  228. // forward
  229. args := []string{
  230. "-i", bridgeName, "-o", bridgeName,
  231. "-p", proto,
  232. "-s", ip1.String(),
  233. "-d", ip2.String(),
  234. "--dport", strconv.Itoa(port),
  235. "-j", "ACCEPT",
  236. }
  237. if err := ProgramRule(Filter, c.Name, action, args); err != nil {
  238. return err
  239. }
  240. // reverse
  241. args[7], args[9] = args[9], args[7]
  242. args[10] = "--sport"
  243. if err := ProgramRule(Filter, c.Name, action, args); err != nil {
  244. return err
  245. }
  246. return nil
  247. }
  248. // ProgramRule adds the rule specified by args only if the
  249. // rule is not already present in the chain. Reciprocally,
  250. // it removes the rule only if present.
  251. func ProgramRule(table Table, chain string, action Action, args []string) error {
  252. if Exists(table, chain, args...) != (action == Delete) {
  253. return nil
  254. }
  255. return RawCombinedOutput(append([]string{"-t", string(table), string(action), chain}, args...)...)
  256. }
  257. // Prerouting adds linking rule to nat/PREROUTING chain.
  258. func (c *ChainInfo) Prerouting(action Action, args ...string) error {
  259. a := []string{"-t", string(Nat), string(action), "PREROUTING"}
  260. if len(args) > 0 {
  261. a = append(a, args...)
  262. }
  263. if output, err := Raw(a...); err != nil {
  264. return err
  265. } else if len(output) != 0 {
  266. return ChainError{Chain: "PREROUTING", Output: output}
  267. }
  268. return nil
  269. }
  270. // Output adds linking rule to an OUTPUT chain.
  271. func (c *ChainInfo) Output(action Action, args ...string) error {
  272. a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
  273. if len(args) > 0 {
  274. a = append(a, args...)
  275. }
  276. if output, err := Raw(a...); err != nil {
  277. return err
  278. } else if len(output) != 0 {
  279. return ChainError{Chain: "OUTPUT", Output: output}
  280. }
  281. return nil
  282. }
  283. // Remove removes the chain.
  284. func (c *ChainInfo) Remove() error {
  285. // Ignore errors - This could mean the chains were never set up
  286. if c.Table == Nat {
  287. c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name)
  288. c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", c.Name)
  289. c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) // Created in versions <= 0.1.6
  290. c.Prerouting(Delete)
  291. c.Output(Delete)
  292. }
  293. Raw("-t", string(c.Table), "-F", c.Name)
  294. Raw("-t", string(c.Table), "-X", c.Name)
  295. return nil
  296. }
  297. // Exists checks if a rule exists
  298. func Exists(table Table, chain string, rule ...string) bool {
  299. if string(table) == "" {
  300. table = Filter
  301. }
  302. initCheck()
  303. if supportsCOpt {
  304. // if exit status is 0 then return true, the rule exists
  305. _, err := Raw(append([]string{"-t", string(table), "-C", chain}, rule...)...)
  306. return err == nil
  307. }
  308. // parse "iptables -S" for the rule (it checks rules in a specific chain
  309. // in a specific table and it is very unreliable)
  310. return existsRaw(table, chain, rule...)
  311. }
  312. func existsRaw(table Table, chain string, rule ...string) bool {
  313. ruleString := fmt.Sprintf("%s %s\n", chain, strings.Join(rule, " "))
  314. existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
  315. return strings.Contains(string(existingRules), ruleString)
  316. }
  317. // Raw calls 'iptables' system command, passing supplied arguments.
  318. func Raw(args ...string) ([]byte, error) {
  319. if firewalldRunning {
  320. output, err := Passthrough(Iptables, args...)
  321. if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
  322. return output, err
  323. }
  324. }
  325. return raw(args...)
  326. }
  327. func raw(args ...string) ([]byte, error) {
  328. if err := initCheck(); err != nil {
  329. return nil, err
  330. }
  331. if supportsXlock {
  332. args = append([]string{"--wait"}, args...)
  333. } else {
  334. bestEffortLock.Lock()
  335. defer bestEffortLock.Unlock()
  336. }
  337. logrus.Debugf("%s, %v", iptablesPath, args)
  338. output, err := exec.Command(iptablesPath, args...).CombinedOutput()
  339. if err != nil {
  340. return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
  341. }
  342. // ignore iptables' message about xtables lock
  343. if strings.Contains(string(output), "waiting for it to exit") {
  344. output = []byte("")
  345. }
  346. return output, err
  347. }
  348. // RawCombinedOutput inernally calls the Raw function and returns a non nil
  349. // error if Raw returned a non nil error or a non empty output
  350. func RawCombinedOutput(args ...string) error {
  351. if output, err := Raw(args...); err != nil || len(output) != 0 {
  352. return fmt.Errorf("%s (%v)", string(output), err)
  353. }
  354. return nil
  355. }
  356. // RawCombinedOutputNative behave as RawCombinedOutput with the difference it
  357. // will always invoke `iptables` binary
  358. func RawCombinedOutputNative(args ...string) error {
  359. if output, err := raw(args...); err != nil || len(output) != 0 {
  360. return fmt.Errorf("%s (%v)", string(output), err)
  361. }
  362. return nil
  363. }
  364. // ExistChain checks if a chain exists
  365. func ExistChain(chain string, table Table) bool {
  366. if _, err := Raw("-t", string(table), "-L", chain); err == nil {
  367. return true
  368. }
  369. return false
  370. }
  371. // GetVersion reads the iptables version numbers
  372. func GetVersion() (major, minor, micro int, err error) {
  373. out, err := Raw("--version")
  374. if err == nil {
  375. major, minor, micro = parseVersionNumbers(string(out))
  376. }
  377. return
  378. }
  379. func parseVersionNumbers(input string) (major, minor, micro int) {
  380. re := regexp.MustCompile(`v\d*.\d*.\d*`)
  381. line := re.FindString(input)
  382. fmt.Sscanf(line, "v%d.%d.%d", &major, &minor, &micro)
  383. return
  384. }
  385. // iptables -C, --check option was added in v.1.4.11
  386. // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
  387. func supportsCOption(mj, mn, mc int) bool {
  388. return mj > 1 || (mj == 1 && (mn > 4 || (mn == 4 && mc >= 11)))
  389. }