mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00
Inspect network info of a joined network namespace
Closes: https://github.com/containers/podman/issues/13150 Signed-off-by: 😎 Mostafa Emami <mustafaemami@gmail.com>
This commit is contained in:
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/containers/podman/v4/pkg/util"
|
"github.com/containers/podman/v4/pkg/util"
|
||||||
"github.com/containers/podman/v4/utils"
|
"github.com/containers/podman/v4/utils"
|
||||||
"github.com/containers/storage/pkg/lockfile"
|
"github.com/containers/storage/pkg/lockfile"
|
||||||
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -990,8 +991,20 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't do more if the network is down.
|
|
||||||
if c.state.NetNS == nil {
|
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
|
// We still want to make dummy configurations for each CNI net
|
||||||
// the container joined.
|
// the container joined.
|
||||||
if len(networks) > 0 {
|
if len(networks) > 0 {
|
||||||
@ -1065,11 +1078,84 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
|
|||||||
return settings, nil
|
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
|
// resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI
|
||||||
// result
|
// result
|
||||||
func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) {
|
func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) {
|
||||||
config := define.InspectBasicNetworkConfig{}
|
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 {
|
for _, netAddress := range netInt.Subnets {
|
||||||
size, _ := netAddress.IPNet.Mask.Size()
|
size, _ := netAddress.IPNet.Mask.Size()
|
||||||
if netAddress.IPNet.IP.To4() != nil {
|
if netAddress.IPNet.IP.To4() != nil {
|
||||||
|
@ -2,15 +2,19 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
. "github.com/containers/podman/v4/test/utils"
|
. "github.com/containers/podman/v4/test/utils"
|
||||||
"github.com/containers/storage/pkg/stringid"
|
"github.com/containers/storage/pkg/stringid"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
. "github.com/onsi/gomega/gexec"
|
. "github.com/onsi/gomega/gexec"
|
||||||
"github.com/uber/jaeger-client-go/utils"
|
"github.com/uber/jaeger-client-go/utils"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Podman run networking", func() {
|
var _ = Describe("Podman run networking", func() {
|
||||||
@ -694,6 +698,157 @@ EXPOSE 2004-2005/tcp`, ALPINE)
|
|||||||
Expect(session.OutputToString()).To(ContainSubstring("11.11.11.11"))
|
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() {
|
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 := podmanTest.Podman([]string{"run", "-dt", "--net", "ns:/run/netns/xxy", ALPINE, "wget", "www.podman.io"})
|
||||||
session.Wait(90)
|
session.Wait(90)
|
||||||
|
Reference in New Issue
Block a user