github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/util/iptables/iptables.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package iptables
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"regexp"
    24  	"strings"
    25  	"sync"
    26  
    27  	"github.com/coreos/go-semver/semver"
    28  	godbus "github.com/godbus/dbus"
    29  	"github.com/golang/glog"
    30  	utildbus "k8s.io/kubernetes/pkg/util/dbus"
    31  	utilexec "k8s.io/kubernetes/pkg/util/exec"
    32  	"k8s.io/kubernetes/pkg/util/sets"
    33  )
    34  
    35  type RulePosition string
    36  
    37  const (
    38  	Prepend RulePosition = "-I"
    39  	Append  RulePosition = "-A"
    40  )
    41  
    42  // An injectable interface for running iptables commands.  Implementations must be goroutine-safe.
    43  type Interface interface {
    44  	// GetVersion returns the "X.Y.Z" semver string for iptables.
    45  	GetVersion() (string, error)
    46  	// EnsureChain checks if the specified chain exists and, if not, creates it.  If the chain existed, return true.
    47  	EnsureChain(table Table, chain Chain) (bool, error)
    48  	// FlushChain clears the specified chain.  If the chain did not exist, return error.
    49  	FlushChain(table Table, chain Chain) error
    50  	// DeleteChain deletes the specified chain.  If the chain did not exist, return error.
    51  	DeleteChain(table Table, chain Chain) error
    52  	// EnsureRule checks if the specified rule is present and, if not, creates it.  If the rule existed, return true.
    53  	EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error)
    54  	// DeleteRule checks if the specified rule is present and, if so, deletes it.
    55  	DeleteRule(table Table, chain Chain, args ...string) error
    56  	// IsIpv6 returns true if this is managing ipv6 tables
    57  	IsIpv6() bool
    58  	// TODO: (BenTheElder) Unit-Test Save/SaveAll, Restore/RestoreAll
    59  	// Save calls `iptables-save` for table.
    60  	Save(table Table) ([]byte, error)
    61  	// SaveAll calls `iptables-save`.
    62  	SaveAll() ([]byte, error)
    63  	// Restore runs `iptables-restore` passing data through a temporary file.
    64  	// table is the Table to restore
    65  	// data should be formatted like the output of Save()
    66  	// flush sets the presence of the "--noflush" flag. see: FlushFlag
    67  	// counters sets the "--counters" flag. see: RestoreCountersFlag
    68  	Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error
    69  	// RestoreAll is the same as Restore except that no table is specified.
    70  	RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error
    71  	// AddReloadFunc adds a function to call on iptables reload
    72  	AddReloadFunc(reloadFunc func())
    73  	// Destroy cleans up resources used by the Interface
    74  	Destroy()
    75  }
    76  
    77  type Protocol byte
    78  
    79  const (
    80  	ProtocolIpv4 Protocol = iota + 1
    81  	ProtocolIpv6
    82  )
    83  
    84  type Table string
    85  
    86  const (
    87  	TableNAT Table = "nat"
    88  )
    89  
    90  type Chain string
    91  
    92  const (
    93  	ChainPostrouting Chain = "POSTROUTING"
    94  	ChainPrerouting  Chain = "PREROUTING"
    95  	ChainOutput      Chain = "OUTPUT"
    96  )
    97  
    98  const (
    99  	cmdIptablesSave    string = "iptables-save"
   100  	cmdIptablesRestore string = "iptables-restore"
   101  	cmdIptables        string = "iptables"
   102  	cmdIp6tables       string = "ip6tables"
   103  )
   104  
   105  // Option flag for Restore
   106  type RestoreCountersFlag bool
   107  
   108  const RestoreCounters RestoreCountersFlag = true
   109  const NoRestoreCounters RestoreCountersFlag = false
   110  
   111  // Option flag for Flush
   112  type FlushFlag bool
   113  
   114  const FlushTables FlushFlag = true
   115  const NoFlushTables FlushFlag = false
   116  
   117  // Versions of iptables less than this do not support the -C / --check flag
   118  // (test whether a rule exists).
   119  const MinCheckVersion = "1.4.11"
   120  
   121  // Minimum iptables versions supporting the -w and -w2 flags
   122  const MinWaitVersion = "1.4.20"
   123  const MinWait2Version = "1.4.22"
   124  
   125  // runner implements Interface in terms of exec("iptables").
   126  type runner struct {
   127  	mu       sync.Mutex
   128  	exec     utilexec.Interface
   129  	dbus     utildbus.Interface
   130  	protocol Protocol
   131  	hasCheck bool
   132  	waitFlag []string
   133  
   134  	reloadFuncs []func()
   135  	signal      chan *godbus.Signal
   136  }
   137  
   138  // New returns a new Interface which will exec iptables.
   139  func New(exec utilexec.Interface, dbus utildbus.Interface, protocol Protocol) Interface {
   140  	vstring, err := getIptablesVersionString(exec)
   141  	if err != nil {
   142  		glog.Warningf("Error checking iptables version, assuming version at least %s: %v", MinCheckVersion, err)
   143  		vstring = MinCheckVersion
   144  	}
   145  	runner := &runner{
   146  		exec:     exec,
   147  		dbus:     dbus,
   148  		protocol: protocol,
   149  		hasCheck: getIptablesHasCheckCommand(vstring),
   150  		waitFlag: getIptablesWaitFlag(vstring),
   151  	}
   152  	runner.connectToFirewallD()
   153  	return runner
   154  }
   155  
   156  // Destroy is part of Interface.
   157  func (runner *runner) Destroy() {
   158  	if runner.signal != nil {
   159  		runner.signal <- nil
   160  	}
   161  }
   162  
   163  const (
   164  	firewalldName      = "org.fedoraproject.FirewallD1"
   165  	firewalldPath      = "/org/fedoraproject/FirewallD1"
   166  	firewalldInterface = "org.fedoraproject.FirewallD1"
   167  )
   168  
   169  // Connects to D-Bus and listens for FirewallD start/restart. (On non-FirewallD-using
   170  // systems, this is effectively a no-op; we listen for the signals, but they will never be
   171  // emitted, so reload() will never be called.)
   172  func (runner *runner) connectToFirewallD() {
   173  	bus, err := runner.dbus.SystemBus()
   174  	if err != nil {
   175  		glog.V(1).Infof("Could not connect to D-Bus system bus: %s", err)
   176  		return
   177  	}
   178  
   179  	rule := fmt.Sprintf("type='signal',sender='%s',path='%s',interface='%s',member='Reloaded'", firewalldName, firewalldPath, firewalldInterface)
   180  	bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
   181  
   182  	rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", firewalldName)
   183  	bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
   184  
   185  	runner.signal = make(chan *godbus.Signal, 10)
   186  	bus.Signal(runner.signal)
   187  
   188  	go runner.dbusSignalHandler(bus)
   189  }
   190  
   191  // GetVersion returns the version string.
   192  func (runner *runner) GetVersion() (string, error) {
   193  	return getIptablesVersionString(runner.exec)
   194  }
   195  
   196  // EnsureChain is part of Interface.
   197  func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) {
   198  	fullArgs := makeFullArgs(table, chain)
   199  
   200  	runner.mu.Lock()
   201  	defer runner.mu.Unlock()
   202  
   203  	out, err := runner.run(opCreateChain, fullArgs)
   204  	if err != nil {
   205  		if ee, ok := err.(utilexec.ExitError); ok {
   206  			if ee.Exited() && ee.ExitStatus() == 1 {
   207  				return true, nil
   208  			}
   209  		}
   210  		return false, fmt.Errorf("error creating chain %q: %v: %s", chain, err, out)
   211  	}
   212  	return false, nil
   213  }
   214  
   215  // FlushChain is part of Interface.
   216  func (runner *runner) FlushChain(table Table, chain Chain) error {
   217  	fullArgs := makeFullArgs(table, chain)
   218  
   219  	runner.mu.Lock()
   220  	defer runner.mu.Unlock()
   221  
   222  	out, err := runner.run(opFlushChain, fullArgs)
   223  	if err != nil {
   224  		return fmt.Errorf("error flushing chain %q: %v: %s", chain, err, out)
   225  	}
   226  	return nil
   227  }
   228  
   229  // DeleteChain is part of Interface.
   230  func (runner *runner) DeleteChain(table Table, chain Chain) error {
   231  	fullArgs := makeFullArgs(table, chain)
   232  
   233  	runner.mu.Lock()
   234  	defer runner.mu.Unlock()
   235  
   236  	// TODO: we could call iptables -S first, ignore the output and check for non-zero return (more like DeleteRule)
   237  	out, err := runner.run(opDeleteChain, fullArgs)
   238  	if err != nil {
   239  		return fmt.Errorf("error deleting chain %q: %v: %s", chain, err, out)
   240  	}
   241  	return nil
   242  }
   243  
   244  // EnsureRule is part of Interface.
   245  func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) {
   246  	fullArgs := makeFullArgs(table, chain, args...)
   247  
   248  	runner.mu.Lock()
   249  	defer runner.mu.Unlock()
   250  
   251  	exists, err := runner.checkRule(table, chain, args...)
   252  	if err != nil {
   253  		return false, err
   254  	}
   255  	if exists {
   256  		return true, nil
   257  	}
   258  	out, err := runner.run(operation(position), fullArgs)
   259  	if err != nil {
   260  		return false, fmt.Errorf("error appending rule: %v: %s", err, out)
   261  	}
   262  	return false, nil
   263  }
   264  
   265  // DeleteRule is part of Interface.
   266  func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error {
   267  	fullArgs := makeFullArgs(table, chain, args...)
   268  
   269  	runner.mu.Lock()
   270  	defer runner.mu.Unlock()
   271  
   272  	exists, err := runner.checkRule(table, chain, args...)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	if !exists {
   277  		return nil
   278  	}
   279  	out, err := runner.run(opDeleteRule, fullArgs)
   280  	if err != nil {
   281  		return fmt.Errorf("error deleting rule: %v: %s", err, out)
   282  	}
   283  	return nil
   284  }
   285  
   286  func (runner *runner) IsIpv6() bool {
   287  	return runner.protocol == ProtocolIpv6
   288  }
   289  
   290  // Save is part of Interface.
   291  func (runner *runner) Save(table Table) ([]byte, error) {
   292  	runner.mu.Lock()
   293  	defer runner.mu.Unlock()
   294  
   295  	// run and return
   296  	args := []string{"-t", string(table)}
   297  	return runner.exec.Command(cmdIptablesSave, args...).CombinedOutput()
   298  }
   299  
   300  // SaveAll is part of Interface.
   301  func (runner *runner) SaveAll() ([]byte, error) {
   302  	runner.mu.Lock()
   303  	defer runner.mu.Unlock()
   304  
   305  	// run and return
   306  	return runner.exec.Command(cmdIptablesSave, []string{}...).CombinedOutput()
   307  }
   308  
   309  // Restore is part of Interface.
   310  func (runner *runner) Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
   311  	// setup args
   312  	args := []string{"-T", string(table)}
   313  	return runner.restoreInternal(args, data, flush, counters)
   314  }
   315  
   316  // RestoreAll is part of Interface.
   317  func (runner *runner) RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
   318  	// setup args
   319  	args := make([]string, 0)
   320  	return runner.restoreInternal(args, data, flush, counters)
   321  }
   322  
   323  // restoreInternal is the shared part of Restore/RestoreAll
   324  func (runner *runner) restoreInternal(args []string, data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
   325  	runner.mu.Lock()
   326  	defer runner.mu.Unlock()
   327  
   328  	if !flush {
   329  		args = append(args, "--noflush")
   330  	}
   331  	if counters {
   332  		args = append(args, "--counters")
   333  	}
   334  	// create temp file through which to pass data
   335  	temp, err := ioutil.TempFile("", "kube-temp-iptables-restore-")
   336  	if err != nil {
   337  		return err
   338  	}
   339  	// make sure we delete the temp file
   340  	defer os.Remove(temp.Name())
   341  	// Put the filename at the end of args.
   342  	// NOTE: the filename must be at the end.
   343  	// See: https://git.netfilter.org/iptables/commit/iptables-restore.c?id=e6869a8f59d779ff4d5a0984c86d80db70784962
   344  	args = append(args, temp.Name())
   345  	if err != nil {
   346  		return err
   347  	}
   348  	// write data to the file
   349  	_, err = temp.Write(data)
   350  	temp.Close()
   351  	if err != nil {
   352  		return err
   353  	}
   354  	// run the command and return the output or an error including the output and error
   355  	b, err := runner.exec.Command(cmdIptablesRestore, args...).CombinedOutput()
   356  	if err != nil {
   357  		return fmt.Errorf("%v (%s)", err, b)
   358  	}
   359  	return nil
   360  }
   361  
   362  func (runner *runner) iptablesCommand() string {
   363  	if runner.IsIpv6() {
   364  		return cmdIp6tables
   365  	} else {
   366  		return cmdIptables
   367  	}
   368  }
   369  
   370  func (runner *runner) run(op operation, args []string) ([]byte, error) {
   371  	iptablesCmd := runner.iptablesCommand()
   372  
   373  	fullArgs := append(runner.waitFlag, string(op))
   374  	fullArgs = append(fullArgs, args...)
   375  	glog.V(4).Infof("running iptables %s %v", string(op), args)
   376  	return runner.exec.Command(iptablesCmd, fullArgs...).CombinedOutput()
   377  	// Don't log err here - callers might not think it is an error.
   378  }
   379  
   380  // Returns (bool, nil) if it was able to check the existence of the rule, or
   381  // (<undefined>, error) if the process of checking failed.
   382  func (runner *runner) checkRule(table Table, chain Chain, args ...string) (bool, error) {
   383  	if runner.hasCheck {
   384  		return runner.checkRuleUsingCheck(makeFullArgs(table, chain, args...))
   385  	} else {
   386  		return runner.checkRuleWithoutCheck(table, chain, args...)
   387  	}
   388  }
   389  
   390  // Executes the rule check without using the "-C" flag, instead parsing iptables-save.
   391  // Present for compatibility with <1.4.11 versions of iptables.  This is full
   392  // of hack and half-measures.  We should nix this ASAP.
   393  func (runner *runner) checkRuleWithoutCheck(table Table, chain Chain, args ...string) (bool, error) {
   394  	glog.V(1).Infof("running iptables-save -t %s", string(table))
   395  	out, err := runner.exec.Command(cmdIptablesSave, "-t", string(table)).CombinedOutput()
   396  	if err != nil {
   397  		return false, fmt.Errorf("error checking rule: %v", err)
   398  	}
   399  
   400  	// Sadly, iptables has inconsistent quoting rules for comments. Just remove all quotes.
   401  	// Also, quoted multi-word comments (which are counted as a single arg)
   402  	// will be unpacked into multiple args,
   403  	// in order to compare against iptables-save output (which will be split at whitespace boundary)
   404  	// e.g. a single arg('"this must be before the NodePort rules"') will be unquoted and unpacked into 7 args.
   405  	var argsCopy []string
   406  	for i := range args {
   407  		tmpField := strings.Trim(args[i], "\"")
   408  		argsCopy = append(argsCopy, strings.Fields(tmpField)...)
   409  	}
   410  	argset := sets.NewString(argsCopy...)
   411  
   412  	for _, line := range strings.Split(string(out), "\n") {
   413  		var fields = strings.Fields(line)
   414  
   415  		// Check that this is a rule for the correct chain, and that it has
   416  		// the correct number of argument (+2 for "-A <chain name>")
   417  		if !strings.HasPrefix(line, fmt.Sprintf("-A %s", string(chain))) || len(fields) != len(argsCopy)+2 {
   418  			continue
   419  		}
   420  
   421  		// Sadly, iptables has inconsistent quoting rules for comments.
   422  		// Just remove all quotes.
   423  		for i := range fields {
   424  			fields[i] = strings.Trim(fields[i], "\"")
   425  		}
   426  
   427  		// TODO: This misses reorderings e.g. "-x foo ! -y bar" will match "! -x foo -y bar"
   428  		if sets.NewString(fields...).IsSuperset(argset) {
   429  			return true, nil
   430  		}
   431  		glog.V(5).Infof("DBG: fields is not a superset of args: fields=%v  args=%v", fields, args)
   432  	}
   433  
   434  	return false, nil
   435  }
   436  
   437  // Executes the rule check using the "-C" flag
   438  func (runner *runner) checkRuleUsingCheck(args []string) (bool, error) {
   439  	out, err := runner.run(opCheckRule, args)
   440  	if err == nil {
   441  		return true, nil
   442  	}
   443  	if ee, ok := err.(utilexec.ExitError); ok {
   444  		// iptables uses exit(1) to indicate a failure of the operation,
   445  		// as compared to a malformed commandline, for example.
   446  		if ee.Exited() && ee.ExitStatus() == 1 {
   447  			return false, nil
   448  		}
   449  	}
   450  	return false, fmt.Errorf("error checking rule: %v: %s", err, out)
   451  }
   452  
   453  type operation string
   454  
   455  const (
   456  	opCreateChain operation = "-N"
   457  	opFlushChain  operation = "-F"
   458  	opDeleteChain operation = "-X"
   459  	opAppendRule  operation = "-A"
   460  	opCheckRule   operation = "-C"
   461  	opDeleteRule  operation = "-D"
   462  )
   463  
   464  func makeFullArgs(table Table, chain Chain, args ...string) []string {
   465  	return append([]string{string(chain), "-t", string(table)}, args...)
   466  }
   467  
   468  // Checks if iptables has the "-C" flag
   469  func getIptablesHasCheckCommand(vstring string) bool {
   470  	minVersion, err := semver.NewVersion(MinCheckVersion)
   471  	if err != nil {
   472  		glog.Errorf("MinCheckVersion (%s) is not a valid version string: %v", MinCheckVersion, err)
   473  		return true
   474  	}
   475  	version, err := semver.NewVersion(vstring)
   476  	if err != nil {
   477  		glog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err)
   478  		return true
   479  	}
   480  	if version.LessThan(*minVersion) {
   481  		return false
   482  	}
   483  	return true
   484  }
   485  
   486  // Checks if iptables version has a "wait" flag
   487  func getIptablesWaitFlag(vstring string) []string {
   488  	version, err := semver.NewVersion(vstring)
   489  	if err != nil {
   490  		glog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err)
   491  		return nil
   492  	}
   493  
   494  	minVersion, err := semver.NewVersion(MinWaitVersion)
   495  	if err != nil {
   496  		glog.Errorf("MinWaitVersion (%s) is not a valid version string: %v", MinWaitVersion, err)
   497  		return nil
   498  	}
   499  	if version.LessThan(*minVersion) {
   500  		return nil
   501  	}
   502  
   503  	minVersion, err = semver.NewVersion(MinWait2Version)
   504  	if err != nil {
   505  		glog.Errorf("MinWait2Version (%s) is not a valid version string: %v", MinWait2Version, err)
   506  		return nil
   507  	}
   508  	if version.LessThan(*minVersion) {
   509  		return []string{"-w"}
   510  	} else {
   511  		return []string{"-w2"}
   512  	}
   513  }
   514  
   515  // getIptablesVersionString runs "iptables --version" to get the version string
   516  // in the form "X.X.X"
   517  func getIptablesVersionString(exec utilexec.Interface) (string, error) {
   518  	// this doesn't access mutable state so we don't need to use the interface / runner
   519  	bytes, err := exec.Command(cmdIptables, "--version").CombinedOutput()
   520  	if err != nil {
   521  		return "", err
   522  	}
   523  	versionMatcher := regexp.MustCompile("v([0-9]+\\.[0-9]+\\.[0-9]+)")
   524  	match := versionMatcher.FindStringSubmatch(string(bytes))
   525  	if match == nil {
   526  		return "", fmt.Errorf("no iptables version found in string: %s", bytes)
   527  	}
   528  	return match[1], nil
   529  }
   530  
   531  // goroutine to listen for D-Bus signals
   532  func (runner *runner) dbusSignalHandler(bus utildbus.Connection) {
   533  	firewalld := bus.Object(firewalldName, firewalldPath)
   534  
   535  	for s := range runner.signal {
   536  		if s == nil {
   537  			// Unregister
   538  			bus.Signal(runner.signal)
   539  			return
   540  		}
   541  
   542  		switch s.Name {
   543  		case "org.freedesktop.DBus.NameOwnerChanged":
   544  			name := s.Body[0].(string)
   545  			new_owner := s.Body[2].(string)
   546  
   547  			if name != firewalldName || len(new_owner) == 0 {
   548  				continue
   549  			}
   550  
   551  			// FirewallD startup (specifically the part where it deletes
   552  			// all existing iptables rules) may not yet be complete when
   553  			// we get this signal, so make a dummy request to it to
   554  			// synchronize.
   555  			firewalld.Call(firewalldInterface+".getDefaultZone", 0)
   556  
   557  			runner.reload()
   558  		case firewalldInterface + ".Reloaded":
   559  			runner.reload()
   560  		}
   561  	}
   562  }
   563  
   564  // AddReloadFunc is part of Interface
   565  func (runner *runner) AddReloadFunc(reloadFunc func()) {
   566  	runner.reloadFuncs = append(runner.reloadFuncs, reloadFunc)
   567  }
   568  
   569  // runs all reload funcs to re-sync iptables rules
   570  func (runner *runner) reload() {
   571  	glog.V(1).Infof("reloading iptables rules")
   572  
   573  	for _, f := range runner.reloadFuncs {
   574  		f()
   575  	}
   576  }