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