github.com/moby/docker@v26.1.3+incompatible/libnetwork/drivers/bridge/setup_ip_tables_linux.go (about)

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