github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/network/iptables/iptables.go (about)

     1  package iptables
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net"
     7  	"os/exec"
     8  	"strings"
     9  
    10  	"sync"
    11  
    12  	"github.com/cloudfoundry-incubator/garden"
    13  	"github.com/cloudfoundry-incubator/garden-linux/logging"
    14  	"github.com/cloudfoundry/gunk/command_runner"
    15  	"github.com/pivotal-golang/lager"
    16  )
    17  
    18  var protocols = map[garden.Protocol]string{
    19  	garden.ProtocolAll:  "all",
    20  	garden.ProtocolTCP:  "tcp",
    21  	garden.ProtocolICMP: "icmp",
    22  	garden.ProtocolUDP:  "udp",
    23  }
    24  
    25  // NewGlobalChain creates a chain without an associated log chain.
    26  // The chain is not created by this package (currently it is created in net.sh).
    27  // It is an error to attempt to call Setup on this chain.
    28  func NewGlobalChain(name string, runner command_runner.CommandRunner, logger lager.Logger) Chain {
    29  	logger = logger.Session("global-chain", lager.Data{
    30  		"name": name,
    31  	})
    32  	return &chain{name: name, logChainName: "", runner: &logging.Runner{runner, logger}, logger: logger}
    33  }
    34  
    35  // NewLoggingChain creates a chain with an associated log chain.
    36  // This allows NetOut calls with the 'log' parameter to succesfully log.
    37  func NewLoggingChain(name string, useKernelLogging bool, runner command_runner.CommandRunner, logger lager.Logger) Chain {
    38  	logger = logger.Session("logging-chain", lager.Data{
    39  		"name":             name,
    40  		"useKernelLogging": useKernelLogging,
    41  	})
    42  	return &chain{
    43  		name:             name,
    44  		logChainName:     name + "-log",
    45  		useKernelLogging: useKernelLogging,
    46  		loglessRunner:    runner,
    47  		runner:           &logging.Runner{runner, logger},
    48  		logger:           logger,
    49  	}
    50  }
    51  
    52  //go:generate counterfeiter . Chain
    53  type Chain interface {
    54  	// Create the actual iptable chains in the underlying system.
    55  	// logPrefix defines the log prefix used for logging this chain.
    56  	Setup(logPrefix string) error
    57  
    58  	// Destroy the actual iptable chains in the underlying system
    59  	TearDown() error
    60  
    61  	AppendRule(source string, destination string, jump Action) error
    62  	DeleteRule(source string, destination string, jump Action) error
    63  
    64  	AppendNatRule(source string, destination string, jump Action, to net.IP) error
    65  	DeleteNatRule(source string, destination string, jump Action, to net.IP) error
    66  
    67  	PrependFilterRule(rule garden.NetOutRule) error
    68  }
    69  
    70  type chain struct {
    71  	mu               sync.Mutex
    72  	name             string
    73  	logChainName     string
    74  	useKernelLogging bool
    75  	runner           command_runner.CommandRunner
    76  	loglessRunner    command_runner.CommandRunner
    77  	logger           lager.Logger
    78  }
    79  
    80  func (ch *chain) Setup(logPrefix string) error {
    81  	ch.mu.Lock()
    82  	defer ch.mu.Unlock()
    83  
    84  	logger := ch.logger.Session("setup", lager.Data{
    85  		"logChainName": ch.logChainName,
    86  	})
    87  	logger.Debug("started")
    88  
    89  	if ch.logChainName == "" {
    90  		// we still use net.sh to set up global non-logging chains
    91  		panic("cannot set up chains without associated log chains")
    92  	}
    93  
    94  	ch.TearDown()
    95  
    96  	if err := ch.runner.Run(exec.Command("/sbin/iptables", "-w", "-N", ch.logChainName)); err != nil {
    97  		return fmt.Errorf("iptables: log chain setup: %v", err)
    98  	}
    99  	logger.Debug("created")
   100  
   101  	logParams := ch.buildLogParams(logPrefix)
   102  	appendFlags := []string{"-w", "-A", ch.logChainName, "-m", "conntrack", "--ctstate", "NEW,UNTRACKED,INVALID", "--protocol", "tcp"}
   103  	if err := ch.runner.Run(exec.Command("/sbin/iptables", append(appendFlags, logParams...)...)); err != nil {
   104  		return fmt.Errorf("iptables: log chain setup: %v", err)
   105  	}
   106  	logger.Debug("conntrack-set-up")
   107  
   108  	if err := ch.runner.Run(exec.Command("/sbin/iptables", "-w", "-A", ch.logChainName, "--jump", "RETURN")); err != nil {
   109  		return fmt.Errorf("iptables: log chain setup: %v", err)
   110  	}
   111  	logger.Debug("ending")
   112  
   113  	return nil
   114  }
   115  
   116  func (ch *chain) buildLogParams(logPrefix string) []string {
   117  	if ch.useKernelLogging {
   118  		return []string{"--jump", "LOG", "--log-prefix", logPrefix}
   119  	} else {
   120  		return []string{"--jump", "NFLOG", "--nflog-prefix", logPrefix, "--nflog-group", "1"}
   121  	}
   122  }
   123  
   124  func (ch *chain) TearDown() error {
   125  	logger := ch.logger.Session("teardown", lager.Data{
   126  		"logChainName": ch.logChainName,
   127  	})
   128  	logger.Debug("started")
   129  	if ch.logChainName == "" {
   130  		// we still use net.sh to tear down global non-logging chains
   131  		panic("cannot tear down chains without associated log chains")
   132  	}
   133  
   134  	// it's ok to skip logs here, we expect this to fail if this is a
   135  	// pre-creation teardown
   136  	ch.loglessRunner.Run(exec.Command("/sbin/iptables", "-w", "-F", ch.logChainName))
   137  	logger.Debug("flushed")
   138  	ch.loglessRunner.Run(exec.Command("/sbin/iptables", "-w", "-X", ch.logChainName))
   139  	logger.Debug("ending")
   140  	return nil
   141  }
   142  
   143  func (ch *chain) AppendRule(source string, destination string, jump Action) error {
   144  	return ch.Create(&rule{
   145  		source:      source,
   146  		destination: destination,
   147  		jump:        jump,
   148  	})
   149  }
   150  
   151  func (ch *chain) DeleteRule(source string, destination string, jump Action) error {
   152  	return ch.Destroy(&rule{
   153  		source:      source,
   154  		destination: destination,
   155  		jump:        jump,
   156  	})
   157  }
   158  
   159  func (ch *chain) AppendNatRule(source string, destination string, jump Action, to net.IP) error {
   160  	return ch.Create(&rule{
   161  		typ:         Nat,
   162  		source:      source,
   163  		destination: destination,
   164  		jump:        jump,
   165  		to:          to,
   166  	})
   167  }
   168  
   169  func (ch *chain) DeleteNatRule(source string, destination string, jump Action, to net.IP) error {
   170  	return ch.Destroy(&rule{
   171  		typ:         Nat,
   172  		source:      source,
   173  		destination: destination,
   174  		jump:        jump,
   175  		to:          to,
   176  	})
   177  }
   178  
   179  type singleRule struct {
   180  	Protocol garden.Protocol
   181  	Networks *garden.IPRange
   182  	Ports    *garden.PortRange
   183  	ICMPs    *garden.ICMPControl
   184  	Log      bool
   185  }
   186  
   187  func (ch *chain) PrependFilterRule(r garden.NetOutRule) error {
   188  	logger := ch.logger.Session("prepend-filter-rule", lager.Data{"rule": r})
   189  	logger.Debug("started")
   190  	if len(r.Ports) > 0 && !allowsPort(r.Protocol) {
   191  		return fmt.Errorf("Ports cannot be specified for Protocol %s", strings.ToUpper(protocols[r.Protocol]))
   192  	}
   193  
   194  	single := singleRule{
   195  		Protocol: r.Protocol,
   196  		ICMPs:    r.ICMPs,
   197  		Log:      r.Log,
   198  	}
   199  
   200  	// It should still loop once even if there are no networks or ports.
   201  	for j := 0; j < len(r.Networks) || j == 0; j++ {
   202  		for i := 0; i < len(r.Ports) || i == 0; i++ {
   203  
   204  			// Preserve nils unless there are ports specified
   205  			if len(r.Ports) > 0 {
   206  				single.Ports = &r.Ports[i]
   207  			}
   208  
   209  			// Preserve nils unless there are networks specified
   210  			if len(r.Networks) > 0 {
   211  				single.Networks = &r.Networks[j]
   212  			}
   213  
   214  			if err := ch.prependSingleRule(single); err != nil {
   215  				return err
   216  			}
   217  		}
   218  	}
   219  
   220  	logger.Debug("ending")
   221  	return nil
   222  }
   223  
   224  func allowsPort(p garden.Protocol) bool {
   225  	return p == garden.ProtocolTCP || p == garden.ProtocolUDP
   226  }
   227  
   228  func (ch *chain) prependSingleRule(r singleRule) error {
   229  	params := []string{"-w", "-I", ch.name, "1"}
   230  
   231  	protocolString, ok := protocols[r.Protocol]
   232  
   233  	if !ok {
   234  		return fmt.Errorf("invalid protocol: %d", r.Protocol)
   235  	}
   236  
   237  	params = append(params, "--protocol", protocolString)
   238  
   239  	network := r.Networks
   240  	if network != nil {
   241  		if network.Start != nil && network.End != nil {
   242  			params = append(params, "-m", "iprange", "--dst-range", network.Start.String()+"-"+network.End.String())
   243  		} else if network.Start != nil {
   244  			params = append(params, "--destination", network.Start.String())
   245  		} else if network.End != nil {
   246  			params = append(params, "--destination", network.End.String())
   247  		}
   248  	}
   249  
   250  	ports := r.Ports
   251  	if ports != nil {
   252  		if ports.End != ports.Start {
   253  			params = append(params, "--destination-port", fmt.Sprintf("%d:%d", ports.Start, ports.End))
   254  		} else {
   255  			params = append(params, "--destination-port", fmt.Sprintf("%d", ports.Start))
   256  		}
   257  	}
   258  
   259  	if r.ICMPs != nil {
   260  		icmpType := fmt.Sprintf("%d", r.ICMPs.Type)
   261  		if r.ICMPs.Code != nil {
   262  			icmpType = fmt.Sprintf("%d/%d", r.ICMPs.Type, *r.ICMPs.Code)
   263  		}
   264  
   265  		params = append(params, "--icmp-type", icmpType)
   266  	}
   267  
   268  	if r.Log {
   269  		params = append(params, "--goto", ch.logChainName)
   270  	} else {
   271  		params = append(params, "--jump", "RETURN")
   272  	}
   273  
   274  	ch.logger.Debug("prepend-filter-rule", lager.Data{"parms": params})
   275  
   276  	var stderr bytes.Buffer
   277  	cmd := exec.Command("/sbin/iptables", params...)
   278  	cmd.Stderr = &stderr
   279  	if err := ch.runner.Run(cmd); err != nil {
   280  		return fmt.Errorf("iptables: %v, %v", err, stderr.String())
   281  	}
   282  	ch.logger.Debug("prependSingleRule-finished")
   283  
   284  	return nil
   285  }
   286  
   287  type rule struct {
   288  	typ         Type
   289  	source      string
   290  	destination string
   291  	to          net.IP
   292  	jump        Action
   293  }
   294  
   295  func (n *rule) create(chain string, runner command_runner.CommandRunner) error {
   296  	return runner.Run(exec.Command("/sbin/iptables", flags("-A", chain, n)...))
   297  }
   298  
   299  func (n *rule) destroy(chain string, runner command_runner.CommandRunner) error {
   300  	return runner.Run(exec.Command("/sbin/iptables", flags("-D", chain, n)...))
   301  }
   302  
   303  func flags(action, chain string, n *rule) []string {
   304  	rule := []string{"-w"}
   305  
   306  	if n.typ != "" {
   307  		rule = append(rule, "-t", string(n.typ))
   308  	}
   309  
   310  	rule = append(rule, action, chain)
   311  
   312  	if n.source != "" {
   313  		rule = append(rule, "--source", n.source)
   314  	}
   315  
   316  	if n.destination != "" {
   317  		rule = append(rule, "--destination", n.destination)
   318  	}
   319  
   320  	rule = append(rule, "--jump", string(n.jump))
   321  
   322  	if n.to != nil {
   323  		rule = append(rule, "--to", string(n.to.String()))
   324  	}
   325  
   326  	return rule
   327  }
   328  
   329  type Destroyable interface {
   330  	Destroy() error
   331  }
   332  
   333  type creater interface {
   334  	create(chain string, runner command_runner.CommandRunner) error
   335  }
   336  
   337  type destroyer interface {
   338  	destroy(chain string, runner command_runner.CommandRunner) error
   339  }
   340  
   341  func (c *chain) Create(rule creater) error {
   342  	return rule.create(c.name, c.runner)
   343  }
   344  
   345  func (c *chain) Destroy(rule destroyer) error {
   346  	return rule.destroy(c.name, c.runner)
   347  }
   348  
   349  type Action string
   350  
   351  const (
   352  	Return    Action = "RETURN"
   353  	SourceNAT        = "SNAT"
   354  	Reject           = "REJECT"
   355  	Drop             = "DROP"
   356  )
   357  
   358  type Type string
   359  
   360  const (
   361  	Nat Type = "nat"
   362  )