iptables.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package iptables
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "os/exec"
  7. "regexp"
  8. "strconv"
  9. "strings"
  10. "github.com/Sirupsen/logrus"
  11. )
  12. type Action string
  13. type Table string
  14. const (
  15. Append Action = "-A"
  16. Delete Action = "-D"
  17. Insert Action = "-I"
  18. Nat Table = "nat"
  19. Filter Table = "filter"
  20. Mangle Table = "mangle"
  21. )
  22. var (
  23. iptablesPath string
  24. supportsXlock = false
  25. ErrIptablesNotFound = errors.New("Iptables not found")
  26. )
  27. type Chain struct {
  28. Name string
  29. Bridge string
  30. Table Table
  31. }
  32. type ChainError struct {
  33. Chain string
  34. Output []byte
  35. }
  36. func (e ChainError) Error() string {
  37. return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
  38. }
  39. func initCheck() error {
  40. if iptablesPath == "" {
  41. path, err := exec.LookPath("iptables")
  42. if err != nil {
  43. return ErrIptablesNotFound
  44. }
  45. iptablesPath = path
  46. supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
  47. }
  48. return nil
  49. }
  50. func NewChain(name, bridge string, table Table, hairpinMode bool) (*Chain, error) {
  51. c := &Chain{
  52. Name: name,
  53. Bridge: bridge,
  54. Table: table,
  55. }
  56. if string(c.Table) == "" {
  57. c.Table = Filter
  58. }
  59. // Add chain if it doesn't exist
  60. if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
  61. if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil {
  62. return nil, err
  63. } else if len(output) != 0 {
  64. return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
  65. }
  66. }
  67. switch table {
  68. case Nat:
  69. preroute := []string{
  70. "-m", "addrtype",
  71. "--dst-type", "LOCAL"}
  72. if !Exists(Nat, "PREROUTING", preroute...) {
  73. if err := c.Prerouting(Append, preroute...); err != nil {
  74. return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
  75. }
  76. }
  77. output := []string{
  78. "-m", "addrtype",
  79. "--dst-type", "LOCAL"}
  80. if !hairpinMode {
  81. output = append(output, "!", "--dst", "127.0.0.0/8")
  82. }
  83. if !Exists(Nat, "OUTPUT", output...) {
  84. if err := c.Output(Append, output...); err != nil {
  85. return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
  86. }
  87. }
  88. case Filter:
  89. link := []string{
  90. "-o", c.Bridge,
  91. "-j", c.Name}
  92. if !Exists(Filter, "FORWARD", link...) {
  93. insert := append([]string{string(Insert), "FORWARD"}, link...)
  94. if output, err := Raw(insert...); err != nil {
  95. return nil, err
  96. } else if len(output) != 0 {
  97. return nil, fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
  98. }
  99. }
  100. }
  101. return c, nil
  102. }
  103. func RemoveExistingChain(name string, table Table) error {
  104. c := &Chain{
  105. Name: name,
  106. Table: table,
  107. }
  108. if string(c.Table) == "" {
  109. c.Table = Filter
  110. }
  111. return c.Remove()
  112. }
  113. // Add forwarding rule to 'filter' table and corresponding nat rule to 'nat' table
  114. func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int) error {
  115. daddr := ip.String()
  116. if ip.IsUnspecified() {
  117. // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
  118. // want "0.0.0.0/0". "0/0" is correctly interpreted as "any
  119. // value" by both iptables and ip6tables.
  120. daddr = "0/0"
  121. }
  122. if output, err := Raw("-t", string(Nat), string(action), c.Name,
  123. "-p", proto,
  124. "-d", daddr,
  125. "--dport", strconv.Itoa(port),
  126. "-j", "DNAT",
  127. "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil {
  128. return err
  129. } else if len(output) != 0 {
  130. return ChainError{Chain: "FORWARD", Output: output}
  131. }
  132. if output, err := Raw("-t", string(Filter), string(action), c.Name,
  133. "!", "-i", c.Bridge,
  134. "-o", c.Bridge,
  135. "-p", proto,
  136. "-d", destAddr,
  137. "--dport", strconv.Itoa(destPort),
  138. "-j", "ACCEPT"); err != nil {
  139. return err
  140. } else if len(output) != 0 {
  141. return ChainError{Chain: "FORWARD", Output: output}
  142. }
  143. if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING",
  144. "-p", proto,
  145. "-s", destAddr,
  146. "-d", destAddr,
  147. "--dport", strconv.Itoa(destPort),
  148. "-j", "MASQUERADE"); err != nil {
  149. return err
  150. } else if len(output) != 0 {
  151. return ChainError{Chain: "FORWARD", Output: output}
  152. }
  153. return nil
  154. }
  155. // Add reciprocal ACCEPT rule for two supplied IP addresses.
  156. // Traffic is allowed from ip1 to ip2 and vice-versa
  157. func (c *Chain) Link(action Action, ip1, ip2 net.IP, port int, proto string) error {
  158. if output, err := Raw("-t", string(Filter), string(action), c.Name,
  159. "-i", c.Bridge, "-o", c.Bridge,
  160. "-p", proto,
  161. "-s", ip1.String(),
  162. "-d", ip2.String(),
  163. "--dport", strconv.Itoa(port),
  164. "-j", "ACCEPT"); err != nil {
  165. return err
  166. } else if len(output) != 0 {
  167. return fmt.Errorf("Error iptables forward: %s", output)
  168. }
  169. if output, err := Raw("-t", string(Filter), string(action), c.Name,
  170. "-i", c.Bridge, "-o", c.Bridge,
  171. "-p", proto,
  172. "-s", ip2.String(),
  173. "-d", ip1.String(),
  174. "--sport", strconv.Itoa(port),
  175. "-j", "ACCEPT"); err != nil {
  176. return err
  177. } else if len(output) != 0 {
  178. return fmt.Errorf("Error iptables forward: %s", output)
  179. }
  180. return nil
  181. }
  182. // Add linking rule to nat/PREROUTING chain.
  183. func (c *Chain) Prerouting(action Action, args ...string) error {
  184. a := []string{"-t", string(Nat), string(action), "PREROUTING"}
  185. if len(args) > 0 {
  186. a = append(a, args...)
  187. }
  188. if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
  189. return err
  190. } else if len(output) != 0 {
  191. return ChainError{Chain: "PREROUTING", Output: output}
  192. }
  193. return nil
  194. }
  195. // Add linking rule to an OUTPUT chain
  196. func (c *Chain) Output(action Action, args ...string) error {
  197. a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
  198. if len(args) > 0 {
  199. a = append(a, args...)
  200. }
  201. if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
  202. return err
  203. } else if len(output) != 0 {
  204. return ChainError{Chain: "OUTPUT", Output: output}
  205. }
  206. return nil
  207. }
  208. func (c *Chain) Remove() error {
  209. // Ignore errors - This could mean the chains were never set up
  210. if c.Table == Nat {
  211. c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL")
  212. c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
  213. c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6
  214. c.Prerouting(Delete)
  215. c.Output(Delete)
  216. }
  217. Raw("-t", string(c.Table), "-F", c.Name)
  218. Raw("-t", string(c.Table), "-X", c.Name)
  219. return nil
  220. }
  221. // Check if a rule exists
  222. func Exists(table Table, chain string, rule ...string) bool {
  223. if string(table) == "" {
  224. table = Filter
  225. }
  226. // iptables -C, --check option was added in v.1.4.11
  227. // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
  228. // try -C
  229. // if exit status is 0 then return true, the rule exists
  230. if _, err := Raw(append([]string{
  231. "-t", string(table), "-C", chain}, rule...)...); err == nil {
  232. return true
  233. }
  234. // parse "iptables -S" for the rule (this checks rules in a specific chain
  235. // in a specific table)
  236. ruleString := strings.Join(rule, " ")
  237. existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
  238. // regex to replace ips in rule
  239. // because MASQUERADE rule will not be exactly what was passed
  240. re := regexp.MustCompile(`[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}`)
  241. return strings.Contains(
  242. re.ReplaceAllString(string(existingRules), "?"),
  243. re.ReplaceAllString(ruleString, "?"),
  244. )
  245. }
  246. // Call 'iptables' system command, passing supplied arguments
  247. func Raw(args ...string) ([]byte, error) {
  248. if firewalldRunning {
  249. output, err := Passthrough(Iptables, args...)
  250. if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
  251. return output, err
  252. }
  253. }
  254. if err := initCheck(); err != nil {
  255. return nil, err
  256. }
  257. if supportsXlock {
  258. args = append([]string{"--wait"}, args...)
  259. }
  260. logrus.Debugf("%s, %v", iptablesPath, args)
  261. output, err := exec.Command(iptablesPath, args...).CombinedOutput()
  262. if err != nil {
  263. return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
  264. }
  265. // ignore iptables' message about xtables lock
  266. if strings.Contains(string(output), "waiting for it to exit") {
  267. output = []byte("")
  268. }
  269. return output, err
  270. }