github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/libnetwork/drivers/bridge/setup_ip_tables.go (about)

     1  // +build linux
     2  
     3  package bridge
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  
    10  	"github.com/docker/docker/libnetwork/iptables"
    11  	"github.com/sirupsen/logrus"
    12  	"github.com/vishvananda/netlink"
    13  )
    14  
    15  // DockerChain: DOCKER iptable chain name
    16  const (
    17  	DockerChain = "DOCKER"
    18  	// Isolation between bridge networks is achieved in two stages by means
    19  	// of the following two chains in the filter table. The first chain matches
    20  	// on the source interface being a bridge network's bridge and the
    21  	// destination being a different interface. A positive match leads to the
    22  	// second isolation chain. No match returns to the parent chain. The second
    23  	// isolation chain matches on destination interface being a bridge network's
    24  	// bridge. A positive match identifies a packet originated from one bridge
    25  	// network's bridge destined to another bridge network's bridge and will
    26  	// result in the packet being dropped. No match returns to the parent chain.
    27  	IsolationChain1 = "DOCKER-ISOLATION-STAGE-1"
    28  	IsolationChain2 = "DOCKER-ISOLATION-STAGE-2"
    29  )
    30  
    31  func setupIPChains(config *configuration, version iptables.IPVersion) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
    32  	// Sanity check.
    33  	if !config.EnableIPTables {
    34  		return nil, nil, nil, nil, errors.New("cannot create new chains, EnableIPTable is disabled")
    35  	}
    36  
    37  	hairpinMode := !config.EnableUserlandProxy
    38  
    39  	iptable := iptables.GetIptable(version)
    40  
    41  	natChain, err := iptable.NewChain(DockerChain, iptables.Nat, hairpinMode)
    42  	if err != nil {
    43  		return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain %s: %v", DockerChain, err)
    44  	}
    45  	defer func() {
    46  		if err != nil {
    47  			if err := iptable.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
    48  				logrus.Warnf("failed on removing iptables NAT chain %s on cleanup: %v", DockerChain, err)
    49  			}
    50  		}
    51  	}()
    52  
    53  	filterChain, err := iptable.NewChain(DockerChain, iptables.Filter, false)
    54  	if err != nil {
    55  		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER chain %s: %v", DockerChain, err)
    56  	}
    57  	defer func() {
    58  		if err != nil {
    59  			if err := iptable.RemoveExistingChain(DockerChain, iptables.Filter); err != nil {
    60  				logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", DockerChain, err)
    61  			}
    62  		}
    63  	}()
    64  
    65  	isolationChain1, err := iptable.NewChain(IsolationChain1, iptables.Filter, false)
    66  	if err != nil {
    67  		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
    68  	}
    69  	defer func() {
    70  		if err != nil {
    71  			if err := iptable.RemoveExistingChain(IsolationChain1, iptables.Filter); err != nil {
    72  				logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain1, err)
    73  			}
    74  		}
    75  	}()
    76  
    77  	isolationChain2, err := iptable.NewChain(IsolationChain2, iptables.Filter, false)
    78  	if err != nil {
    79  		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
    80  	}
    81  	defer func() {
    82  		if err != nil {
    83  			if err := iptable.RemoveExistingChain(IsolationChain2, iptables.Filter); err != nil {
    84  				logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain2, err)
    85  			}
    86  		}
    87  	}()
    88  
    89  	if err := iptable.AddReturnRule(IsolationChain1); err != nil {
    90  		return nil, nil, nil, nil, err
    91  	}
    92  
    93  	if err := iptable.AddReturnRule(IsolationChain2); err != nil {
    94  		return nil, nil, nil, nil, err
    95  	}
    96  
    97  	return natChain, filterChain, isolationChain1, isolationChain2, nil
    98  }
    99  
   100  func (n *bridgeNetwork) setupIP4Tables(config *networkConfiguration, i *bridgeInterface) error {
   101  	d := n.driver
   102  	d.Lock()
   103  	driverConfig := d.config
   104  	d.Unlock()
   105  
   106  	// Sanity check.
   107  	if !driverConfig.EnableIPTables {
   108  		return errors.New("Cannot program chains, EnableIPTable is disabled")
   109  	}
   110  
   111  	maskedAddrv4 := &net.IPNet{
   112  		IP:   i.bridgeIPv4.IP.Mask(i.bridgeIPv4.Mask),
   113  		Mask: i.bridgeIPv4.Mask,
   114  	}
   115  	return n.setupIPTables(iptables.IPv4, maskedAddrv4, config, i)
   116  }
   117  
   118  func (n *bridgeNetwork) setupIP6Tables(config *networkConfiguration, i *bridgeInterface) error {
   119  	d := n.driver
   120  	d.Lock()
   121  	driverConfig := d.config
   122  	d.Unlock()
   123  
   124  	// Sanity check.
   125  	if !driverConfig.EnableIP6Tables {
   126  		return errors.New("Cannot program chains, EnableIP6Tables is disabled")
   127  	}
   128  
   129  	maskedAddrv6 := &net.IPNet{
   130  		IP:   i.bridgeIPv6.IP.Mask(i.bridgeIPv6.Mask),
   131  		Mask: i.bridgeIPv6.Mask,
   132  	}
   133  
   134  	return n.setupIPTables(iptables.IPv6, maskedAddrv6, config, i)
   135  }
   136  
   137  func (n *bridgeNetwork) setupIPTables(ipVersion iptables.IPVersion, maskedAddr *net.IPNet, config *networkConfiguration, i *bridgeInterface) error {
   138  	var err error
   139  
   140  	d := n.driver
   141  	d.Lock()
   142  	driverConfig := d.config
   143  	d.Unlock()
   144  
   145  	// Pickup this configuration option from driver
   146  	hairpinMode := !driverConfig.EnableUserlandProxy
   147  
   148  	iptable := iptables.GetIptable(ipVersion)
   149  
   150  	if config.Internal {
   151  		if err = setupInternalNetworkRules(config.BridgeName, maskedAddr, config.EnableICC, true); err != nil {
   152  			return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
   153  		}
   154  		n.registerIptCleanFunc(func() error {
   155  			return setupInternalNetworkRules(config.BridgeName, maskedAddr, config.EnableICC, false)
   156  		})
   157  	} else {
   158  		if err = setupIPTablesInternal(config.HostIP, config.BridgeName, maskedAddr, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil {
   159  			return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
   160  		}
   161  		n.registerIptCleanFunc(func() error {
   162  			return setupIPTablesInternal(config.HostIP, config.BridgeName, maskedAddr, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
   163  		})
   164  		natChain, filterChain, _, _, err := n.getDriverChains(ipVersion)
   165  		if err != nil {
   166  			return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
   167  		}
   168  
   169  		err = iptable.ProgramChain(natChain, config.BridgeName, hairpinMode, true)
   170  		if err != nil {
   171  			return fmt.Errorf("Failed to program NAT chain: %s", err.Error())
   172  		}
   173  
   174  		err = iptable.ProgramChain(filterChain, config.BridgeName, hairpinMode, true)
   175  		if err != nil {
   176  			return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
   177  		}
   178  
   179  		n.registerIptCleanFunc(func() error {
   180  			return iptable.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
   181  		})
   182  
   183  		if ipVersion == iptables.IPv4 {
   184  			n.portMapper.SetIptablesChain(natChain, n.getNetworkBridgeName())
   185  		} else {
   186  			n.portMapperV6.SetIptablesChain(natChain, n.getNetworkBridgeName())
   187  		}
   188  	}
   189  
   190  	d.Lock()
   191  	err = iptable.EnsureJumpRule("FORWARD", IsolationChain1)
   192  	d.Unlock()
   193  	return err
   194  }
   195  
   196  type iptRule struct {
   197  	table   iptables.Table
   198  	chain   string
   199  	preArgs []string
   200  	args    []string
   201  }
   202  
   203  func setupIPTablesInternal(hostIP net.IP, bridgeIface string, addr *net.IPNet, icc, ipmasq, hairpin, enable bool) error {
   204  
   205  	var (
   206  		address   = addr.String()
   207  		skipDNAT  = iptRule{table: iptables.Nat, chain: DockerChain, preArgs: []string{"-t", "nat"}, args: []string{"-i", bridgeIface, "-j", "RETURN"}}
   208  		outRule   = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}}
   209  		natArgs   []string
   210  		hpNatArgs []string
   211  	)
   212  	// if hostIP is set use this address as the src-ip during SNAT
   213  	if hostIP != nil {
   214  		hostAddr := hostIP.String()
   215  		natArgs = []string{"-s", address, "!", "-o", bridgeIface, "-j", "SNAT", "--to-source", hostAddr}
   216  		hpNatArgs = []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "SNAT", "--to-source", hostAddr}
   217  		// Else use MASQUERADE which picks the src-ip based on NH from the route table
   218  	} else {
   219  		natArgs = []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"}
   220  		hpNatArgs = []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}
   221  	}
   222  
   223  	natRule := iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: natArgs}
   224  	hpNatRule := iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: hpNatArgs}
   225  
   226  	ipVersion := iptables.IPv4
   227  
   228  	if addr.IP.To4() == nil {
   229  		ipVersion = iptables.IPv6
   230  	}
   231  
   232  	// Set NAT.
   233  	if ipmasq {
   234  		if err := programChainRule(ipVersion, natRule, "NAT", enable); err != nil {
   235  			return err
   236  		}
   237  	}
   238  
   239  	if ipmasq && !hairpin {
   240  		if err := programChainRule(ipVersion, skipDNAT, "SKIP DNAT", enable); err != nil {
   241  			return err
   242  		}
   243  	}
   244  
   245  	// In hairpin mode, masquerade traffic from localhost
   246  	if hairpin {
   247  		if err := programChainRule(ipVersion, hpNatRule, "MASQ LOCAL HOST", enable); err != nil {
   248  			return err
   249  		}
   250  	}
   251  
   252  	// Set Inter Container Communication.
   253  	if err := setIcc(ipVersion, bridgeIface, icc, enable); err != nil {
   254  		return err
   255  	}
   256  
   257  	// Set Accept on all non-intercontainer outgoing packets.
   258  	return programChainRule(ipVersion, outRule, "ACCEPT NON_ICC OUTGOING", enable)
   259  }
   260  
   261  func programChainRule(version iptables.IPVersion, rule iptRule, ruleDescr string, insert bool) error {
   262  
   263  	iptable := iptables.GetIptable(version)
   264  
   265  	var (
   266  		prefix    []string
   267  		operation string
   268  		condition bool
   269  		doesExist = iptable.Exists(rule.table, rule.chain, rule.args...)
   270  	)
   271  
   272  	if insert {
   273  		condition = !doesExist
   274  		prefix = []string{"-I", rule.chain}
   275  		operation = "enable"
   276  	} else {
   277  		condition = doesExist
   278  		prefix = []string{"-D", rule.chain}
   279  		operation = "disable"
   280  	}
   281  	if rule.preArgs != nil {
   282  		prefix = append(rule.preArgs, prefix...)
   283  	}
   284  
   285  	if condition {
   286  		if err := iptable.RawCombinedOutput(append(prefix, rule.args...)...); err != nil {
   287  			return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
   288  		}
   289  	}
   290  
   291  	return nil
   292  }
   293  
   294  func setIcc(version iptables.IPVersion, bridgeIface string, iccEnable, insert bool) error {
   295  	iptable := iptables.GetIptable(version)
   296  	var (
   297  		table      = iptables.Filter
   298  		chain      = "FORWARD"
   299  		args       = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"}
   300  		acceptArgs = append(args, "ACCEPT")
   301  		dropArgs   = append(args, "DROP")
   302  	)
   303  
   304  	if insert {
   305  		if !iccEnable {
   306  			iptable.Raw(append([]string{"-D", chain}, acceptArgs...)...)
   307  
   308  			if !iptable.Exists(table, chain, dropArgs...) {
   309  				if err := iptable.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil {
   310  					return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
   311  				}
   312  			}
   313  		} else {
   314  			iptable.Raw(append([]string{"-D", chain}, dropArgs...)...)
   315  
   316  			if !iptable.Exists(table, chain, acceptArgs...) {
   317  				if err := iptable.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
   318  					return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
   319  				}
   320  			}
   321  		}
   322  	} else {
   323  		// Remove any ICC rule.
   324  		if !iccEnable {
   325  			if iptable.Exists(table, chain, dropArgs...) {
   326  				iptable.Raw(append([]string{"-D", chain}, dropArgs...)...)
   327  			}
   328  		} else {
   329  			if iptable.Exists(table, chain, acceptArgs...) {
   330  				iptable.Raw(append([]string{"-D", chain}, acceptArgs...)...)
   331  			}
   332  		}
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  // Control Inter Network Communication. Install[Remove] only if it is [not] present.
   339  func setINC(version iptables.IPVersion, iface string, enable bool) error {
   340  	iptable := iptables.GetIptable(version)
   341  	var (
   342  		action    = iptables.Insert
   343  		actionMsg = "add"
   344  		chains    = []string{IsolationChain1, IsolationChain2}
   345  		rules     = [][]string{
   346  			{"-i", iface, "!", "-o", iface, "-j", IsolationChain2},
   347  			{"-o", iface, "-j", "DROP"},
   348  		}
   349  	)
   350  
   351  	if !enable {
   352  		action = iptables.Delete
   353  		actionMsg = "remove"
   354  	}
   355  
   356  	for i, chain := range chains {
   357  		if err := iptable.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil {
   358  			msg := fmt.Sprintf("unable to %s inter-network communication rule: %v", actionMsg, err)
   359  			if enable {
   360  				if i == 1 {
   361  					// Rollback the rule installed on first chain
   362  					if err2 := iptable.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil {
   363  						logrus.Warnf("Failed to rollback iptables rule after failure (%v): %v", err, err2)
   364  					}
   365  				}
   366  				return fmt.Errorf(msg)
   367  			}
   368  			logrus.Warn(msg)
   369  		}
   370  	}
   371  
   372  	return nil
   373  }
   374  
   375  // Obsolete chain from previous docker versions
   376  const oldIsolationChain = "DOCKER-ISOLATION"
   377  
   378  func removeIPChains(version iptables.IPVersion) {
   379  	ipt := iptables.IPTable{Version: version}
   380  
   381  	// Remove obsolete rules from default chains
   382  	ipt.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain})
   383  
   384  	// Remove chains
   385  	for _, chainInfo := range []iptables.ChainInfo{
   386  		{Name: DockerChain, Table: iptables.Nat, IPTable: ipt},
   387  		{Name: DockerChain, Table: iptables.Filter, IPTable: ipt},
   388  		{Name: IsolationChain1, Table: iptables.Filter, IPTable: ipt},
   389  		{Name: IsolationChain2, Table: iptables.Filter, IPTable: ipt},
   390  		{Name: oldIsolationChain, Table: iptables.Filter, IPTable: ipt},
   391  	} {
   392  
   393  		if err := chainInfo.Remove(); err != nil {
   394  			logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err)
   395  		}
   396  	}
   397  }
   398  
   399  func setupInternalNetworkRules(bridgeIface string, addr *net.IPNet, icc, insert bool) error {
   400  	var (
   401  		inDropRule  = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}}
   402  		outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}}
   403  	)
   404  
   405  	version := iptables.IPv4
   406  
   407  	if addr.IP.To4() == nil {
   408  		version = iptables.IPv6
   409  	}
   410  
   411  	if err := programChainRule(version, inDropRule, "DROP INCOMING", insert); err != nil {
   412  		return err
   413  	}
   414  	if err := programChainRule(version, outDropRule, "DROP OUTGOING", insert); err != nil {
   415  		return err
   416  	}
   417  	// Set Inter Container Communication.
   418  	return setIcc(version, bridgeIface, icc, insert)
   419  }
   420  
   421  func clearEndpointConnections(nlh *netlink.Handle, ep *bridgeEndpoint) {
   422  	var ipv4List []net.IP
   423  	var ipv6List []net.IP
   424  	if ep.addr != nil {
   425  		ipv4List = append(ipv4List, ep.addr.IP)
   426  	}
   427  	if ep.addrv6 != nil {
   428  		ipv6List = append(ipv6List, ep.addrv6.IP)
   429  	}
   430  	iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List)
   431  }