iptables.go 7.8 KB

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