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