From 8dbb5b5a7d42f55cd112048dc579868793179660 Mon Sep 17 00:00:00 2001 From: Billy Ridgway Date: Tue, 28 Nov 2017 15:15:55 -0600 Subject: [PATCH] Implement NAT IPv6 to fix the issue https://github.com/moby/moby/issues/25407 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Billy Ridgway Signed-off-by: Benjamin Böhmke --- libnetwork/drivers/bridge/bridge.go | 106 +++++++--- libnetwork/drivers/bridge/bridge_test.go | 41 ++-- libnetwork/drivers/bridge/port_mapping.go | 39 +++- libnetwork/drivers/bridge/setup_firewalld.go | 4 + .../drivers/bridge/setup_ip_forwarding.go | 5 +- libnetwork/drivers/bridge/setup_ip_tables.go | 177 ++++++++++++----- .../drivers/bridge/setup_ip_tables_test.go | 12 +- libnetwork/drivers/overlay/encryption.go | 12 +- libnetwork/drivers/overlay/filter.go | 34 ++-- libnetwork/firewall_linux.go | 7 +- libnetwork/firewall_test.go | 16 +- libnetwork/iptables/firewalld_test.go | 12 +- libnetwork/iptables/iptables.go | 184 ++++++++++++------ libnetwork/iptables/iptables_test.go | 55 +++--- libnetwork/libnetwork_test.go | 2 +- libnetwork/portmapper/mapper.go | 12 +- libnetwork/resolver_unix.go | 20 +- libnetwork/service_linux.go | 70 ++++--- 18 files changed, 535 insertions(+), 273 deletions(-) diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index 88fbafaea3..17189cf8e8 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/libnetwork/drivers/bridge/bridge.go @@ -58,6 +58,7 @@ type configuration struct { EnableIPForwarding bool EnableIPTables bool EnableUserlandProxy bool + EnableIPv6 bool UserlandProxyPath string } @@ -133,22 +134,27 @@ type bridgeNetwork struct { config *networkConfiguration endpoints map[string]*bridgeEndpoint // key: endpoint id portMapper *portmapper.PortMapper + portMapperV6 *portmapper.PortMapper driver *driver // The network's driver iptCleanFuncs iptablesCleanFuncs sync.Mutex } type driver struct { - config *configuration - network *bridgeNetwork - natChain *iptables.ChainInfo - filterChain *iptables.ChainInfo - isolationChain1 *iptables.ChainInfo - isolationChain2 *iptables.ChainInfo - networks map[string]*bridgeNetwork - store datastore.DataStore - nlh *netlink.Handle - configNetwork sync.Mutex + config *configuration + network *bridgeNetwork + natChain *iptables.ChainInfo + filterChain *iptables.ChainInfo + isolationChain1 *iptables.ChainInfo + isolationChain2 *iptables.ChainInfo + natChainV6 *iptables.ChainInfo + filterChainV6 *iptables.ChainInfo + isolationChain1V6 *iptables.ChainInfo + isolationChain2V6 *iptables.ChainInfo + networks map[string]*bridgeNetwork + store datastore.DataStore + nlh *netlink.Handle + configNetwork sync.Mutex sync.Mutex } @@ -277,7 +283,7 @@ func (n *bridgeNetwork) registerIptCleanFunc(clean iptableCleanFunc) { n.iptCleanFuncs = append(n.iptCleanFuncs, clean) } -func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) { +func (n *bridgeNetwork) getDriverChains(version iptables.IPVersion) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) { n.Lock() defer n.Unlock() @@ -285,6 +291,10 @@ func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainI return nil, nil, nil, nil, types.BadRequestErrorf("no driver found") } + if version == iptables.IPv6 { + return n.driver.natChainV6, n.driver.filterChainV6, n.driver.isolationChain1V6, n.driver.isolationChain2V6, nil + } + return n.driver.natChain, n.driver.filterChain, n.driver.isolationChain1, n.driver.isolationChain2, nil } @@ -313,7 +323,7 @@ func (n *bridgeNetwork) getEndpoint(eid string) (*bridgeEndpoint, error) { // Install/Removes the iptables rules needed to isolate this network // from each of the other networks -func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) error { +func (n *bridgeNetwork) isolateNetwork(version iptables.IPVersion, others []*bridgeNetwork, enable bool) error { n.Lock() thisConfig := n.config n.Unlock() @@ -323,17 +333,21 @@ func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) err } // Install the rules to isolate this network against each of the other networks - return setINC(thisConfig.BridgeName, enable) + return setINC(version, thisConfig.BridgeName, enable) } func (d *driver) configure(option map[string]interface{}) error { var ( - config *configuration - err error - natChain *iptables.ChainInfo - filterChain *iptables.ChainInfo - isolationChain1 *iptables.ChainInfo - isolationChain2 *iptables.ChainInfo + config *configuration + err error + natChain *iptables.ChainInfo + filterChain *iptables.ChainInfo + isolationChain1 *iptables.ChainInfo + isolationChain2 *iptables.ChainInfo + natChainV6 *iptables.ChainInfo + filterChainV6 *iptables.ChainInfo + isolationChain1V6 *iptables.ChainInfo + isolationChain2V6 *iptables.ChainInfo ) genericData, ok := option[netlabel.GenericData] @@ -360,13 +374,32 @@ func (d *driver) configure(option map[string]interface{}) error { logrus.Warnf("Running modprobe bridge br_netfilter failed with message: %s, error: %v", out, err) } } - removeIPChains() - natChain, filterChain, isolationChain1, isolationChain2, err = setupIPChains(config) + + removeIPChains(iptables.IPv4) + if config.EnableIPv6 { + removeIPChains(iptables.IPv6) + } + + natChain, filterChain, isolationChain1, isolationChain2, err = setupIPChains(config, iptables.IPv4) if err != nil { return err } + if config.EnableIPv6 { + natChainV6, filterChainV6, isolationChain1V6, isolationChain2V6, err = setupIPChains(config, iptables.IPv6) + if err != nil { + return err + } + } + // Make sure on firewall reload, first thing being re-played is chains creation - iptables.OnReloaded(func() { logrus.Debugf("Recreating iptables chains on firewall reload"); setupIPChains(config) }) + iptables.OnReloaded(func() { + logrus.Debugf("Recreating iptables chains on firewall reload") + setupIPChains(config, iptables.IPv4) + }) + iptables.OnReloaded(func() { + logrus.Debugf("Recreating ip6tables chains on firewall reload") + setupIPChains(config, iptables.IPv6) + }) } if config.EnableIPForwarding { @@ -375,6 +408,12 @@ func (d *driver) configure(option map[string]interface{}) error { logrus.Warn(err) return err } + if config.EnableIPv6 { + iptable := iptables.GetIptable(iptables.IPv6) + if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil { + logrus.Warnf("Setting the default DROP policy on firewall reload failed, %v", err) + } + } } d.Lock() @@ -382,6 +421,10 @@ func (d *driver) configure(option map[string]interface{}) error { d.filterChain = filterChain d.isolationChain1 = isolationChain1 d.isolationChain2 = isolationChain2 + d.natChainV6 = natChainV6 + d.filterChainV6 = filterChainV6 + d.isolationChain1V6 = isolationChain1V6 + d.isolationChain2V6 = isolationChain2V6 d.config = config d.Unlock() @@ -644,12 +687,13 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) { // Create and set network handler in driver network := &bridgeNetwork{ - id: config.ID, - endpoints: make(map[string]*bridgeEndpoint), - config: config, - portMapper: portmapper.New(d.config.UserlandProxyPath), - bridge: bridgeIface, - driver: d, + id: config.ID, + endpoints: make(map[string]*bridgeEndpoint), + config: config, + portMapper: portmapper.New(d.config.UserlandProxyPath), + portMapperV6: portmapper.New(d.config.UserlandProxyPath), + bridge: bridgeIface, + driver: d, } d.Lock() @@ -667,8 +711,8 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) { // Add inter-network communication rules. setupNetworkIsolationRules := func(config *networkConfiguration, i *bridgeInterface) error { - if err := network.isolateNetwork(networkList, true); err != nil { - if err = network.isolateNetwork(networkList, false); err != nil { + if err := network.isolateNetwork(iptables.IPv4, networkList, true); err != nil { + if err = network.isolateNetwork(iptables.IPv4, networkList, false); err != nil { logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err) } return err @@ -676,7 +720,7 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) { // register the cleanup function network.registerIptCleanFunc(func() error { nwList := d.getNetworks() - return network.isolateNetwork(nwList, false) + return network.isolateNetwork(iptables.IPv4, nwList, false) }) return nil } diff --git a/libnetwork/drivers/bridge/bridge_test.go b/libnetwork/drivers/bridge/bridge_test.go index 52e1f5c1ab..77a8093749 100644 --- a/libnetwork/drivers/bridge/bridge_test.go +++ b/libnetwork/drivers/bridge/bridge_test.go @@ -468,11 +468,12 @@ func TestCreateMultipleNetworks(t *testing.T) { // Verify the network isolation rules are installed for each network func verifyV4INCEntries(networks map[string]*bridgeNetwork, t *testing.T) { - out1, err := iptables.Raw("-S", IsolationChain1) + iptable := iptables.GetIptable(iptables.IPv4) + out1, err := iptable.Raw("-S", IsolationChain1) if err != nil { t.Fatal(err) } - out2, err := iptables.Raw("-S", IsolationChain2) + out2, err := iptable.Raw("-S", IsolationChain2) if err != nil { t.Fatal(err) } @@ -715,6 +716,7 @@ func TestLinkContainers(t *testing.T) { } d := newDriver() + iptable := iptables.GetIptable(iptables.IPv4) config := &configuration{ EnableIPTables: true, @@ -790,7 +792,7 @@ func TestLinkContainers(t *testing.T) { t.Fatalf("Failed to program external connectivity: %v", err) } - out, err := iptables.Raw("-L", DockerChain) + out, err := iptable.Raw("-L", DockerChain) for _, pm := range exposedPorts { regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port) re := regexp.MustCompile(regex) @@ -816,7 +818,7 @@ func TestLinkContainers(t *testing.T) { t.Fatal("Failed to unlink ep1 and ep2") } - out, err = iptables.Raw("-L", DockerChain) + out, err = iptable.Raw("-L", DockerChain) for _, pm := range exposedPorts { regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port) re := regexp.MustCompile(regex) @@ -844,7 +846,7 @@ func TestLinkContainers(t *testing.T) { } err = d.ProgramExternalConnectivity("net1", "ep2", sbOptions) if err != nil { - out, err = iptables.Raw("-L", DockerChain) + out, err = iptable.Raw("-L", DockerChain) for _, pm := range exposedPorts { regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port) re := regexp.MustCompile(regex) @@ -998,18 +1000,25 @@ func TestCleanupIptableRules(t *testing.T) { {Name: DockerChain, Table: iptables.Filter}, {Name: IsolationChain1, Table: iptables.Filter}, } - if _, _, _, _, err := setupIPChains(&configuration{EnableIPTables: true}); err != nil { - t.Fatalf("Error setting up ip chains: %v", err) - } - for _, chainInfo := range bridgeChain { - if !iptables.ExistChain(chainInfo.Name, chainInfo.Table) { - t.Fatalf("iptables chain %s of %s table should have been created", chainInfo.Name, chainInfo.Table) + + ipVersions := []iptables.IPVersion{iptables.IPv4, iptables.IPv6} + + for _, version := range ipVersions { + if _, _, _, _, err := setupIPChains(&configuration{EnableIPTables: true}, version); err != nil { + t.Fatalf("Error setting up ip chains for %s: %v", version, err) } - } - removeIPChains() - for _, chainInfo := range bridgeChain { - if iptables.ExistChain(chainInfo.Name, chainInfo.Table) { - t.Fatalf("iptables chain %s of %s table should have been deleted", chainInfo.Name, chainInfo.Table) + + iptable := iptables.GetIptable(version) + for _, chainInfo := range bridgeChain { + if !iptable.ExistChain(chainInfo.Name, chainInfo.Table) { + t.Fatalf("iptables version %s chain %s of %s table should have been created", version, chainInfo.Name, chainInfo.Table) + } + } + removeIPChains(version) + for _, chainInfo := range bridgeChain { + if iptable.ExistChain(chainInfo.Name, chainInfo.Table) { + t.Fatalf("iptables version %s chain %s of %s table should have been deleted", version, chainInfo.Name, chainInfo.Table) + } } } } diff --git a/libnetwork/drivers/bridge/port_mapping.go b/libnetwork/drivers/bridge/port_mapping.go index 853129fc27..f55905372c 100644 --- a/libnetwork/drivers/bridge/port_mapping.go +++ b/libnetwork/drivers/bridge/port_mapping.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "strings" "github.com/docker/libnetwork/types" "github.com/ishidawataru/sctp" @@ -12,7 +13,8 @@ import ( ) var ( - defaultBindingIP = net.IPv4(0, 0, 0, 0) + defaultBindingIP = net.IPv4(0, 0, 0, 0) + defaultBindingIPV6 = net.ParseIP("::") ) func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) { @@ -25,11 +27,23 @@ func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, u defHostIP = reqDefBindIP } - return n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled) + var pb []types.PortBinding + + if ep.addrv6 != nil { + pb, _ = n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addrv6.IP, defaultBindingIPV6, ulPxyEnabled, nil) + } + + return n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled, pb) } -func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) { - bs := make([]types.PortBinding, 0, len(bindings)) +func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool, existingPortBindings []types.PortBinding) ([]types.PortBinding, error) { + + bs := existingPortBindings + + if existingPortBindings == nil { + bs = make([]types.PortBinding, 0, len(bindings)) + } + for _, c := range bindings { b := c.GetCopy() if err := n.allocatePort(&b, containerIP, defHostIP, ulPxyEnabled); err != nil { @@ -69,9 +83,15 @@ func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHos return err } + portmapper := n.portMapper + + if containerIP.To4() == nil { + portmapper = n.portMapperV6 + } + // Try up to maxAllocatePortAttempts times to get a port that's not already allocated. for i := 0; i < maxAllocatePortAttempts; i++ { - if host, err = n.portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled); err == nil { + if host, err = portmapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled); err == nil { break } // There is no point in immediately retrying to map an explicitly chosen port. @@ -128,5 +148,12 @@ func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error { if err != nil { return err } - return n.portMapper.Unmap(host) + + portmapper := n.portMapper + + if strings.ContainsAny(host.String(), "]") == true { + portmapper = n.portMapperV6 + } + + return portmapper.Unmap(host) } diff --git a/libnetwork/drivers/bridge/setup_firewalld.go b/libnetwork/drivers/bridge/setup_firewalld.go index 50cbdb1ddc..1712bf8c97 100644 --- a/libnetwork/drivers/bridge/setup_firewalld.go +++ b/libnetwork/drivers/bridge/setup_firewalld.go @@ -16,5 +16,9 @@ func (n *bridgeNetwork) setupFirewalld(config *networkConfiguration, i *bridgeIn iptables.OnReloaded(func() { n.setupIPTables(config, i) }) iptables.OnReloaded(n.portMapper.ReMapAll) + if driverConfig.EnableIPv6 == true { + iptables.OnReloaded(n.portMapperV6.ReMapAll) + } + return nil } diff --git a/libnetwork/drivers/bridge/setup_ip_forwarding.go b/libnetwork/drivers/bridge/setup_ip_forwarding.go index 10f61a1868..0526bd5b1a 100644 --- a/libnetwork/drivers/bridge/setup_ip_forwarding.go +++ b/libnetwork/drivers/bridge/setup_ip_forwarding.go @@ -39,7 +39,8 @@ func setupIPForwarding(enableIPTables bool) error { if !enableIPTables { return nil } - if err := iptables.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil { + iptable := iptables.GetIptable(iptables.IPv4) + if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil { if err := configureIPForwarding(false); err != nil { logrus.Errorf("Disabling IP forwarding failed, %v", err) } @@ -47,7 +48,7 @@ func setupIPForwarding(enableIPTables bool) error { } iptables.OnReloaded(func() { logrus.Debug("Setting the default DROP policy on firewall reload") - if err := iptables.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil { + if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil { logrus.Warnf("Setting the default DROP policy on firewall reload failed, %v", err) } }) diff --git a/libnetwork/drivers/bridge/setup_ip_tables.go b/libnetwork/drivers/bridge/setup_ip_tables.go index e59b9ca828..8ae37b72f5 100644 --- a/libnetwork/drivers/bridge/setup_ip_tables.go +++ b/libnetwork/drivers/bridge/setup_ip_tables.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net" + "strings" "github.com/docker/libnetwork/iptables" "github.com/sirupsen/logrus" @@ -26,7 +27,7 @@ const ( IsolationChain2 = "DOCKER-ISOLATION-STAGE-2" ) -func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) { +func setupIPChains(config *configuration, version iptables.IPVersion) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) { // Sanity check. if config.EnableIPTables == false { return nil, nil, nil, nil, errors.New("cannot create new chains, EnableIPTable is disabled") @@ -34,59 +35,61 @@ func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainI hairpinMode := !config.EnableUserlandProxy - natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode) + iptable := iptables.GetIptable(version) + + natChain, err := iptable.NewChain(DockerChain, iptables.Nat, hairpinMode) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain %s: %v", DockerChain, err) } defer func() { if err != nil { - if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil { + if err := iptable.RemoveExistingChain(DockerChain, iptables.Nat); err != nil { logrus.Warnf("failed on removing iptables NAT chain %s on cleanup: %v", DockerChain, err) } } }() - filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false) + filterChain, err := iptable.NewChain(DockerChain, iptables.Filter, false) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER chain %s: %v", DockerChain, err) } defer func() { if err != nil { - if err := iptables.RemoveExistingChain(DockerChain, iptables.Filter); err != nil { + if err := iptable.RemoveExistingChain(DockerChain, iptables.Filter); err != nil { logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", DockerChain, err) } } }() - isolationChain1, err := iptables.NewChain(IsolationChain1, iptables.Filter, false) + isolationChain1, err := iptable.NewChain(IsolationChain1, iptables.Filter, false) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err) } defer func() { if err != nil { - if err := iptables.RemoveExistingChain(IsolationChain1, iptables.Filter); err != nil { + if err := iptable.RemoveExistingChain(IsolationChain1, iptables.Filter); err != nil { logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain1, err) } } }() - isolationChain2, err := iptables.NewChain(IsolationChain2, iptables.Filter, false) + isolationChain2, err := iptable.NewChain(IsolationChain2, iptables.Filter, false) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err) } defer func() { if err != nil { - if err := iptables.RemoveExistingChain(IsolationChain2, iptables.Filter); err != nil { + if err := iptable.RemoveExistingChain(IsolationChain2, iptables.Filter); err != nil { logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain2, err) } } }() - if err := iptables.AddReturnRule(IsolationChain1); err != nil { + if err := iptable.AddReturnRule(IsolationChain1); err != nil { return nil, nil, nil, nil, err } - if err := iptables.AddReturnRule(IsolationChain2); err != nil { + if err := iptable.AddReturnRule(IsolationChain2); err != nil { return nil, nil, nil, nil, err } @@ -113,6 +116,9 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt IP: i.bridgeIPv4.IP.Mask(i.bridgeIPv4.Mask), Mask: i.bridgeIPv4.Mask, } + + iptable := iptables.GetIptable(iptables.IPv4) + if config.Internal { if err = setupInternalNetworkRules(config.BridgeName, maskedAddrv4, config.EnableICC, true); err != nil { return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) @@ -127,30 +133,84 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt n.registerIptCleanFunc(func() error { return setupIPTablesInternal(config.HostIP, config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false) }) - natChain, filterChain, _, _, err := n.getDriverChains() + natChain, filterChain, _, _, err := n.getDriverChains(iptables.IPv4) if err != nil { return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error()) } - err = iptables.ProgramChain(natChain, config.BridgeName, hairpinMode, true) + err = iptable.ProgramChain(natChain, config.BridgeName, hairpinMode, true) if err != nil { return fmt.Errorf("Failed to program NAT chain: %s", err.Error()) } - err = iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, true) + err = iptable.ProgramChain(filterChain, config.BridgeName, hairpinMode, true) if err != nil { return fmt.Errorf("Failed to program FILTER chain: %s", err.Error()) } n.registerIptCleanFunc(func() error { - return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false) + return iptable.ProgramChain(filterChain, config.BridgeName, hairpinMode, false) }) n.portMapper.SetIptablesChain(natChain, n.getNetworkBridgeName()) } d.Lock() - err = iptables.EnsureJumpRule("FORWARD", IsolationChain1) + err = iptable.EnsureJumpRule("FORWARD", IsolationChain1) + d.Unlock() + if err != nil { + return err + } + + if !driverConfig.EnableIPv6 || i.bridgeIPv6 == nil { + return nil + } + + maskedAddrv6 := &net.IPNet{ + IP: i.bridgeIPv6.IP.Mask(i.bridgeIPv6.Mask), + Mask: i.bridgeIPv6.Mask, + } + + iptable = iptables.GetIptable(iptables.IPv6) + + if config.Internal { + if err = setupInternalNetworkRules(config.BridgeName, maskedAddrv6, config.EnableICC, true); err != nil { + return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) + } + n.registerIptCleanFunc(func() error { + return setupInternalNetworkRules(config.BridgeName, maskedAddrv6, config.EnableICC, false) + }) + } else { + if err = setupIPTablesInternal(nil, config.BridgeName, maskedAddrv6, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil { + return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) + } + n.registerIptCleanFunc(func() error { + return setupIPTablesInternal(nil, config.BridgeName, maskedAddrv6, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false) + }) + natChainV6, filterChainV6, _, _, err := n.getDriverChains(iptables.IPv6) + if err != nil { + return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error()) + } + + err = iptable.ProgramChain(natChainV6, config.BridgeName, hairpinMode, true) + if err != nil { + return fmt.Errorf("Failed to program NAT chain: %s", err.Error()) + } + + err = iptable.ProgramChain(filterChainV6, config.BridgeName, hairpinMode, true) + if err != nil { + return fmt.Errorf("Failed to program FILTER chain: %s", err.Error()) + } + + n.registerIptCleanFunc(func() error { + return iptable.ProgramChain(filterChainV6, config.BridgeName, hairpinMode, false) + }) + + n.portMapperV6.SetIptablesChain(natChainV6, n.getNetworkBridgeName()) + } + + d.Lock() + err = iptable.EnsureJumpRule("FORWARD", IsolationChain1) d.Unlock() if err != nil { return err @@ -189,41 +249,50 @@ func setupIPTablesInternal(hostIP net.IP, bridgeIface string, addr net.Addr, icc natRule := iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: natArgs} hpNatRule := iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: hpNatArgs} + ipVersion := iptables.IPv4 + + if strings.Contains(address, ":") { + ipVersion = iptables.IPv6 + } + // Set NAT. if ipmasq { - if err := programChainRule(natRule, "NAT", enable); err != nil { + if err := programChainRule(ipVersion, natRule, "NAT", enable); err != nil { return err } } if ipmasq && !hairpin { - if err := programChainRule(skipDNAT, "SKIP DNAT", enable); err != nil { + if err := programChainRule(ipVersion, skipDNAT, "SKIP DNAT", enable); err != nil { return err } } // In hairpin mode, masquerade traffic from localhost if hairpin { - if err := programChainRule(hpNatRule, "MASQ LOCAL HOST", enable); err != nil { + if err := programChainRule(ipVersion, hpNatRule, "MASQ LOCAL HOST", enable); err != nil { return err } } // Set Inter Container Communication. - if err := setIcc(bridgeIface, icc, enable); err != nil { + if err := setIcc(ipVersion, bridgeIface, icc, enable); err != nil { return err } // Set Accept on all non-intercontainer outgoing packets. - return programChainRule(outRule, "ACCEPT NON_ICC OUTGOING", enable) + return programChainRule(ipVersion, outRule, "ACCEPT NON_ICC OUTGOING", enable) } -func programChainRule(rule iptRule, ruleDescr string, insert bool) error { +func programChainRule(version iptables.IPVersion, rule iptRule, ruleDescr string, insert bool) error { + + iptable := iptables.GetIptable(version) + var ( prefix []string operation string condition bool - doesExist = iptables.Exists(rule.table, rule.chain, rule.args...) + doesExist = iptable.Exists(rule.table, rule.chain, rule.args...) ) if insert { @@ -240,7 +309,7 @@ func programChainRule(rule iptRule, ruleDescr string, insert bool) error { } if condition { - if err := iptables.RawCombinedOutput(append(prefix, rule.args...)...); err != nil { + if err := iptable.RawCombinedOutput(append(prefix, rule.args...)...); err != nil { return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error()) } } @@ -248,7 +317,8 @@ func programChainRule(rule iptRule, ruleDescr string, insert bool) error { return nil } -func setIcc(bridgeIface string, iccEnable, insert bool) error { +func setIcc(version iptables.IPVersion, bridgeIface string, iccEnable, insert bool) error { + iptable := iptables.GetIptable(version) var ( table = iptables.Filter chain = "FORWARD" @@ -259,18 +329,18 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error { if insert { if !iccEnable { - iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...) + iptable.Raw(append([]string{"-D", chain}, acceptArgs...)...) - if !iptables.Exists(table, chain, dropArgs...) { - if err := iptables.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil { + if !iptable.Exists(table, chain, dropArgs...) { + if err := iptable.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil { return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error()) } } } else { - iptables.Raw(append([]string{"-D", chain}, dropArgs...)...) + iptable.Raw(append([]string{"-D", chain}, dropArgs...)...) - if !iptables.Exists(table, chain, acceptArgs...) { - if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil { + if !iptable.Exists(table, chain, acceptArgs...) { + if err := iptable.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil { return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error()) } } @@ -278,12 +348,12 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error { } else { // Remove any ICC rule. if !iccEnable { - if iptables.Exists(table, chain, dropArgs...) { - iptables.Raw(append([]string{"-D", chain}, dropArgs...)...) + if iptable.Exists(table, chain, dropArgs...) { + iptable.Raw(append([]string{"-D", chain}, dropArgs...)...) } } else { - if iptables.Exists(table, chain, acceptArgs...) { - iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...) + if iptable.Exists(table, chain, acceptArgs...) { + iptable.Raw(append([]string{"-D", chain}, acceptArgs...)...) } } } @@ -292,7 +362,8 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error { } // Control Inter Network Communication. Install[Remove] only if it is [not] present. -func setINC(iface string, enable bool) error { +func setINC(version iptables.IPVersion, iface string, enable bool) error { + iptable := iptables.GetIptable(version) var ( action = iptables.Insert actionMsg = "add" @@ -309,12 +380,12 @@ func setINC(iface string, enable bool) error { } for i, chain := range chains { - if err := iptables.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil { + if err := iptable.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil { msg := fmt.Sprintf("unable to %s inter-network communication rule: %v", actionMsg, err) if enable { if i == 1 { // Rollback the rule installed on first chain - if err2 := iptables.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil { + if err2 := iptable.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil { logrus.Warnf("Failed to rollback iptables rule after failure (%v): %v", err, err2) } } @@ -330,18 +401,21 @@ func setINC(iface string, enable bool) error { // Obsolete chain from previous docker versions const oldIsolationChain = "DOCKER-ISOLATION" -func removeIPChains() { +func removeIPChains(version iptables.IPVersion) { + ipt := iptables.IPTable{Version: version} + // Remove obsolete rules from default chains - iptables.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain}) + ipt.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain}) // Remove chains for _, chainInfo := range []iptables.ChainInfo{ - {Name: DockerChain, Table: iptables.Nat}, - {Name: DockerChain, Table: iptables.Filter}, - {Name: IsolationChain1, Table: iptables.Filter}, - {Name: IsolationChain2, Table: iptables.Filter}, - {Name: oldIsolationChain, Table: iptables.Filter}, + {Name: DockerChain, Table: iptables.Nat, IPTable: ipt}, + {Name: DockerChain, Table: iptables.Filter, IPTable: ipt}, + {Name: IsolationChain1, Table: iptables.Filter, IPTable: ipt}, + {Name: IsolationChain2, Table: iptables.Filter, IPTable: ipt}, + {Name: oldIsolationChain, Table: iptables.Filter, IPTable: ipt}, } { + if err := chainInfo.Remove(); err != nil { logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err) } @@ -353,14 +427,21 @@ func setupInternalNetworkRules(bridgeIface string, addr net.Addr, icc, insert bo inDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}} outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}} ) - if err := programChainRule(inDropRule, "DROP INCOMING", insert); err != nil { + + version := iptables.IPv4 + + if strings.Contains(addr.String(), ":") { + version = iptables.IPv6 + } + + if err := programChainRule(version, inDropRule, "DROP INCOMING", insert); err != nil { return err } - if err := programChainRule(outDropRule, "DROP OUTGOING", insert); err != nil { + if err := programChainRule(version, outDropRule, "DROP OUTGOING", insert); err != nil { return err } // Set Inter Container Communication. - return setIcc(bridgeIface, icc, insert) + return setIcc(version, bridgeIface, icc, insert) } func clearEndpointConnections(nlh *netlink.Handle, ep *bridgeEndpoint) { diff --git a/libnetwork/drivers/bridge/setup_ip_tables_test.go b/libnetwork/drivers/bridge/setup_ip_tables_test.go index ea8e5da16d..169b9d0fef 100644 --- a/libnetwork/drivers/bridge/setup_ip_tables_test.go +++ b/libnetwork/drivers/bridge/setup_ip_tables_test.go @@ -96,18 +96,20 @@ func createTestBridge(config *networkConfiguration, br *bridgeInterface, t *test // Assert base function which pushes iptables chain rules on insertion and removal. func assertIPTableChainProgramming(rule iptRule, descr string, t *testing.T) { // Add - if err := programChainRule(rule, descr, true); err != nil { + if err := programChainRule(iptables.IPv4, rule, descr, true); err != nil { t.Fatalf("Failed to program iptable rule %s: %s", descr, err.Error()) } - if iptables.Exists(rule.table, rule.chain, rule.args...) == false { + + iptable := iptables.GetIptable(iptables.IPv4) + if iptable.Exists(rule.table, rule.chain, rule.args...) == false { t.Fatalf("Failed to effectively program iptable rule: %s", descr) } // Remove - if err := programChainRule(rule, descr, false); err != nil { + if err := programChainRule(iptables.IPv4, rule, descr, false); err != nil { t.Fatalf("Failed to remove iptable rule %s: %s", descr, err.Error()) } - if iptables.Exists(rule.table, rule.chain, rule.args...) == true { + if iptable.Exists(rule.table, rule.chain, rule.args...) == true { t.Fatalf("Failed to effectively remove iptable rule: %s", descr) } } @@ -116,7 +118,7 @@ func assertIPTableChainProgramming(rule iptRule, descr string, t *testing.T) { func assertChainConfig(d *driver, t *testing.T) { var err error - d.natChain, d.filterChain, d.isolationChain1, d.isolationChain2, err = setupIPChains(d.config) + d.natChain, d.filterChain, d.isolationChain1, d.isolationChain2, err = setupIPChains(d.config, iptables.IPv4) if err != nil { t.Fatal(err) } diff --git a/libnetwork/drivers/overlay/encryption.go b/libnetwork/drivers/overlay/encryption.go index d71f81bdf0..2804ddc76a 100644 --- a/libnetwork/drivers/overlay/encryption.go +++ b/libnetwork/drivers/overlay/encryption.go @@ -210,7 +210,9 @@ func programMangle(vni uint32, add bool) (err error) { action = "install" ) - if add == iptables.Exists(iptables.Mangle, chain, rule...) { + iptable := iptables.GetIptable(iptables.IPv4) + + if add == iptable.Exists(iptables.Mangle, chain, rule...) { return } @@ -219,7 +221,7 @@ func programMangle(vni uint32, add bool) (err error) { action = "remove" } - if err = iptables.RawCombinedOutput(append([]string{"-t", string(iptables.Mangle), a, chain}, rule...)...); err != nil { + if err = iptable.RawCombinedOutput(append([]string{"-t", string(iptables.Mangle), a, chain}, rule...)...); err != nil { logrus.Warnf("could not %s mangle rule: %v", action, err) } @@ -239,16 +241,18 @@ func programInput(vni uint32, add bool) (err error) { msg = "add" ) + iptable := iptables.GetIptable(iptables.IPv4) + if !add { action = iptables.Delete msg = "remove" } - if err := iptables.ProgramRule(iptables.Filter, chain, action, accept); err != nil { + if err := iptable.ProgramRule(iptables.Filter, chain, action, accept); err != nil { logrus.Errorf("could not %s input rule: %v. Please do it manually.", msg, err) } - if err := iptables.ProgramRule(iptables.Filter, chain, action, block); err != nil { + if err := iptable.ProgramRule(iptables.Filter, chain, action, block); err != nil { logrus.Errorf("could not %s input rule: %v. Please do it manually.", msg, err) } diff --git a/libnetwork/drivers/overlay/filter.go b/libnetwork/drivers/overlay/filter.go index 1601803aa0..250c67153e 100644 --- a/libnetwork/drivers/overlay/filter.go +++ b/libnetwork/drivers/overlay/filter.go @@ -20,7 +20,8 @@ func filterWait() func() { } func chainExists(cname string) bool { - if _, err := iptables.Raw("-L", cname); err != nil { + iptable := iptables.GetIptable(iptables.IPv4) + if _, err := iptable.Raw("-L", cname); err != nil { return false } @@ -28,22 +29,24 @@ func chainExists(cname string) bool { } func setupGlobalChain() { + iptable := iptables.GetIptable(iptables.IPv4) // Because of an ungraceful shutdown, chain could already be present if !chainExists(globalChain) { - if err := iptables.RawCombinedOutput("-N", globalChain); err != nil { + if err := iptable.RawCombinedOutput("-N", globalChain); err != nil { logrus.Errorf("could not create global overlay chain: %v", err) return } } - if !iptables.Exists(iptables.Filter, globalChain, "-j", "RETURN") { - if err := iptables.RawCombinedOutput("-A", globalChain, "-j", "RETURN"); err != nil { + if !iptable.Exists(iptables.Filter, globalChain, "-j", "RETURN") { + if err := iptable.RawCombinedOutput("-A", globalChain, "-j", "RETURN"); err != nil { logrus.Errorf("could not install default return chain in the overlay global chain: %v", err) } } } func setNetworkChain(cname string, remove bool) error { + iptable := iptables.GetIptable(iptables.IPv4) // Initialize the onetime global overlay chain filterOnce.Do(setupGlobalChain) @@ -52,21 +55,21 @@ func setNetworkChain(cname string, remove bool) error { opt := "-N" // In case of remove, make sure to flush the rules in the chain if remove && exists { - if err := iptables.RawCombinedOutput("-F", cname); err != nil { + if err := iptable.RawCombinedOutput("-F", cname); err != nil { return fmt.Errorf("failed to flush overlay network chain %s rules: %v", cname, err) } opt = "-X" } if (!remove && !exists) || (remove && exists) { - if err := iptables.RawCombinedOutput(opt, cname); err != nil { + if err := iptable.RawCombinedOutput(opt, cname); err != nil { return fmt.Errorf("failed network chain operation %q for chain %s: %v", opt, cname, err) } } if !remove { - if !iptables.Exists(iptables.Filter, cname, "-j", "DROP") { - if err := iptables.RawCombinedOutput("-A", cname, "-j", "DROP"); err != nil { + if !iptable.Exists(iptables.Filter, cname, "-j", "DROP") { + if err := iptable.RawCombinedOutput("-A", cname, "-j", "DROP"); err != nil { return fmt.Errorf("failed adding default drop rule to overlay network chain %s: %v", cname, err) } } @@ -92,37 +95,38 @@ func setFilters(cname, brName string, remove bool) error { if remove { opt = "-D" } + iptable := iptables.GetIptable(iptables.IPv4) // Every time we set filters for a new subnet make sure to move the global overlay hook to the top of the both the OUTPUT and forward chains if !remove { for _, chain := range []string{"OUTPUT", "FORWARD"} { - exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain) + exists := iptable.Exists(iptables.Filter, chain, "-j", globalChain) if exists { - if err := iptables.RawCombinedOutput("-D", chain, "-j", globalChain); err != nil { + if err := iptable.RawCombinedOutput("-D", chain, "-j", globalChain); err != nil { return fmt.Errorf("failed to delete overlay hook in chain %s while moving the hook: %v", chain, err) } } - if err := iptables.RawCombinedOutput("-I", chain, "-j", globalChain); err != nil { + if err := iptable.RawCombinedOutput("-I", chain, "-j", globalChain); err != nil { return fmt.Errorf("failed to insert overlay hook in chain %s: %v", chain, err) } } } // Insert/Delete the rule to jump to per-bridge chain - exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname) + exists := iptable.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname) if (!remove && !exists) || (remove && exists) { - if err := iptables.RawCombinedOutput(opt, globalChain, "-o", brName, "-j", cname); err != nil { + if err := iptable.RawCombinedOutput(opt, globalChain, "-o", brName, "-j", cname); err != nil { return fmt.Errorf("failed to add per-bridge filter rule for bridge %s, network chain %s: %v", brName, cname, err) } } - exists = iptables.Exists(iptables.Filter, cname, "-i", brName, "-j", "ACCEPT") + exists = iptable.Exists(iptables.Filter, cname, "-i", brName, "-j", "ACCEPT") if (!remove && exists) || (remove && !exists) { return nil } - if err := iptables.RawCombinedOutput(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil { + if err := iptable.RawCombinedOutput(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil { return fmt.Errorf("failed to add overlay filter rile for network chain %s, bridge %s: %v", cname, brName, err) } diff --git a/libnetwork/firewall_linux.go b/libnetwork/firewall_linux.go index 26ee91346e..a657825dfa 100644 --- a/libnetwork/firewall_linux.go +++ b/libnetwork/firewall_linux.go @@ -26,18 +26,19 @@ func arrangeUserFilterRule() { if ctrl == nil || !ctrl.iptablesEnabled() { return } - _, err := iptables.NewChain(userChain, iptables.Filter, false) + iptable := iptables.GetIptable(iptables.IPv4) + _, err := iptable.NewChain(userChain, iptables.Filter, false) if err != nil { logrus.Warnf("Failed to create %s chain: %v", userChain, err) return } - if err = iptables.AddReturnRule(userChain); err != nil { + if err = iptable.AddReturnRule(userChain); err != nil { logrus.Warnf("Failed to add the RETURN rule for %s: %v", userChain, err) return } - err = iptables.EnsureJumpRule("FORWARD", userChain) + err = iptable.EnsureJumpRule("FORWARD", userChain) if err != nil { logrus.Warnf("Failed to ensure the jump rule for %s: %v", userChain, err) } diff --git a/libnetwork/firewall_test.go b/libnetwork/firewall_test.go index 65a12f5a48..bb3111d574 100644 --- a/libnetwork/firewall_test.go +++ b/libnetwork/firewall_test.go @@ -17,6 +17,8 @@ const ( ) func TestUserChain(t *testing.T) { + iptable := iptables.GetIptable(iptables.IPv4) + nc, err := New() assert.NilError(t, err) @@ -60,7 +62,7 @@ func TestUserChain(t *testing.T) { assert.DeepEqual(t, getRules(t, fwdChainName), []string{"-P FORWARD ACCEPT"}) if tc.insert { - _, err = iptables.Raw("-A", fwdChainName, "-j", "DROP") + _, err = iptable.Raw("-A", fwdChainName, "-j", "DROP") assert.NilError(t, err) } arrangeUserFilterRule() @@ -69,7 +71,7 @@ func TestUserChain(t *testing.T) { if tc.userChain != nil { assert.DeepEqual(t, getRules(t, usrChainName), tc.userChain) } else { - _, err := iptables.Raw("-S", usrChainName) + _, err := iptable.Raw("-S", usrChainName) assert.Assert(t, err != nil, "chain %v: created unexpectedly", usrChainName) } }) @@ -78,8 +80,10 @@ func TestUserChain(t *testing.T) { } func getRules(t *testing.T, chain string) []string { + iptable := iptables.GetIptable(iptables.IPv4) + t.Helper() - output, err := iptables.Raw("-S", chain) + output, err := iptable.Raw("-S", chain) assert.NilError(t, err, "chain %s: failed to get rules", chain) rules := strings.Split(string(output), "\n") @@ -90,8 +94,10 @@ func getRules(t *testing.T, chain string) []string { } func resetIptables(t *testing.T) { + iptable := iptables.GetIptable(iptables.IPv4) + t.Helper() - _, err := iptables.Raw("-F", fwdChainName) + _, err := iptable.Raw("-F", fwdChainName) assert.NilError(t, err) - _ = iptables.RemoveExistingChain(usrChainName, "") + _ = iptable.RemoveExistingChain(usrChainName, "") } diff --git a/libnetwork/iptables/firewalld_test.go b/libnetwork/iptables/firewalld_test.go index 6ce618a49a..1bfc58e09e 100644 --- a/libnetwork/iptables/firewalld_test.go +++ b/libnetwork/iptables/firewalld_test.go @@ -19,13 +19,14 @@ func TestReloaded(t *testing.T) { var err error var fwdChain *ChainInfo - fwdChain, err = NewChain("FWD", Filter, false) + iptable := GetIptable(IPv4) + fwdChain, err = iptable.NewChain("FWD", Filter, false) if err != nil { t.Fatal(err) } bridgeName := "lo" - err = ProgramChain(fwdChain, bridgeName, false, true) + err = iptable.ProgramChain(fwdChain, bridgeName, false, true) if err != nil { t.Fatal(err) } @@ -54,7 +55,7 @@ func TestReloaded(t *testing.T) { "--dport", strconv.Itoa(port), "-j", "ACCEPT"} - if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { + if !iptable.Exists(fwdChain.Table, fwdChain.Name, rule1...) { t.Fatal("rule1 does not exist") } @@ -64,7 +65,7 @@ func TestReloaded(t *testing.T) { reloaded() // make sure the rules have been recreated - if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { + if !iptable.Exists(fwdChain.Table, fwdChain.Name, rule1...) { t.Fatal("rule1 hasn't been recreated") } } @@ -76,12 +77,13 @@ func TestPassthrough(t *testing.T) { "--dport", "123", "-j", "ACCEPT"} + iptable := GetIptable(IPv4) if firewalldRunning { _, err := Passthrough(Iptables, append([]string{"-A"}, rule1...)...) if err != nil { t.Fatal(err) } - if !Exists(Filter, "INPUT", rule1...) { + if !iptable.Exists(Filter, "INPUT", rule1...) { t.Fatal("rule1 does not exist") } } diff --git a/libnetwork/iptables/iptables.go b/libnetwork/iptables/iptables.go index bd262eb86c..dc6e0c6ce3 100644 --- a/libnetwork/iptables/iptables.go +++ b/libnetwork/iptables/iptables.go @@ -23,6 +23,9 @@ type Policy string // Table refers to Nat, Filter or Mangle. type Table string +// IPVersion refers to IP version, v4 or v6 +type IPVersion string + const ( // Append appends the rule at the end of the chain. Append Action = "-A" @@ -40,10 +43,15 @@ const ( Drop Policy = "DROP" // Accept is the default iptables ACCEPT policy Accept Policy = "ACCEPT" + // IPv4 is version 4 + IPv4 IPVersion = "IPV4" + // IPv6 is version 6 + IPv6 IPVersion = "IPV6" ) var ( iptablesPath string + ip6tablesPath string supportsXlock = false supportsCOpt = false xLockWaitMsg = "Another app is currently holding the xtables lock" @@ -54,11 +62,17 @@ var ( initOnce sync.Once ) +// IPTable defines struct with IPVersion and few other members are added later +type IPTable struct { + Version IPVersion +} + // ChainInfo defines the iptables chain. type ChainInfo struct { Name string Table Table HairpinMode bool + IPTable IPTable } // ChainError is returned to represent errors during ip table operation. @@ -80,6 +94,11 @@ func probe() { if out, err := exec.Command(path, "--wait", "-t", "nat", "-L", "-n").CombinedOutput(); err != nil { logrus.Warnf("Running iptables --wait -t nat -L -n failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err) } + _, err = exec.LookPath("ip6tables") + if err != nil { + logrus.Warnf("Failed to find ip6tables: %v", err) + return + } } func initFirewalld() { @@ -94,6 +113,11 @@ func detectIptables() { return } iptablesPath = path + path, err = exec.LookPath("ip6tables") + if err != nil { + return + } + ip6tablesPath = path supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil mj, mn, mc, err := GetVersion() if err != nil { @@ -118,20 +142,26 @@ func initCheck() error { return nil } +// GetIptable returns an instance of IPTable with specified version +func GetIptable(version IPVersion) *IPTable { + return &IPTable{Version: version} +} + // NewChain adds a new chain to ip table. -func NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) { +func (iptable IPTable) NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) { c := &ChainInfo{ Name: name, Table: table, HairpinMode: hairpinMode, + IPTable: iptable, } if string(c.Table) == "" { c.Table = Filter } // Add chain if it doesn't exist - if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil { - if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil { + if _, err := iptable.Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil { + if output, err := iptable.Raw("-t", string(c.Table), "-N", c.Name); err != nil { return nil, err } else if len(output) != 0 { return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output) @@ -140,8 +170,16 @@ func NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) { return c, nil } +// LoopbackByVersion returns loopback address by version +func (iptable IPTable) LoopbackByVersion() string { + if iptable.Version == IPv6 { + return "::1/128" + } + return "127.0.0.0/8" +} + // ProgramChain is used to add rules to a chain -func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error { +func (iptable IPTable) ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error { if c.Name == "" { return errors.New("Could not program chain, missing chain name") } @@ -165,11 +203,11 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) err "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name} - if !Exists(Nat, "PREROUTING", preroute...) && enable { + if !iptable.Exists(Nat, "PREROUTING", preroute...) && enable { if err := c.Prerouting(Append, preroute...); err != nil { return fmt.Errorf("Failed to inject %s in PREROUTING chain: %s", c.Name, err) } - } else if Exists(Nat, "PREROUTING", preroute...) && !enable { + } else if iptable.Exists(Nat, "PREROUTING", preroute...) && !enable { if err := c.Prerouting(Delete, preroute...); err != nil { return fmt.Errorf("Failed to remove %s in PREROUTING chain: %s", c.Name, err) } @@ -179,13 +217,13 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) err "--dst-type", "LOCAL", "-j", c.Name} if !hairpinMode { - output = append(output, "!", "--dst", "127.0.0.0/8") + output = append(output, "!", "--dst", iptable.LoopbackByVersion()) } - if !Exists(Nat, "OUTPUT", output...) && enable { + if !iptable.Exists(Nat, "OUTPUT", output...) && enable { if err := c.Output(Append, output...); err != nil { return fmt.Errorf("Failed to inject %s in OUTPUT chain: %s", c.Name, err) } - } else if Exists(Nat, "OUTPUT", output...) && !enable { + } else if iptable.Exists(Nat, "OUTPUT", output...) && !enable { if err := c.Output(Delete, output...); err != nil { return fmt.Errorf("Failed to inject %s in OUTPUT chain: %s", c.Name, err) } @@ -198,16 +236,16 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) err link := []string{ "-o", bridgeName, "-j", c.Name} - if !Exists(Filter, "FORWARD", link...) && enable { + if !iptable.Exists(Filter, "FORWARD", link...) && enable { insert := append([]string{string(Insert), "FORWARD"}, link...) - if output, err := Raw(insert...); err != nil { + if output, err := iptable.Raw(insert...); err != nil { return err } else if len(output) != 0 { return fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output) } - } else if Exists(Filter, "FORWARD", link...) && !enable { + } else if iptable.Exists(Filter, "FORWARD", link...) && !enable { del := append([]string{string(Delete), "FORWARD"}, link...) - if output, err := Raw(del...); err != nil { + if output, err := iptable.Raw(del...); err != nil { return err } else if len(output) != 0 { return fmt.Errorf("Could not delete linking rule from %s/%s: %s", c.Table, c.Name, output) @@ -219,16 +257,16 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) err "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"} - if !Exists(Filter, "FORWARD", establish...) && enable { + if !iptable.Exists(Filter, "FORWARD", establish...) && enable { insert := append([]string{string(Insert), "FORWARD"}, establish...) - if output, err := Raw(insert...); err != nil { + if output, err := iptable.Raw(insert...); err != nil { return err } else if len(output) != 0 { return fmt.Errorf("Could not create establish rule to %s: %s", c.Table, output) } - } else if Exists(Filter, "FORWARD", establish...) && !enable { + } else if iptable.Exists(Filter, "FORWARD", establish...) && !enable { del := append([]string{string(Delete), "FORWARD"}, establish...) - if output, err := Raw(del...); err != nil { + if output, err := iptable.Raw(del...); err != nil { return err } else if len(output) != 0 { return fmt.Errorf("Could not delete establish rule from %s: %s", c.Table, output) @@ -239,10 +277,11 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) err } // RemoveExistingChain removes existing chain from the table. -func RemoveExistingChain(name string, table Table) error { +func (iptable IPTable) RemoveExistingChain(name string, table Table) error { c := &ChainInfo{ - Name: name, - Table: table, + Name: name, + Table: table, + IPTable: iptable, } if string(c.Table) == "" { c.Table = Filter @@ -252,6 +291,8 @@ func RemoveExistingChain(name string, table Table) error { // Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table. func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int, bridgeName string) error { + + iptable := GetIptable(c.IPTable.Version) daddr := ip.String() if ip.IsUnspecified() { // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we @@ -266,10 +307,11 @@ func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr "--dport", strconv.Itoa(port), "-j", "DNAT", "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))} + if !c.HairpinMode { args = append(args, "!", "-i", bridgeName) } - if err := ProgramRule(Nat, c.Name, action, args); err != nil { + if err := iptable.ProgramRule(Nat, c.Name, action, args); err != nil { return err } @@ -281,7 +323,7 @@ func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr "--dport", strconv.Itoa(destPort), "-j", "ACCEPT", } - if err := ProgramRule(Filter, c.Name, action, args); err != nil { + if err := iptable.ProgramRule(Filter, c.Name, action, args); err != nil { return err } @@ -293,7 +335,7 @@ func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr "-j", "MASQUERADE", } - if err := ProgramRule(Nat, "POSTROUTING", action, args); err != nil { + if err := iptable.ProgramRule(Nat, "POSTROUTING", action, args); err != nil { return err } @@ -311,7 +353,7 @@ func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr "-j", "CHECKSUM", "--checksum-fill", } - if err := ProgramRule(Mangle, "POSTROUTING", action, args); err != nil { + if err := iptable.ProgramRule(Mangle, "POSTROUTING", action, args); err != nil { return err } } @@ -322,6 +364,7 @@ func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr // Link adds reciprocal ACCEPT rule for two supplied IP addresses. // Traffic is allowed from ip1 to ip2 and vice-versa func (c *ChainInfo) Link(action Action, ip1, ip2 net.IP, port int, proto string, bridgeName string) error { + iptable := GetIptable(c.IPTable.Version) // forward args := []string{ "-i", bridgeName, "-o", bridgeName, @@ -331,32 +374,34 @@ func (c *ChainInfo) Link(action Action, ip1, ip2 net.IP, port int, proto string, "--dport", strconv.Itoa(port), "-j", "ACCEPT", } - if err := ProgramRule(Filter, c.Name, action, args); err != nil { + + if err := iptable.ProgramRule(Filter, c.Name, action, args); err != nil { return err } // reverse args[7], args[9] = args[9], args[7] args[10] = "--sport" - return ProgramRule(Filter, c.Name, action, args) + return iptable.ProgramRule(Filter, c.Name, action, args) } // ProgramRule adds the rule specified by args only if the // rule is not already present in the chain. Reciprocally, // it removes the rule only if present. -func ProgramRule(table Table, chain string, action Action, args []string) error { - if Exists(table, chain, args...) != (action == Delete) { +func (iptable IPTable) ProgramRule(table Table, chain string, action Action, args []string) error { + if iptable.Exists(table, chain, args...) != (action == Delete) { return nil } - return RawCombinedOutput(append([]string{"-t", string(table), string(action), chain}, args...)...) + return iptable.RawCombinedOutput(append([]string{"-t", string(table), string(action), chain}, args...)...) } // Prerouting adds linking rule to nat/PREROUTING chain. func (c *ChainInfo) Prerouting(action Action, args ...string) error { + iptable := GetIptable(c.IPTable.Version) a := []string{"-t", string(Nat), string(action), "PREROUTING"} if len(args) > 0 { a = append(a, args...) } - if output, err := Raw(a...); err != nil { + if output, err := iptable.Raw(a...); err != nil { return err } else if len(output) != 0 { return ChainError{Chain: "PREROUTING", Output: output} @@ -366,11 +411,12 @@ func (c *ChainInfo) Prerouting(action Action, args ...string) error { // Output adds linking rule to an OUTPUT chain. func (c *ChainInfo) Output(action Action, args ...string) error { + iptable := GetIptable(c.IPTable.Version) a := []string{"-t", string(c.Table), string(action), "OUTPUT"} if len(args) > 0 { a = append(a, args...) } - if output, err := Raw(a...); err != nil { + if output, err := iptable.Raw(a...); err != nil { return err } else if len(output) != 0 { return ChainError{Chain: "OUTPUT", Output: output} @@ -380,35 +426,36 @@ func (c *ChainInfo) Output(action Action, args ...string) error { // Remove removes the chain. func (c *ChainInfo) Remove() error { + iptable := GetIptable(c.IPTable.Version) // Ignore errors - This could mean the chains were never set up if c.Table == Nat { c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) - c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", c.Name) + c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", iptable.LoopbackByVersion(), "-j", c.Name) c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) // Created in versions <= 0.1.6 c.Prerouting(Delete) c.Output(Delete) } - Raw("-t", string(c.Table), "-F", c.Name) - Raw("-t", string(c.Table), "-X", c.Name) + iptable.Raw("-t", string(c.Table), "-F", c.Name) + iptable.Raw("-t", string(c.Table), "-X", c.Name) return nil } // Exists checks if a rule exists -func Exists(table Table, chain string, rule ...string) bool { - return exists(false, table, chain, rule...) +func (iptable IPTable) Exists(table Table, chain string, rule ...string) bool { + return iptable.exists(false, table, chain, rule...) } // ExistsNative behaves as Exists with the difference it // will always invoke `iptables` binary. -func ExistsNative(table Table, chain string, rule ...string) bool { - return exists(true, table, chain, rule...) +func (iptable IPTable) ExistsNative(table Table, chain string, rule ...string) bool { + return iptable.exists(true, table, chain, rule...) } -func exists(native bool, table Table, chain string, rule ...string) bool { - f := Raw +func (iptable IPTable) exists(native bool, table Table, chain string, rule ...string) bool { + f := iptable.Raw if native { - f = raw + f = iptable.raw } if string(table) == "" { @@ -429,12 +476,16 @@ func exists(native bool, table Table, chain string, rule ...string) bool { // parse "iptables -S" for the rule (it checks rules in a specific chain // in a specific table and it is very unreliable) - return existsRaw(table, chain, rule...) + return iptable.existsRaw(table, chain, rule...) } -func existsRaw(table Table, chain string, rule ...string) bool { +func (iptable IPTable) existsRaw(table Table, chain string, rule ...string) bool { + path := iptablesPath + if iptable.Version == IPv6 { + path = ip6tablesPath + } ruleString := fmt.Sprintf("%s %s\n", chain, strings.Join(rule, " ")) - existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output() + existingRules, _ := exec.Command(path, "-t", string(table), "-S", chain).Output() return strings.Contains(string(existingRules), ruleString) } @@ -459,7 +510,7 @@ func filterOutput(start time.Time, output []byte, args ...string) []byte { } // Raw calls 'iptables' system command, passing supplied arguments. -func Raw(args ...string) ([]byte, error) { +func (iptable IPTable) Raw(args ...string) ([]byte, error) { if firewalldRunning { startTime := time.Now() output, err := Passthrough(Iptables, args...) @@ -467,10 +518,10 @@ func Raw(args ...string) ([]byte, error) { return filterOutput(startTime, output, args...), err } } - return raw(args...) + return iptable.raw(args...) } -func raw(args ...string) ([]byte, error) { +func (iptable IPTable) raw(args ...string) ([]byte, error) { if err := initCheck(); err != nil { return nil, err } @@ -481,10 +532,15 @@ func raw(args ...string) ([]byte, error) { defer bestEffortLock.Unlock() } - logrus.Debugf("%s, %v", iptablesPath, args) + path := iptablesPath + if iptable.Version == IPv6 { + path = ip6tablesPath + } + + logrus.Debugf("%s, %v", path, args) startTime := time.Now() - output, err := exec.Command(iptablesPath, args...).CombinedOutput() + output, err := exec.Command(path, args...).CombinedOutput() if err != nil { return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) } @@ -494,8 +550,8 @@ func raw(args ...string) ([]byte, error) { // RawCombinedOutput internally calls the Raw function and returns a non nil // error if Raw returned a non nil error or a non empty output -func RawCombinedOutput(args ...string) error { - if output, err := Raw(args...); err != nil || len(output) != 0 { +func (iptable IPTable) RawCombinedOutput(args ...string) error { + if output, err := iptable.Raw(args...); err != nil || len(output) != 0 { return fmt.Errorf("%s (%v)", string(output), err) } return nil @@ -503,16 +559,16 @@ func RawCombinedOutput(args ...string) error { // RawCombinedOutputNative behave as RawCombinedOutput with the difference it // will always invoke `iptables` binary -func RawCombinedOutputNative(args ...string) error { - if output, err := raw(args...); err != nil || len(output) != 0 { +func (iptable IPTable) RawCombinedOutputNative(args ...string) error { + if output, err := iptable.raw(args...); err != nil || len(output) != 0 { return fmt.Errorf("%s (%v)", string(output), err) } return nil } // ExistChain checks if a chain exists -func ExistChain(chain string, table Table) bool { - if _, err := Raw("-t", string(table), "-nL", chain); err == nil { +func (iptable IPTable) ExistChain(chain string, table Table) bool { + if _, err := iptable.Raw("-t", string(table), "-nL", chain); err == nil { return true } return false @@ -528,8 +584,8 @@ func GetVersion() (major, minor, micro int, err error) { } // SetDefaultPolicy sets the passed default policy for the table/chain -func SetDefaultPolicy(table Table, chain string, policy Policy) error { - if err := RawCombinedOutput("-t", string(table), "-P", chain, string(policy)); err != nil { +func (iptable IPTable) SetDefaultPolicy(table Table, chain string, policy Policy) error { + if err := iptable.RawCombinedOutput("-t", string(table), "-P", chain, string(policy)); err != nil { return fmt.Errorf("setting default policy to %v in %v chain failed: %v", policy, chain, err) } return nil @@ -549,17 +605,17 @@ func supportsCOption(mj, mn, mc int) bool { } // AddReturnRule adds a return rule for the chain in the filter table -func AddReturnRule(chain string) error { +func (iptable IPTable) AddReturnRule(chain string) error { var ( table = Filter args = []string{"-j", "RETURN"} ) - if Exists(table, chain, args...) { + if iptable.Exists(table, chain, args...) { return nil } - err := RawCombinedOutput(append([]string{"-A", chain}, args...)...) + err := iptable.RawCombinedOutput(append([]string{"-A", chain}, args...)...) if err != nil { return fmt.Errorf("unable to add return rule in %s chain: %s", chain, err.Error()) } @@ -568,20 +624,20 @@ func AddReturnRule(chain string) error { } // EnsureJumpRule ensures the jump rule is on top -func EnsureJumpRule(fromChain, toChain string) error { +func (iptable IPTable) EnsureJumpRule(fromChain, toChain string) error { var ( table = Filter args = []string{"-j", toChain} ) - if Exists(table, fromChain, args...) { - err := RawCombinedOutput(append([]string{"-D", fromChain}, args...)...) + if iptable.Exists(table, fromChain, args...) { + err := iptable.RawCombinedOutput(append([]string{"-D", fromChain}, args...)...) if err != nil { return fmt.Errorf("unable to remove jump to %s rule in %s chain: %s", toChain, fromChain, err.Error()) } } - err := RawCombinedOutput(append([]string{"-I", fromChain}, args...)...) + err := iptable.RawCombinedOutput(append([]string{"-I", fromChain}, args...)...) if err != nil { return fmt.Errorf("unable to insert jump to %s rule in %s chain: %s", toChain, fromChain, err.Error()) } diff --git a/libnetwork/iptables/iptables_test.go b/libnetwork/iptables/iptables_test.go index 0664a71afe..344d36d13d 100644 --- a/libnetwork/iptables/iptables_test.go +++ b/libnetwork/iptables/iptables_test.go @@ -21,20 +21,22 @@ func TestNewChain(t *testing.T) { var err error bridgeName = "lo" - natChain, err = NewChain(chainName, Nat, false) + iptable := GetIptable(IPv4) + + natChain, err = iptable.NewChain(chainName, Nat, false) if err != nil { t.Fatal(err) } - err = ProgramChain(natChain, bridgeName, false, true) + err = iptable.ProgramChain(natChain, bridgeName, false, true) if err != nil { t.Fatal(err) } - filterChain, err = NewChain(chainName, Filter, false) + filterChain, err = iptable.NewChain(chainName, Filter, false) if err != nil { t.Fatal(err) } - err = ProgramChain(filterChain, bridgeName, false, true) + err = iptable.ProgramChain(filterChain, bridgeName, false, true) if err != nil { t.Fatal(err) } @@ -48,6 +50,8 @@ func TestForward(t *testing.T) { proto := "tcp" bridgeName := "lo" + iptable := GetIptable(IPv4) + err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort, bridgeName) if err != nil { t.Fatal(err) @@ -62,7 +66,7 @@ func TestForward(t *testing.T) { "!", "-i", bridgeName, } - if !Exists(natChain.Table, natChain.Name, dnatRule...) { + if !iptable.Exists(natChain.Table, natChain.Name, dnatRule...) { t.Fatal("DNAT rule does not exist") } @@ -75,7 +79,7 @@ func TestForward(t *testing.T) { "-j", "ACCEPT", } - if !Exists(filterChain.Table, filterChain.Name, filterRule...) { + if !iptable.Exists(filterChain.Table, filterChain.Name, filterRule...) { t.Fatal("filter rule does not exist") } @@ -87,7 +91,7 @@ func TestForward(t *testing.T) { "-j", "MASQUERADE", } - if !Exists(natChain.Table, "POSTROUTING", masqRule...) { + if !iptable.Exists(natChain.Table, "POSTROUTING", masqRule...) { t.Fatal("MASQUERADE rule does not exist") } } @@ -96,6 +100,7 @@ func TestLink(t *testing.T) { var err error bridgeName := "lo" + iptable := GetIptable(IPv4) ip1 := net.ParseIP("192.168.1.1") ip2 := net.ParseIP("192.168.1.2") port := 1234 @@ -115,7 +120,7 @@ func TestLink(t *testing.T) { "--dport", strconv.Itoa(port), "-j", "ACCEPT"} - if !Exists(filterChain.Table, filterChain.Name, rule1...) { + if !iptable.Exists(filterChain.Table, filterChain.Name, rule1...) { t.Fatal("rule1 does not exist") } @@ -128,7 +133,7 @@ func TestLink(t *testing.T) { "--sport", strconv.Itoa(port), "-j", "ACCEPT"} - if !Exists(filterChain.Table, filterChain.Name, rule2...) { + if !iptable.Exists(filterChain.Table, filterChain.Name, rule2...) { t.Fatal("rule2 does not exist") } } @@ -137,18 +142,19 @@ func TestPrerouting(t *testing.T) { args := []string{ "-i", "lo", "-d", "192.168.1.1"} + iptable := GetIptable(IPv4) err := natChain.Prerouting(Insert, args...) if err != nil { t.Fatal(err) } - if !Exists(natChain.Table, "PREROUTING", args...) { + if !iptable.Exists(natChain.Table, "PREROUTING", args...) { t.Fatal("rule does not exist") } delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, args...) - if _, err = Raw(delRule...); err != nil { + if _, err = iptable.Raw(delRule...); err != nil { t.Fatal(err) } } @@ -157,19 +163,20 @@ func TestOutput(t *testing.T) { args := []string{ "-o", "lo", "-d", "192.168.1.1"} + iptable := GetIptable(IPv4) err := natChain.Output(Insert, args...) if err != nil { t.Fatal(err) } - if !Exists(natChain.Table, "OUTPUT", args...) { + if !iptable.Exists(natChain.Table, "OUTPUT", args...) { t.Fatal("rule does not exist") } delRule := append([]string{"-D", "OUTPUT", "-t", string(natChain.Table)}, args...) - if _, err = Raw(delRule...); err != nil { + if _, err = iptable.Raw(delRule...); err != nil { t.Fatal(err) } } @@ -222,12 +229,14 @@ func TestCleanup(t *testing.T) { string(Delete), "FORWARD", "-o", bridgeName, "-j", filterChain.Name} - if _, err = Raw(link...); err != nil { + iptable := GetIptable(IPv4) + + if _, err = iptable.Raw(link...); err != nil { t.Fatal(err) } filterChain.Remove() - err = RemoveExistingChain(chainName, Nat) + err = iptable.RemoveExistingChain(chainName, Nat) if err != nil { t.Fatal(err) } @@ -245,20 +254,22 @@ func TestExistsRaw(t *testing.T) { testChain1 := "ABCD" testChain2 := "EFGH" - _, err := NewChain(testChain1, Filter, false) + iptable := GetIptable(IPv4) + + _, err := iptable.NewChain(testChain1, Filter, false) if err != nil { t.Fatal(err) } defer func() { - RemoveExistingChain(testChain1, Filter) + iptable.RemoveExistingChain(testChain1, Filter) }() - _, err = NewChain(testChain2, Filter, false) + _, err = iptable.NewChain(testChain2, Filter, false) if err != nil { t.Fatal(err) } defer func() { - RemoveExistingChain(testChain2, Filter) + iptable.RemoveExistingChain(testChain2, Filter) }() // Test detection over full and truncated rule string @@ -271,18 +282,18 @@ func TestExistsRaw(t *testing.T) { for i, r := range input { ruleAdd := append([]string{"-t", string(Filter), "-A", testChain1}, r.rule...) - err = RawCombinedOutput(ruleAdd...) + err = iptable.RawCombinedOutput(ruleAdd...) if err != nil { t.Fatalf("i=%d, err: %v", i, err) } - if !existsRaw(Filter, testChain1, r.rule...) { + if !iptable.existsRaw(Filter, testChain1, r.rule...) { t.Fatalf("Failed to detect rule. i=%d", i) } // Truncate the rule trg := r.rule[len(r.rule)-1] trg = trg[:len(trg)-2] r.rule[len(r.rule)-1] = trg - if existsRaw(Filter, testChain1, r.rule...) { + if iptable.existsRaw(Filter, testChain1, r.rule...) { t.Fatalf("Invalid detection. i=%d", i) } } diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index ace38e89a2..3e1a2524ae 100644 --- a/libnetwork/libnetwork_test.go +++ b/libnetwork/libnetwork_test.go @@ -195,7 +195,7 @@ func TestBridge(t *testing.T) { if !ok { t.Fatalf("Unexpected format for port mapping in endpoint operational data") } - if len(pm) != 5 { + if len(pm) != 10 { t.Fatalf("Incomplete data for port mapping in endpoint operational data: %d", len(pm)) } } diff --git a/libnetwork/portmapper/mapper.go b/libnetwork/portmapper/mapper.go index 47c99a9e34..abb7ea1700 100644 --- a/libnetwork/portmapper/mapper.go +++ b/libnetwork/portmapper/mapper.go @@ -151,7 +151,7 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, } containerIP, containerPort := getIPAndPort(m.container) - if hostIP.To4() != nil { + if hostIP.To4() != nil || hostIP.To16() != nil { if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { return nil, err } @@ -170,11 +170,13 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, return nil } - if err := m.userlandProxy.Start(); err != nil { - if err := cleanup(); err != nil { - return nil, fmt.Errorf("Error during port allocation cleanup: %v", err) + if hostIP.To4() != nil { + if err := m.userlandProxy.Start(); err != nil { + if err := cleanup(); err != nil { + return nil, fmt.Errorf("Error during port allocation cleanup: %v", err) + } + return nil, err } - return nil, err } pm.currentMappings[key] = m diff --git a/libnetwork/resolver_unix.go b/libnetwork/resolver_unix.go index f4e4ad6184..ce65c09558 100644 --- a/libnetwork/resolver_unix.go +++ b/libnetwork/resolver_unix.go @@ -57,25 +57,27 @@ func reexecSetupResolver() { os.Exit(3) } + iptable := iptables.GetIptable(iptables.IPv4) + // insert outputChain and postroutingchain - err = iptables.RawCombinedOutputNative("-t", "nat", "-C", "OUTPUT", "-d", resolverIP, "-j", outputChain) + err = iptable.RawCombinedOutputNative("-t", "nat", "-C", "OUTPUT", "-d", resolverIP, "-j", outputChain) if err == nil { - iptables.RawCombinedOutputNative("-t", "nat", "-F", outputChain) + iptable.RawCombinedOutputNative("-t", "nat", "-F", outputChain) } else { - iptables.RawCombinedOutputNative("-t", "nat", "-N", outputChain) - iptables.RawCombinedOutputNative("-t", "nat", "-I", "OUTPUT", "-d", resolverIP, "-j", outputChain) + iptable.RawCombinedOutputNative("-t", "nat", "-N", outputChain) + iptable.RawCombinedOutputNative("-t", "nat", "-I", "OUTPUT", "-d", resolverIP, "-j", outputChain) } - err = iptables.RawCombinedOutputNative("-t", "nat", "-C", "POSTROUTING", "-d", resolverIP, "-j", postroutingchain) + err = iptable.RawCombinedOutputNative("-t", "nat", "-C", "POSTROUTING", "-d", resolverIP, "-j", postroutingchain) if err == nil { - iptables.RawCombinedOutputNative("-t", "nat", "-F", postroutingchain) + iptable.RawCombinedOutputNative("-t", "nat", "-F", postroutingchain) } else { - iptables.RawCombinedOutputNative("-t", "nat", "-N", postroutingchain) - iptables.RawCombinedOutputNative("-t", "nat", "-I", "POSTROUTING", "-d", resolverIP, "-j", postroutingchain) + iptable.RawCombinedOutputNative("-t", "nat", "-N", postroutingchain) + iptable.RawCombinedOutputNative("-t", "nat", "-I", "POSTROUTING", "-d", resolverIP, "-j", postroutingchain) } for _, rule := range rules { - if iptables.RawCombinedOutputNative(rule...) != nil { + if iptable.RawCombinedOutputNative(rule...) != nil { logrus.Errorf("set up rule failed, %v", rule) } } diff --git a/libnetwork/service_linux.go b/libnetwork/service_linux.go index 02ea8ff6bc..bb94141c0c 100644 --- a/libnetwork/service_linux.go +++ b/libnetwork/service_linux.go @@ -302,6 +302,9 @@ func filterPortConfigs(ingressPorts []*PortConfig, isDelete bool) []*PortConfig } func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) error { + + iptable := iptables.GetIptable(iptables.IPv4) + addDelOpt := "-I" rollbackAddDelOpt := "-D" if isDelete { @@ -312,19 +315,19 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro ingressMu.Lock() defer ingressMu.Unlock() - chainExists := iptables.ExistChain(ingressChain, iptables.Nat) - filterChainExists := iptables.ExistChain(ingressChain, iptables.Filter) + chainExists := iptable.ExistChain(ingressChain, iptables.Nat) + filterChainExists := iptable.ExistChain(ingressChain, iptables.Filter) ingressOnce.Do(func() { // Flush nat table and filter table ingress chain rules during init if it // exists. It might contain stale rules from previous life. if chainExists { - if err := iptables.RawCombinedOutput("-t", "nat", "-F", ingressChain); err != nil { + if err := iptable.RawCombinedOutput("-t", "nat", "-F", ingressChain); err != nil { logrus.Errorf("Could not flush nat table ingress chain rules during init: %v", err) } } if filterChainExists { - if err := iptables.RawCombinedOutput("-F", ingressChain); err != nil { + if err := iptable.RawCombinedOutput("-F", ingressChain); err != nil { logrus.Errorf("Could not flush filter table ingress chain rules during init: %v", err) } } @@ -332,38 +335,38 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro if !isDelete { if !chainExists { - if err := iptables.RawCombinedOutput("-t", "nat", "-N", ingressChain); err != nil { + if err := iptable.RawCombinedOutput("-t", "nat", "-N", ingressChain); err != nil { return fmt.Errorf("failed to create ingress chain: %v", err) } } if !filterChainExists { - if err := iptables.RawCombinedOutput("-N", ingressChain); err != nil { + if err := iptable.RawCombinedOutput("-N", ingressChain); err != nil { return fmt.Errorf("failed to create filter table ingress chain: %v", err) } } - if !iptables.Exists(iptables.Nat, ingressChain, "-j", "RETURN") { - if err := iptables.RawCombinedOutput("-t", "nat", "-A", ingressChain, "-j", "RETURN"); err != nil { + if !iptable.Exists(iptables.Nat, ingressChain, "-j", "RETURN") { + if err := iptable.RawCombinedOutput("-t", "nat", "-A", ingressChain, "-j", "RETURN"); err != nil { return fmt.Errorf("failed to add return rule in nat table ingress chain: %v", err) } } - if !iptables.Exists(iptables.Filter, ingressChain, "-j", "RETURN") { - if err := iptables.RawCombinedOutput("-A", ingressChain, "-j", "RETURN"); err != nil { + if !iptable.Exists(iptables.Filter, ingressChain, "-j", "RETURN") { + if err := iptable.RawCombinedOutput("-A", ingressChain, "-j", "RETURN"); err != nil { return fmt.Errorf("failed to add return rule to filter table ingress chain: %v", err) } } for _, chain := range []string{"OUTPUT", "PREROUTING"} { - if !iptables.Exists(iptables.Nat, chain, "-m", "addrtype", "--dst-type", "LOCAL", "-j", ingressChain) { - if err := iptables.RawCombinedOutput("-t", "nat", "-I", chain, "-m", "addrtype", "--dst-type", "LOCAL", "-j", ingressChain); err != nil { + if !iptable.Exists(iptables.Nat, chain, "-m", "addrtype", "--dst-type", "LOCAL", "-j", ingressChain) { + if err := iptable.RawCombinedOutput("-t", "nat", "-I", chain, "-m", "addrtype", "--dst-type", "LOCAL", "-j", ingressChain); err != nil { return fmt.Errorf("failed to add jump rule in %s to ingress chain: %v", chain, err) } } } - if !iptables.Exists(iptables.Filter, "FORWARD", "-j", ingressChain) { - if err := iptables.RawCombinedOutput("-I", "FORWARD", "-j", ingressChain); err != nil { + if !iptable.Exists(iptables.Filter, "FORWARD", "-j", ingressChain) { + if err := iptable.RawCombinedOutput("-I", "FORWARD", "-j", ingressChain); err != nil { return fmt.Errorf("failed to add jump rule to %s in filter table forward chain: %v", ingressChain, err) } arrangeUserFilterRule() @@ -380,8 +383,8 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro } ruleArgs := strings.Fields(fmt.Sprintf("-m addrtype --src-type LOCAL -o %s -j MASQUERADE", oifName)) - if !iptables.Exists(iptables.Nat, "POSTROUTING", ruleArgs...) { - if err := iptables.RawCombinedOutput(append([]string{"-t", "nat", "-I", "POSTROUTING"}, ruleArgs...)...); err != nil { + if !iptable.Exists(iptables.Nat, "POSTROUTING", ruleArgs...) { + if err := iptable.RawCombinedOutput(append([]string{"-t", "nat", "-I", "POSTROUTING"}, ruleArgs...)...); err != nil { return fmt.Errorf("failed to add ingress localhost POSTROUTING rule for %s: %v", oifName, err) } } @@ -395,7 +398,7 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro if portErr != nil && !isDelete { filterPortConfigs(filteredPorts, !isDelete) for _, rule := range rollbackRules { - if err := iptables.RawCombinedOutput(rule...); err != nil { + if err := iptable.RawCombinedOutput(rule...); err != nil { logrus.Warnf("roll back rule failed, %v: %v", rule, err) } } @@ -403,10 +406,10 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro }() for _, iPort := range filteredPorts { - if iptables.ExistChain(ingressChain, iptables.Nat) { + if iptable.ExistChain(ingressChain, iptables.Nat) { rule := strings.Fields(fmt.Sprintf("-t nat %s %s -p %s --dport %d -j DNAT --to-destination %s:%d", addDelOpt, ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort, gwIP, iPort.PublishedPort)) - if portErr = iptables.RawCombinedOutput(rule...); portErr != nil { + if portErr = iptable.RawCombinedOutput(rule...); portErr != nil { errStr := fmt.Sprintf("set up rule failed, %v: %v", rule, portErr) if !isDelete { return fmt.Errorf("%s", errStr) @@ -423,7 +426,7 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro // 2) unmanaged containers on bridge networks rule := strings.Fields(fmt.Sprintf("%s %s -m state -p %s --sport %d --state ESTABLISHED,RELATED -j ACCEPT", addDelOpt, ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort)) - if portErr = iptables.RawCombinedOutput(rule...); portErr != nil { + if portErr = iptable.RawCombinedOutput(rule...); portErr != nil { errStr := fmt.Sprintf("set up rule failed, %v: %v", rule, portErr) if !isDelete { return fmt.Errorf("%s", errStr) @@ -436,7 +439,7 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro rule = strings.Fields(fmt.Sprintf("%s %s -p %s --dport %d -j ACCEPT", addDelOpt, ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort)) - if portErr = iptables.RawCombinedOutput(rule...); portErr != nil { + if portErr = iptable.RawCombinedOutput(rule...); portErr != nil { errStr := fmt.Sprintf("set up rule failed, %v: %v", rule, portErr) if !isDelete { return fmt.Errorf("%s", errStr) @@ -461,13 +464,14 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro // This chain has the rules to allow access to the published ports for swarm tasks // from local bridge networks and docker_gwbridge (ie:taks on other swarm networks) func arrangeIngressFilterRule() { - if iptables.ExistChain(ingressChain, iptables.Filter) { - if iptables.Exists(iptables.Filter, "FORWARD", "-j", ingressChain) { - if err := iptables.RawCombinedOutput("-D", "FORWARD", "-j", ingressChain); err != nil { + iptable := iptables.GetIptable(iptables.IPv4) + if iptable.ExistChain(ingressChain, iptables.Filter) { + if iptable.Exists(iptables.Filter, "FORWARD", "-j", ingressChain) { + if err := iptable.RawCombinedOutput("-D", "FORWARD", "-j", ingressChain); err != nil { logrus.Warnf("failed to delete jump rule to ingressChain in filter table: %v", err) } } - if err := iptables.RawCombinedOutput("-I", "FORWARD", "-j", ingressChain); err != nil { + if err := iptable.RawCombinedOutput("-I", "FORWARD", "-j", ingressChain); err != nil { logrus.Warnf("failed to add jump rule to ingressChain in filter table: %v", err) } } @@ -606,6 +610,7 @@ func invokeFWMarker(path string, vip net.IP, fwMark uint32, ingressPorts []*Port // Firewall marker reexec function. func fwMarker() { + iptable := iptables.GetIptable(iptables.IPv4) runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -660,7 +665,7 @@ func fwMarker() { } ruleParams := strings.Fields(fmt.Sprintf("-m ipvs --ipvs -d %s -j SNAT --to-source %s", subnet, eIP)) - if !iptables.Exists("nat", "POSTROUTING", ruleParams...) { + if !iptable.Exists("nat", "POSTROUTING", ruleParams...) { rule := append(strings.Fields("-t nat -A POSTROUTING"), ruleParams...) rules = append(rules, rule) @@ -676,7 +681,7 @@ func fwMarker() { rules = append(rules, rule) for _, rule := range rules { - if err := iptables.RawCombinedOutputNative(rule...); err != nil { + if err := iptable.RawCombinedOutputNative(rule...); err != nil { logrus.Errorf("set up rule failed, %v: %v", rule, err) os.Exit(8) } @@ -711,6 +716,7 @@ func addRedirectRules(path string, eIP *net.IPNet, ingressPorts []*PortConfig) e // Redirector reexec function. func redirector() { + iptable := iptables.GetIptable(iptables.IPv4) runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -763,7 +769,7 @@ func redirector() { } for _, rule := range rules { - if err := iptables.RawCombinedOutputNative(rule...); err != nil { + if err := iptable.RawCombinedOutputNative(rule...); err != nil { logrus.Errorf("set up rule failed, %v: %v", rule, err) os.Exit(6) } @@ -779,15 +785,15 @@ func redirector() { {"-d", eIP.String(), "-p", "udp", "-j", "DROP"}, {"-d", eIP.String(), "-p", "tcp", "-j", "DROP"}, } { - if !iptables.ExistsNative(iptables.Filter, "INPUT", rule...) { - if err := iptables.RawCombinedOutputNative(append([]string{"-A", "INPUT"}, rule...)...); err != nil { + if !iptable.ExistsNative(iptables.Filter, "INPUT", rule...) { + if err := iptable.RawCombinedOutputNative(append([]string{"-A", "INPUT"}, rule...)...); err != nil { logrus.Errorf("set up rule failed, %v: %v", rule, err) os.Exit(7) } } rule[0] = "-s" - if !iptables.ExistsNative(iptables.Filter, "OUTPUT", rule...) { - if err := iptables.RawCombinedOutputNative(append([]string{"-A", "OUTPUT"}, rule...)...); err != nil { + if !iptable.ExistsNative(iptables.Filter, "OUTPUT", rule...) { + if err := iptable.RawCombinedOutputNative(append([]string{"-A", "OUTPUT"}, rule...)...); err != nil { logrus.Errorf("set up rule failed, %v: %v", rule, err) os.Exit(8) }