github.com/codemac/docker@v1.2.1-0.20150518222241-6a18412d5b9c/pkg/iptables/iptables.go (about)

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