iptables.go 12 KB

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