From 07e3f1bba9674c0cb93a0fa260930bfebbf75728 Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Fri, 14 Aug 2020 11:19:02 +0200 Subject: [PATCH] podman support for IPv6 networks podman containers using IPv6 were missing the default route, breaking deployments trying to use them. The problem is that the default route was hardcoded to IPv4, this takes into consideration the podman subnet IP family to generate the corresponding default route. Signed-off-by: Antonio Ojea --- pkg/domain/infra/abi/network.go | 2 +- pkg/network/ip.go | 5 ++++ pkg/network/netconflist.go | 15 +++++++++--- pkg/network/netconflist_test.go | 38 ++++++++++++++++++++++++++++++ test/e2e/network_create_test.go | 41 +++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 pkg/network/netconflist_test.go diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index 0183a90d89..c06714cbb6 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -191,7 +191,7 @@ func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreate var plugins []network.CNIPlugins var routes []network.IPAMRoute - defaultRoute, err := network.NewIPAMDefaultRoute() + defaultRoute, err := network.NewIPAMDefaultRoute(network.IsIPv6(subnet.IP)) if err != nil { return "", err } diff --git a/pkg/network/ip.go b/pkg/network/ip.go index 1798cd939d..ba93a0d059 100644 --- a/pkg/network/ip.go +++ b/pkg/network/ip.go @@ -12,3 +12,8 @@ func CalcGatewayIP(ipn *net.IPNet) net.IP { nid := ipn.IP.Mask(ipn.Mask) return ip.NextIP(nid) } + +// IsIPv6 returns if netIP is IPv6. +func IsIPv6(netIP net.IP) bool { + return netIP != nil && netIP.To4() == nil +} diff --git a/pkg/network/netconflist.go b/pkg/network/netconflist.go index 4271d3f548..8187fdb393 100644 --- a/pkg/network/netconflist.go +++ b/pkg/network/netconflist.go @@ -6,6 +6,11 @@ import ( "path/filepath" ) +const ( + defaultIPv4Route = "0.0.0.0/0" + defaultIPv6Route = "::/0" +) + // NcList describes a generic map type NcList map[string]interface{} @@ -86,9 +91,13 @@ func NewIPAMRoute(r *net.IPNet) IPAMRoute { //nolint:interfacer } // NewIPAMDefaultRoute creates a new IPAMDefault route of -// 0.0.0.0/0 -func NewIPAMDefaultRoute() (IPAMRoute, error) { - _, n, err := net.ParseCIDR("0.0.0.0/0") +// 0.0.0.0/0 for IPv4 or ::/0 for IPv6 +func NewIPAMDefaultRoute(isIPv6 bool) (IPAMRoute, error) { + route := defaultIPv4Route + if isIPv6 { + route = defaultIPv6Route + } + _, n, err := net.ParseCIDR(route) if err != nil { return IPAMRoute{}, err } diff --git a/pkg/network/netconflist_test.go b/pkg/network/netconflist_test.go new file mode 100644 index 0000000000..a82a0140a3 --- /dev/null +++ b/pkg/network/netconflist_test.go @@ -0,0 +1,38 @@ +package network + +import ( + "reflect" + "testing" +) + +func TestNewIPAMDefaultRoute(t *testing.T) { + + tests := []struct { + name string + isIPv6 bool + want IPAMRoute + }{ + { + name: "IPv4 default route", + isIPv6: false, + want: IPAMRoute{defaultIPv4Route}, + }, + { + name: "IPv6 default route", + isIPv6: true, + want: IPAMRoute{defaultIPv6Route}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got, err := NewIPAMDefaultRoute(tt.isIPv6) + if err != nil { + t.Errorf("no errorr expected: %v", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewIPAMDefaultRoute() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index fcd324cd1e..f97e6c1f1f 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -178,6 +178,47 @@ var _ = Describe("Podman network create", func() { Expect(subnet.Contains(containerIP)).To(BeTrue()) }) + It("podman network create with name and IPv6 subnet", func() { + SkipIfRemote() + var ( + results []network.NcList + ) + nc := podmanTest.Podman([]string{"network", "create", "--subnet", "fd00:1:2:3:4::/64", "newIPv6network"}) + nc.WaitWithDefaultTimeout() + Expect(nc.ExitCode()).To(BeZero()) + + defer podmanTest.removeCNINetwork("newIPv6network") + + // Inspect the network configuration + inspect := podmanTest.Podman([]string{"network", "inspect", "newIPv6network"}) + inspect.WaitWithDefaultTimeout() + + // JSON the network configuration into something usable + err := json.Unmarshal([]byte(inspect.OutputToString()), &results) + Expect(err).To(BeNil()) + result := results[0] + Expect(result["name"]).To(Equal("newIPv6network")) + + // JSON the bridge info + bridgePlugin, err := genericPluginsToBridge(result["plugins"], "bridge") + Expect(err).To(BeNil()) + Expect(bridgePlugin.IPAM.Routes[0].Dest).To(Equal("::/0")) + + // Once a container executes a new network, the nic will be created. We should clean those up + // best we can + defer removeNetworkDevice(bridgePlugin.BrName) + + try := podmanTest.Podman([]string{"run", "-it", "--rm", "--network", "newIPv6network", ALPINE, "sh", "-c", "ip addr show eth0 | grep global | awk ' /inet6 / {print $2}'"}) + try.WaitWithDefaultTimeout() + + _, subnet, err := net.ParseCIDR("fd00:1:2:3:4::/64") + Expect(err).To(BeNil()) + containerIP, _, err := net.ParseCIDR(try.OutputToString()) + Expect(err).To(BeNil()) + // Ensure that the IP the container got is within the subnet the user asked for + Expect(subnet.Contains(containerIP)).To(BeTrue()) + }) + It("podman network create with invalid subnet", func() { nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/17000", "fail"}) nc.WaitWithDefaultTimeout()