github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/libnetwork/drivers/bridge/setup_ip_tables.go (about)

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