github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/libnetwork/drivers/bridge/setup_ip_tables.go (about)

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