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