github.com/rawahars/moby@v24.0.4+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. If hairpin is disabled or if we're tearing down
   248  	// that bridge, make sure the iptables rule isn't lying around.
   249  	if err := programChainRule(ipVersion, hpNatRule, "MASQ LOCAL HOST", enable && hairpin); err != nil {
   250  		return err
   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  		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  // clearConntrackEntries flushes conntrack entries matching endpoint IP address
   422  // or matching one of the exposed UDP port.
   423  // In the first case, this could happen if packets were received by the host
   424  // between userland proxy startup and iptables setup.
   425  // In the latter case, this could happen if packets were received whereas there
   426  // were nowhere to route them, as netfilter creates entries in such case.
   427  // This is required because iptables NAT rules are evaluated by netfilter only
   428  // when creating a new conntrack entry. When Docker latter adds NAT rules,
   429  // netfilter ignore them for any packet matching a pre-existing conntrack entry.
   430  // As such, we need to flush all those conntrack entries to make sure NAT rules
   431  // are correctly applied to all packets.
   432  // See: #8795, #44688 & #44742.
   433  func clearConntrackEntries(nlh *netlink.Handle, ep *bridgeEndpoint) {
   434  	var ipv4List []net.IP
   435  	var ipv6List []net.IP
   436  	var udpPorts []uint16
   437  
   438  	if ep.addr != nil {
   439  		ipv4List = append(ipv4List, ep.addr.IP)
   440  	}
   441  	if ep.addrv6 != nil {
   442  		ipv6List = append(ipv6List, ep.addrv6.IP)
   443  	}
   444  	for _, pb := range ep.portMapping {
   445  		if pb.Proto == types.UDP {
   446  			udpPorts = append(udpPorts, pb.HostPort)
   447  		}
   448  	}
   449  
   450  	iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List)
   451  	iptables.DeleteConntrackEntriesByPort(nlh, types.UDP, udpPorts)
   452  }