From 6595ffbe3cd3353f36e7b34c9951e7e26df90bb1 Mon Sep 17 00:00:00 2001 From: Toshiki Sonoda Date: Mon, 15 May 2023 15:04:00 +0900 Subject: [PATCH] libnetwork: add NetworkInfo() for get network information NetworkInfo() return the network binary path, package version, program version and DNS information. Signed-off-by: Toshiki Sonoda --- libnetwork/cni/network.go | 38 ++++++++++++++ libnetwork/netavark/network.go | 32 ++++++++++++ libnetwork/types/network.go | 20 ++++++++ pkg/util/util.go | 90 +++++++++++++++++++++++++++++++++- 4 files changed, 179 insertions(+), 1 deletion(-) diff --git a/libnetwork/cni/network.go b/libnetwork/cni/network.go index e9121252a..8180b49b2 100644 --- a/libnetwork/cni/network.go +++ b/libnetwork/cni/network.go @@ -17,6 +17,7 @@ import ( "github.com/containernetworking/cni/libcni" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/config" + cutil "github.com/containers/common/pkg/util" "github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/unshare" "github.com/sirupsen/logrus" @@ -295,6 +296,43 @@ func (n *cniNetwork) DefaultInterfaceName() string { return cniDeviceName } +// NetworkInfo return the network information about binary path, +// package version and program version. +func (n *cniNetwork) NetworkInfo() types.NetworkInfo { + path := "" + packageVersion := "" + for _, p := range n.cniPluginDirs { + ver := cutil.PackageVersion(p) + if ver != cutil.UnknownPackage { + path = p + packageVersion = ver + break + } + } + + info := types.NetworkInfo{ + Backend: types.CNI, + Package: packageVersion, + Path: path, + } + + dnsPath := filepath.Join(path, "dnsname") + dnsPackage := cutil.PackageVersion(dnsPath) + dnsProgram, err := cutil.ProgramVersionDnsname(dnsPath) + if err != nil { + logrus.Infof("Failed to get the dnsname plugin version: %v", err) + } + if _, err := os.Stat(dnsPath); err == nil { + info.DNS = types.DNSNetworkInfo{ + Path: dnsPath, + Package: dnsPackage, + Version: dnsProgram, + } + } + + return info +} + func (n *cniNetwork) Network(nameOrID string) (*types.Network, error) { network, err := n.getNetwork(nameOrID) if err != nil { diff --git a/libnetwork/netavark/network.go b/libnetwork/netavark/network.go index 77dda2483..cadf5e718 100644 --- a/libnetwork/netavark/network.go +++ b/libnetwork/netavark/network.go @@ -15,6 +15,7 @@ import ( "github.com/containers/common/libnetwork/internal/util" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/config" + cutil "github.com/containers/common/pkg/util" "github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/unshare" "github.com/sirupsen/logrus" @@ -336,6 +337,37 @@ func (n *netavarkNetwork) DefaultInterfaceName() string { return defaultBridgeName } +// NetworkInfo return the network information about binary path, +// package version and program version. +func (n *netavarkNetwork) NetworkInfo() types.NetworkInfo { + path := n.netavarkBinary + packageVersion := cutil.PackageVersion(path) + programVersion, err := cutil.ProgramVersion(path) + if err != nil { + logrus.Infof("Failed to get the netavark version: %v", err) + } + info := types.NetworkInfo{ + Backend: types.Netavark, + Version: programVersion, + Package: packageVersion, + Path: path, + } + + dnsPath := n.aardvarkBinary + dnsPackage := cutil.PackageVersion(dnsPath) + dnsProgram, err := cutil.ProgramVersion(dnsPath) + if err != nil { + logrus.Infof("Failed to get the aardvark version: %v", err) + } + info.DNS = types.DNSNetworkInfo{ + Package: dnsPackage, + Path: dnsPath, + Version: dnsProgram, + } + + return info +} + func (n *netavarkNetwork) Network(nameOrID string) (*types.Network, error) { network, err := n.getNetwork(nameOrID) if err != nil { diff --git a/libnetwork/types/network.go b/libnetwork/types/network.go index b8804bf6b..2058597a0 100644 --- a/libnetwork/types/network.go +++ b/libnetwork/types/network.go @@ -34,6 +34,10 @@ type ContainerNetwork interface { // DefaultNetworkName will return the default network name // for this interface. DefaultNetworkName() string + + // NetworkInfo return the network information about backend type, + // binary path, package version and so on. + NetworkInfo() NetworkInfo } // Network describes the Network attributes. @@ -80,6 +84,22 @@ type NetworkUpdateOptions struct { RemoveDNSServers []string `json:"remove_dns_servers,omitempty"` } +// NetworkInfo contains the network information. +type NetworkInfo struct { + Backend NetworkBackend `json:"backend"` + Version string `json:"version,omitempty"` + Package string `json:"package,omitempty"` + Path string `json:"path,omitempty"` + DNS DNSNetworkInfo `json:"dns,omitempty"` +} + +// NetworkInfo contains the DNS information. +type DNSNetworkInfo struct { + Version string `json:"version,omitempty"` + Package string `json:"package,omitempty"` + Path string `json:"path,omitempty"` +} + // IPNet is used as custom net.IPNet type to add Marshal/Unmarshal methods. type IPNet struct { net.IPNet diff --git a/pkg/util/util.go b/pkg/util/util.go index 98890a686..44d4cda6e 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,6 +1,94 @@ package util -import "regexp" +import ( + "bytes" + "fmt" + "os/exec" + "regexp" + "strings" +) + +const ( + UnknownPackage = "Unknown" +) + +// Note: This function is copied from containers/podman libpod/util.go +// Please see https://github.com/containers/common/pull/1460 +func queryPackageVersion(cmdArg ...string) string { + output := UnknownPackage + if 1 < len(cmdArg) { + cmd := exec.Command(cmdArg[0], cmdArg[1:]...) + if outp, err := cmd.Output(); err == nil { + output = string(outp) + if cmdArg[0] == "/usr/bin/dpkg" { + r := strings.Split(output, ": ") + queryFormat := `${Package}_${Version}_${Architecture}` + cmd = exec.Command("/usr/bin/dpkg-query", "-f", queryFormat, "-W", r[0]) + if outp, err := cmd.Output(); err == nil { + output = string(outp) + } + } + } + if cmdArg[0] == "/sbin/apk" { + prefix := cmdArg[len(cmdArg)-1] + " is owned by " + output = strings.Replace(output, prefix, "", 1) + } + } + return strings.Trim(output, "\n") +} + +// Note: This function is copied from containers/podman libpod/util.go +// Please see https://github.com/containers/common/pull/1460 +func PackageVersion(program string) string { // program is full path + packagers := [][]string{ + {"/usr/bin/rpm", "-q", "-f"}, + {"/usr/bin/dpkg", "-S"}, // Debian, Ubuntu + {"/usr/bin/pacman", "-Qo"}, // Arch + {"/usr/bin/qfile", "-qv"}, // Gentoo (quick) + {"/usr/bin/equery", "b"}, // Gentoo (slow) + {"/sbin/apk", "info", "-W"}, // Alpine + {"/usr/local/sbin/pkg", "which", "-q"}, // FreeBSD + } + + for _, cmd := range packagers { + cmd = append(cmd, program) + if out := queryPackageVersion(cmd...); out != UnknownPackage { + return out + } + } + return UnknownPackage +} + +// Note: This function is copied from containers/podman libpod/util.go +// Please see https://github.com/containers/common/pull/1460 +func ProgramVersion(program string) (string, error) { + return programVersion(program, false) +} + +func ProgramVersionDnsname(program string) (string, error) { + return programVersion(program, true) +} + +func programVersion(program string, dnsname bool) (string, error) { + cmd := exec.Command(program, "--version") + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("`%v --version` failed: %v %v (%v)", program, stderr.String(), stdout.String(), err) + } + + output := strings.TrimSuffix(stdout.String(), "\n") + // dnsname --version returns the information to stderr + if dnsname { + output = strings.TrimSuffix(stderr.String(), "\n") + } + + return output, nil +} // StringInSlice determines if a string is in a string slice, returns bool func StringInSlice(s string, sl []string) bool {