iptables.go 6.4 KB

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