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

Inspect network info of a joined network namespace #13366

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 88 additions & 2 deletions libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/podman/v4/utils"
"github.com/containers/storage/pkg/lockfile"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -990,8 +991,20 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
return nil, err
}

// We can't do more if the network is down.
if c.state.NetNS == nil {
if networkNSPath := c.joinedNetworkNSPath(); networkNSPath != "" {
if result, err := c.inspectJoinedNetworkNS(networkNSPath); err == nil {
if basicConfig, err := resultToBasicNetworkConfig(result); err == nil {
// fallback to dummy configuration
settings.InspectBasicNetworkConfig = basicConfig
return settings, nil
}
}
// do not propagate error inspecting a joined network ns
logrus.Errorf("Error inspecting network namespace: %s of container %s: %v", networkNSPath, c.ID(), err)
}
// We can't do more if the network is down.

// We still want to make dummy configurations for each CNI net
// the container joined.
if len(networks) > 0 {
Expand Down Expand Up @@ -1065,11 +1078,84 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
return settings, nil
}

func (c *Container) joinedNetworkNSPath() string {
for _, namespace := range c.config.Spec.Linux.Namespaces {
if namespace.Type == spec.NetworkNamespace {
return namespace.Path
}
}
return ""
}

func (c *Container) inspectJoinedNetworkNS(networkns string) (q types.StatusBlock, retErr error) {
var result types.StatusBlock
err := ns.WithNetNSPath(networkns, func(_ ns.NetNS) error {
ifaces, err := net.Interfaces()
if err != nil {
return err
}
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
if err != nil {
return err
}
var gateway net.IP
for _, route := range routes {
// default gateway
if route.Dst == nil {
gateway = route.Gw
}
}
result.Interfaces = make(map[string]types.NetInterface)
for _, iface := range ifaces {
if iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
if len(addrs) == 0 {
continue
}
subnets := make([]types.NetAddress, 0, len(addrs))
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok {
if ipnet.IP.IsLinkLocalMulticast() || ipnet.IP.IsLinkLocalUnicast() {
continue
}
subnet := types.NetAddress{
IPNet: types.IPNet{
IPNet: *ipnet,
},
}
if ipnet.Contains(gateway) {
subnet.Gateway = gateway
}
subnets = append(subnets, subnet)
}
}
result.Interfaces[iface.Name] = types.NetInterface{
Subnets: subnets,
MacAddress: types.HardwareAddr(iface.HardwareAddr),
}
}
return nil
})
return result, err
}

// resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI
// result
func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) {
config := define.InspectBasicNetworkConfig{}
for _, netInt := range result.Interfaces {
interfaceNames := make([]string, len(result.Interfaces))
for interfaceName := range result.Interfaces {
interfaceNames = append(interfaceNames, interfaceName)
}
// ensure consistent inspect results by sorting
sort.Strings(interfaceNames)
for _, interfaceName := range interfaceNames {
netInt := result.Interfaces[interfaceName]
for _, netAddress := range netInt.Subnets {
size, _ := netAddress.IPNet.Mask.Size()
if netAddress.IPNet.IP.To4() != nil {
Expand Down
155 changes: 155 additions & 0 deletions test/e2e/run_networking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package integration

import (
"fmt"
"net"
"os"
"strings"
"syscall"

"github.com/containernetworking/plugins/pkg/ns"
. "github.com/containers/podman/v4/test/utils"
"github.com/containers/storage/pkg/stringid"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
"github.com/uber/jaeger-client-go/utils"
"github.com/vishvananda/netlink"
)

var _ = Describe("Podman run networking", func() {
Expand Down Expand Up @@ -694,6 +698,157 @@ EXPOSE 2004-2005/tcp`, ALPINE)
Expect(session.OutputToString()).To(ContainSubstring("11.11.11.11"))
})

addAddr := func(cidr string, containerInterface netlink.Link) error {
_, ipnet, err := net.ParseCIDR(cidr)
Expect(err).To(BeNil())
addr := &netlink.Addr{IPNet: ipnet, Label: ""}
if err := netlink.AddrAdd(containerInterface, addr); err != nil && err != syscall.EEXIST {
return err
}
return nil
}

loopbackup := func() {
lo, err := netlink.LinkByName("lo")
Expect(err).To(BeNil())
err = netlink.LinkSetUp(lo)
Expect(err).To(BeNil())
}

linkup := func(name string, mac string, addresses []string) {
linkAttr := netlink.NewLinkAttrs()
linkAttr.Name = name
m, err := net.ParseMAC(mac)
Expect(err).To(BeNil())
linkAttr.HardwareAddr = net.HardwareAddr(m)
eth := &netlink.Dummy{LinkAttrs: linkAttr}
err = netlink.LinkAdd(eth)
Expect(err).To(BeNil())
err = netlink.LinkSetUp(eth)
Expect(err).To(BeNil())
for _, address := range addresses {
err := addAddr(address, eth)
Expect(err).To(BeNil())
}
}

routeAdd := func(gateway string) {
gw := net.ParseIP(gateway)
route := &netlink.Route{Dst: nil, Gw: gw}
netlink.RouteAdd(route)
}

setupNetworkNs := func(networkNSName string) {
ns.WithNetNSPath("/run/netns/"+networkNSName, func(_ ns.NetNS) error {
loopbackup()
linkup("eth0", "46:7f:45:6e:4f:c8", []string{"10.25.40.0/24", "fd04:3e42:4a4e:3381::/64"})
linkup("eth1", "56:6e:35:5d:3e:a8", []string{"10.88.0.0/16"})

routeAdd("10.25.40.0")
return nil
})
}

checkNetworkNsInspect := func(name string) {
inspectOut := podmanTest.InspectContainer(name)
Expect(inspectOut[0].NetworkSettings.IPAddress).To(Equal("10.25.40.0"))
Expect(inspectOut[0].NetworkSettings.IPPrefixLen).To(Equal(24))
Expect(len(inspectOut[0].NetworkSettings.SecondaryIPAddresses)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.SecondaryIPAddresses[0].Addr).To(Equal("10.88.0.0"))
Expect(inspectOut[0].NetworkSettings.SecondaryIPAddresses[0].PrefixLength).To(Equal(16))
Expect(inspectOut[0].NetworkSettings.GlobalIPv6Address).To(Equal("fd04:3e42:4a4e:3381::"))
Expect(inspectOut[0].NetworkSettings.GlobalIPv6PrefixLen).To(Equal(64))
Expect(len(inspectOut[0].NetworkSettings.SecondaryIPv6Addresses)).To(Equal(0))
Expect(inspectOut[0].NetworkSettings.MacAddress).To(Equal("46:7f:45:6e:4f:c8"))
Expect(len(inspectOut[0].NetworkSettings.AdditionalMacAddresses)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.AdditionalMacAddresses[0]).To(Equal("56:6e:35:5d:3e:a8"))
Expect(inspectOut[0].NetworkSettings.Gateway).To(Equal("10.25.40.0"))

}

It("podman run newtork inspect fails gracefully on non-reachable network ns", func() {
SkipIfRootless("ip netns is not supported for rootless users")

networkNSName := RandomString(12)
addNamedNetwork := SystemExec("ip", []string{"netns", "add", networkNSName})
Expect(addNamedNetwork).Should(Exit(0))

setupNetworkNs(networkNSName)

name := RandomString(12)
session := podmanTest.Podman([]string{"run", "-d", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE, "top"})
session.WaitWithDefaultTimeout()

// delete the named network ns before inspect
delNetworkNamespace := SystemExec("ip", []string{"netns", "delete", networkNSName})
Expect(delNetworkNamespace).Should(Exit(0))

inspectOut := podmanTest.InspectContainer(name)
Expect(inspectOut[0].NetworkSettings.IPAddress).To(Equal(""))
Expect(len(inspectOut[0].NetworkSettings.Networks)).To(Equal(0))
})

It("podman inspect can handle joined network ns with multiple interfaces", func() {
SkipIfRootless("ip netns is not supported for rootless users")

networkNSName := RandomString(12)
addNamedNetwork := SystemExec("ip", []string{"netns", "add", networkNSName})
Expect(addNamedNetwork).Should(Exit(0))
defer func() {
delNetworkNamespace := SystemExec("ip", []string{"netns", "delete", networkNSName})
Expect(delNetworkNamespace).Should(Exit(0))
}()
setupNetworkNs(networkNSName)

name := RandomString(12)
session := podmanTest.Podman([]string{"run", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE})
session.WaitWithDefaultTimeout()

session = podmanTest.Podman([]string{"container", "rm", name})
session.WaitWithDefaultTimeout()

// no network teardown should touch joined network ns interfaces
session = podmanTest.Podman([]string{"run", "-d", "--replace", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE, "top"})
session.WaitWithDefaultTimeout()

checkNetworkNsInspect(name)
})

It("podman do not tamper with joined network ns interfaces", func() {
SkipIfRootless("ip netns is not supported for rootless users")

networkNSName := RandomString(12)
addNamedNetwork := SystemExec("ip", []string{"netns", "add", networkNSName})
Expect(addNamedNetwork).Should(Exit(0))
defer func() {
delNetworkNamespace := SystemExec("ip", []string{"netns", "delete", networkNSName})
Expect(delNetworkNamespace).Should(Exit(0))
}()

setupNetworkNs(networkNSName)

name := RandomString(12)
session := podmanTest.Podman([]string{"run", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE})
session.WaitWithDefaultTimeout()

checkNetworkNsInspect(name)

name = RandomString(12)
session = podmanTest.Podman([]string{"run", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE})
session.WaitWithDefaultTimeout()

checkNetworkNsInspect(name)

// delete container, the network inspect should not change
session = podmanTest.Podman([]string{"container", "rm", name})
session.WaitWithDefaultTimeout()

session = podmanTest.Podman([]string{"run", "-d", "--replace", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE, "top"})
session.WaitWithDefaultTimeout()

checkNetworkNsInspect(name)
})

It("podman run network in bogus user created network namespace", func() {
session := podmanTest.Podman([]string{"run", "-dt", "--net", "ns:/run/netns/xxy", ALPINE, "wget", "www.podman.io"})
session.Wait(90)
Expand Down