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