iptables.go 8.5 KB

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