Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable IPv6 NAT (rebase of #2023) #2572

Merged
merged 13 commits into from Oct 29, 2020
Merged
117 changes: 87 additions & 30 deletions drivers/bridge/bridge.go
Expand Up @@ -57,6 +57,7 @@ type iptablesCleanFuncs []iptableCleanFunc
type configuration struct {
EnableIPForwarding bool
EnableIPTables bool
EnableIP6Tables bool
EnableUserlandProxy bool
UserlandProxyPath string
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -277,14 +283,18 @@ 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()

if n.driver == nil {
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
}

Expand Down Expand Up @@ -323,17 +333,31 @@ 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)
if n.driver.config.EnableIP6Tables {
err := setINC(iptables.IPv6, thisConfig.BridgeName, enable)
if err != nil {
return err
}
}

arkodg marked this conversation as resolved.
Show resolved Hide resolved
if n.driver.config.EnableIPTables {
return setINC(iptables.IPv4, thisConfig.BridgeName, enable)
}
return nil
}

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]
Expand All @@ -354,23 +378,46 @@ func (d *driver) configure(option map[string]interface{}) error {
return &ErrInvalidDriverConfig{}
}

if config.EnableIPTables {
if config.EnableIPTables || config.EnableIP6Tables {
if _, err := os.Stat("/proc/sys/net/bridge"); err != nil {
if out, err := exec.Command("modprobe", "-va", "bridge", "br_netfilter").CombinedOutput(); err != nil {
logrus.Warnf("Running modprobe bridge br_netfilter failed with message: %s, error: %v", out, err)
}
}
removeIPChains()
natChain, filterChain, isolationChain1, isolationChain2, err = setupIPChains(config)
}

if config.EnableIPTables {
removeIPChains(iptables.IPv4)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we remove/setup either ipv4 or ipv6 chains in this function ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old patch was build in a way that it only enables the IPv6 handling if also the IPv4 handling is enabled.
For sure it is an option to enable it independent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resorted the EnableIP6Tables usage in driver.configure


natChain, filterChain, isolationChain1, isolationChain2, err = setupIPChains(config, iptables.IPv4)
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.IPv4)
})
}

if config.EnableIP6Tables {
removeIPChains(iptables.IPv6)

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 ip6tables chains on firewall reload")
setupIPChains(config, iptables.IPv6)
})
}

if config.EnableIPForwarding {
err = setupIPForwarding(config.EnableIPTables)
err = setupIPForwarding(config.EnableIPTables, config.EnableIP6Tables)
if err != nil {
logrus.Warn(err)
return err
Expand All @@ -382,6 +429,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()

Expand Down Expand Up @@ -644,12 +695,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()
Expand Down Expand Up @@ -724,11 +776,16 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) {
{!d.config.EnableUserlandProxy, setupLoopbackAddressesRouting},

// Setup IPTables.
{d.config.EnableIPTables, network.setupIPTables},
{d.config.EnableIPTables, network.setupIP4Tables},

// Setup IP6Tables.
{d.config.EnableIP6Tables, network.setupIP6Tables},

//We want to track firewalld configuration so that
//if it is started/reloaded, the rules can be applied correctly
{d.config.EnableIPTables, network.setupFirewalld},
// same for IPv6
{d.config.EnableIP6Tables, network.setupFirewalld6},

// Setup DefaultGatewayIPv4
{config.DefaultGatewayIPv4 != nil, setupGatewayIPv4},
Expand Down
41 changes: 25 additions & 16 deletions drivers/bridge/bridge_test.go
Expand Up @@ -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)
}
Expand Down Expand Up @@ -715,6 +716,7 @@ func TestLinkContainers(t *testing.T) {
}

d := newDriver()
iptable := iptables.GetIptable(iptables.IPv4)

config := &configuration{
EnableIPTables: true,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
}
}
}
Expand Down
40 changes: 36 additions & 4 deletions drivers/bridge/port_mapping.go
Expand Up @@ -12,7 +12,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) {
Expand All @@ -25,7 +26,25 @@ func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, u
defHostIP = reqDefBindIP
}

return n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
// IPv4 port binding including user land proxy
pb, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
if err != nil {
return nil, err
}

// IPv6 port binding excluding user land proxy
if n.driver.config.EnableIP6Tables && ep.addrv6 != nil {
// TODO IPv6 custom default binding IP
pbv6, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addrv6.IP, defaultBindingIPV6, false)
Copy link
Contributor

@arkodg arkodg Jul 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another TODO is to have a defaultBindingIPv6 similar to what IPv4 has, that can be specified by user via driver opts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added TODO

if err != nil {
// ensure we clear the previous allocated IPv4 ports
n.releasePortsInternal(pb)
return nil, err
}

pb = append(pb, pbv6...)
}
return pb, nil
}

func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
Expand Down Expand Up @@ -69,9 +88,15 @@ func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHos
return err
}

portmapper := n.portMapper

if containerIP.To4() == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hoping we can be consistent with the way we decipher ipv6 and ipv4 throughout the codebase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all checks should now check against net.IP.To4() so it should now be more consistent

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.
Expand Down Expand Up @@ -128,5 +153,12 @@ func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error {
if err != nil {
return err
}
return n.portMapper.Unmap(host)

portmapper := n.portMapper

if bnd.HostIP.To4() == nil {
portmapper = n.portMapperV6
}

return portmapper.Unmap(host)
}
17 changes: 16 additions & 1 deletion drivers/bridge/setup_firewalld.go
Expand Up @@ -13,8 +13,23 @@ func (n *bridgeNetwork) setupFirewalld(config *networkConfiguration, i *bridgeIn
return IPTableCfgError(config.BridgeName)
}

iptables.OnReloaded(func() { n.setupIPTables(config, i) })
iptables.OnReloaded(func() { n.setupIP4Tables(config, i) })
iptables.OnReloaded(n.portMapper.ReMapAll)
return nil
}

func (n *bridgeNetwork) setupFirewalld6(config *networkConfiguration, i *bridgeInterface) error {
d := n.driver
d.Lock()
driverConfig := d.config
d.Unlock()

// Sanity check.
if !driverConfig.EnableIP6Tables {
return IPTableCfgError(config.BridgeName)
}

iptables.OnReloaded(func() { n.setupIP6Tables(config, i) })
iptables.OnReloaded(n.portMapperV6.ReMapAll)
return nil
}