Skip to content

Commit

Permalink
Inspect network info of a joined network namespace
Browse files Browse the repository at this point in the history
Closes: containers#13150
Signed-off-by: 馃槑 Mostafa Emami <mustafaemami@gmail.com>
  • Loading branch information
idleroamer committed Feb 27, 2022
1 parent f1d510b commit 463ede9
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 4 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ require (
github.com/uber/jaeger-client-go v2.29.1+incompatible
github.com/vbauerster/mpb/v6 v6.0.4
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
Expand Down
5 changes: 3 additions & 2 deletions libpod/define/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/containers/image/v5/manifest"
"github.com/docker/docker/api/types/network"
)

// InspectContainerConfig holds further data about how a container was initially
Expand Down Expand Up @@ -551,7 +552,7 @@ type InspectBasicNetworkConfig struct {
IPPrefixLen int `json:"IPPrefixLen"`
// SecondaryIPAddresses is a list of extra IP Addresses that the
// container has been assigned in this network.
SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty"`
SecondaryIPAddresses []network.Address `json:"SecondaryIPAddresses,omitempty"`
// IPv6Gateway is the IPv6 gateway this network will use.
IPv6Gateway string `json:"IPv6Gateway"`
// GlobalIPv6Address is the global-scope IPv6 Address for this network.
Expand All @@ -560,7 +561,7 @@ type InspectBasicNetworkConfig struct {
GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen"`
// SecondaryIPv6Addresses is a list of extra IPv6 Addresses that the
// container has been assigned in this network.
SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty"`
SecondaryIPv6Addresses []network.Address `json:"SecondaryIPv6Addresses,omitempty"`
// MacAddress is the MAC address for the interface in this network.
MacAddress string `json:"MacAddress"`
// AdditionalMacAddresses is a set of additional MAC Addresses beyond
Expand Down
102 changes: 100 additions & 2 deletions libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"syscall"
"time"

Expand All @@ -30,6 +31,8 @@ import (
"github.com/containers/podman/v3/pkg/util"
"github.com/containers/storage/pkg/lockfile"
"github.com/cri-o/ocicni/pkg/ocicni"
dockernetworktype "github.com/docker/docker/api/types/network"
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 @@ -999,6 +1002,78 @@ func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) {
return netStats, err
}

func (c *Container) inspectJoinedNetworkNS(networkns string) (q cnitypes.Result, retErr error) {
var (
wg sync.WaitGroup
)
var result cnitypes.Result
result.CNIVersion = "none"
var err error
wg.Add(1)
go func() {
defer wg.Done()
var f *os.File
f, err = os.OpenFile(networkns, os.O_RDONLY, 0755)
if err != nil {
return
}
if err = unix.Setns(int(f.Fd()), unix.CLONE_NEWNET); err != nil {
return
}
var ifaces []net.Interface
ifaces, err = net.Interfaces()
if err != nil {
return
}
var routes []netlink.Route
routes, err = netlink.RouteList(nil, netlink.FAMILY_ALL)
if err != nil {
return
}
var gateway net.IP
for _, route := range routes {
// default gateway
if route.Dst == nil {
gateway = route.Gw
}
}

for _, iface := range ifaces {
if strings.Contains(iface.Flags.String(), "loopback") {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
if len(addrs) == 0 {
continue
}
result.Interfaces = append(result.Interfaces, &cnitypes.Interface{
Name: iface.Name,
Mac: iface.HardwareAddr.String(),
})
interfaceIndex := len(result.Interfaces) - 1
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok {
version := "4"
if ipnet.IP.To4() == nil {
version = "6"
}
result.IPs = append(result.IPs, &cnitypes.IPConfig{
Version: version,
Address: *ipnet,
Gateway: gateway,
Interface: &interfaceIndex,
})
}
}
}
}()
wg.Wait()
return result, err
}

// Produce an InspectNetworkSettings containing information on the container
// network.
func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) {
Expand Down Expand Up @@ -1028,6 +1103,25 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
return nil, err
}

for _, namespace := range c.config.Spec.Linux.Namespaces {
if namespace.Type == spec.NetworkNamespace {
if namespace.Path != "" {
result, err := c.inspectJoinedNetworkNS(namespace.Path)
// do not propagate the error inspecting a joined network ns
if err != nil {
logrus.Errorf("Error inspecting network namespace: %s of container %s: %v", namespace.Path, c.ID(), err)
return settings, nil
}
basicConfig, err := resultToBasicNetworkConfig(&result)
if err != nil {
return nil, err
}
settings.InspectBasicNetworkConfig = basicConfig
return settings, nil
}
}
}

// We can't do more if the network is down.
if c.state.NetNS == nil {
// We still want to make dummy configurations for each CNI net
Expand Down Expand Up @@ -1144,7 +1238,9 @@ func resultToBasicNetworkConfig(result *cnitypes.Result) (define.InspectBasicNet
config.MacAddress = result.Interfaces[*ctrIP.Interface].Mac
}
case ctrIP.Version == "4" && config.IPAddress != "":
config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, ctrIP.Address.String())
config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, dockernetworktype.Address{
Addr: ctrIP.Address.IP.String(),
PrefixLen: size})
if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface >= 0 {
config.AdditionalMacAddresses = append(config.AdditionalMacAddresses, result.Interfaces[*ctrIP.Interface].Mac)
}
Expand All @@ -1153,7 +1249,9 @@ func resultToBasicNetworkConfig(result *cnitypes.Result) (define.InspectBasicNet
config.GlobalIPv6PrefixLen = size
config.IPv6Gateway = ctrIP.Gateway.String()
case ctrIP.Version == "6" && config.IPAddress != "":
config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, ctrIP.Address.String())
config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, dockernetworktype.Address{
Addr: ctrIP.Address.IP.String(),
PrefixLen: size})
default:
return config, errors.Wrapf(define.ErrInternal, "unrecognized IP version %q", ctrIP.Version)
}
Expand Down
137 changes: 137 additions & 0 deletions test/e2e/run_networking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package integration
import (
"fmt"
"os"
"runtime"
"strings"

. "github.com/containers/podman/v3/test/utils"
Expand All @@ -11,6 +12,7 @@ import (
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
"github.com/uber/jaeger-client-go/utils"
"github.com/vishvananda/netns"
)

var _ = Describe("Podman run networking", func() {
Expand Down Expand Up @@ -641,6 +643,141 @@ var _ = Describe("Podman run networking", func() {
Expect(session.OutputToString()).To(ContainSubstring("11.11.11.11"))
})

setup_network_ns := func(networkNSName string, newns netns.NsHandle) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

// Save the current network namespace
origns, _ := netns.Get()
defer origns.Close()

err = netns.Set(newns)
Expect(err).To(BeNil())

setupNetworkNS := SystemExec("ip", []string{"link", "add", "enp2s0", "type", "veth", "peer", "name", "eth0"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))
setupNetworkNS = SystemExec("ip", []string{"addr", "add", "10.0.0.1/24", "dev", "eth0"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))
setupNetworkNS = SystemExec("ip", []string{"-6", "addr", "add", "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64", "dev", "eth0"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))
setupNetworkNS = SystemExec("ip", []string{"link", "set", "eth0", "up"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))

setupNetworkNS = SystemExec("ip", []string{"link", "add", "enp2s", "type", "veth", "peer", "name", "eth1"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))
setupNetworkNS = SystemExec("ip", []string{"addr", "add", "10.10.10.0/20", "dev", "eth1"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))
setupNetworkNS = SystemExec("ip", []string{"addr", "add", "10.20.20.0/16", "dev", "eth1"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))
setupNetworkNS = SystemExec("ip", []string{"link", "set", "eth1", "up"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))

setupNetworkNS = SystemExec("ip", []string{"route", "add", "default", "via", "10.10.10.0", "dev", "eth1"})
Expect(setupNetworkNS.ExitCode()).To(Equal(0))

// Switch back to the original namespace
netns.Set(origns)
}

check_network_ns_inspect := func(name string) {
inspectOut := podmanTest.InspectContainer(name)
Expect(inspectOut[0].NetworkSettings.IPAddress).To(Equal("10.0.0.1"))
Expect(inspectOut[0].NetworkSettings.IPPrefixLen).To(Equal(24))
Expect(len(inspectOut[0].NetworkSettings.SecondaryIPAddresses)).To(Equal(2))
Expect(inspectOut[0].NetworkSettings.SecondaryIPAddresses[0].Addr).To(Equal("10.10.10.0"))
Expect(inspectOut[0].NetworkSettings.SecondaryIPAddresses[0].PrefixLen).To(Equal(20))
Expect(inspectOut[0].NetworkSettings.SecondaryIPAddresses[1].Addr).To(Equal("10.20.20.0"))
Expect(inspectOut[0].NetworkSettings.SecondaryIPAddresses[1].PrefixLen).To(Equal(16))
Expect(len(inspectOut[0].NetworkSettings.SecondaryIPv6Addresses)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.SecondaryIPv6Addresses[0].Addr).To(Equal("2a00:c98:2060:a000:1:0:1d1e:ca75"))
Expect(inspectOut[0].NetworkSettings.SecondaryIPv6Addresses[0].PrefixLen).To(Equal(64))
Expect(inspectOut[0].NetworkSettings.Gateway).To(Equal("10.10.10.0"))
}

It("podman run newtork inspect fails gracefully on non-reachable network ns", func() {
SkipIfRootless("ip netns is not supported for rootless users")
if Containerized() {
Skip("Cannot be run within a container.")
}
networkNSName := "xxx3"
newns, _ := netns.NewNamed(networkNSName)

setup_network_ns(networkNSName, newns)

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

// delete the named network ns before inspect
newns.Close()
netns.DeleteNamed(networkNSName)

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")
if Containerized() {
Skip("Cannot be run within a container.")
}

networkNSName := "xxx3"
newns, _ := netns.NewNamed(networkNSName)
defer newns.Close()
defer netns.DeleteNamed(networkNSName)

setup_network_ns(networkNSName, newns)

name := "xxx3Container"
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()

check_network_ns_inspect(name)
})

It("podman do not tamper with joined network ns interfaces", func() {
SkipIfRootless("ip netns is not supported for rootless users")
if Containerized() {
Skip("Cannot be run within a container.")
}

networkNSName := "xxx3"
newns, _ := netns.NewNamed(networkNSName)
defer newns.Close()
defer netns.DeleteNamed(networkNSName)

setup_network_ns(networkNSName, newns)

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

check_network_ns_inspect(name)

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

check_network_ns_inspect(name)

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()

check_network_ns_inspect(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

0 comments on commit 463ede9

Please sign in to comment.