summaryrefslogtreecommitdiff
path: root/libpod/networking_linux.go
diff options
context:
space:
mode:
Diffstat (limited to 'libpod/networking_linux.go')
-rw-r--r--libpod/networking_linux.go230
1 files changed, 230 insertions, 0 deletions
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
new file mode 100644
index 000000000..ee90b765c
--- /dev/null
+++ b/libpod/networking_linux.go
@@ -0,0 +1,230 @@
+// +build linux
+
+package libpod
+
+import (
+ "crypto/rand"
+ "fmt"
+ "net"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+
+ cnitypes "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containernetworking/plugins/pkg/ns"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/pkg/errors"
+ "github.com/projectatomic/libpod/utils"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+ "golang.org/x/sys/unix"
+)
+
+// Get an OCICNI network config
+func getPodNetwork(id, name, nsPath string, ports []ocicni.PortMapping) ocicni.PodNetwork {
+ return ocicni.PodNetwork{
+ Name: name,
+ Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces
+ ID: id,
+ NetNS: nsPath,
+ PortMappings: ports,
+ }
+}
+
+// Create and configure a new network namespace for a container
+func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) {
+ podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.PortMappings)
+
+ result, err := r.netPlugin.SetUpPod(podNetwork)
+ if err != nil {
+ return errors.Wrapf(err, "error configuring network namespace for container %s", ctr.ID())
+ }
+ defer func() {
+ if err != nil {
+ if err2 := r.netPlugin.TearDownPod(podNetwork); err2 != nil {
+ logrus.Errorf("Error tearing down partially created network namespace for container %s: %v", ctr.ID(), err2)
+ }
+ }
+ }()
+
+ logrus.Debugf("Response from CNI plugins: %v", result.String())
+
+ resultStruct, err := cnitypes.GetResult(result)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing result from CNI plugins")
+ }
+
+ ctr.state.NetNS = ctrNS
+ ctr.state.IPs = resultStruct.IPs
+ ctr.state.Routes = resultStruct.Routes
+ ctr.state.Interfaces = resultStruct.Interfaces
+
+ // We need to temporarily use iptables to allow the container
+ // to resolve DNS until this issue is fixed upstream.
+ // https://github.com/containernetworking/plugins/pull/75
+ if resultStruct.IPs != nil {
+ for _, ip := range resultStruct.IPs {
+ if ip.Address.IP.To4() != nil {
+ iptablesDNS("-I", ip.Address.IP.String())
+ }
+ }
+ }
+ return nil
+}
+
+// Create and configure a new network namespace for a container
+func (r *Runtime) createNetNS(ctr *Container) (err error) {
+ ctrNS, err := ns.NewNS()
+ if err != nil {
+ return errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID())
+ }
+ defer func() {
+ if err != nil {
+ if err2 := ctrNS.Close(); err2 != nil {
+ logrus.Errorf("Error closing partially created network namespace for container %s: %v", ctr.ID(), err2)
+ }
+ }
+ }()
+
+ logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID())
+ return r.configureNetNS(ctr, ctrNS)
+}
+
+// Configure the network namespace using the container process
+func (r *Runtime) setupNetNS(ctr *Container) (err error) {
+ nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID)
+
+ b := make([]byte, 16)
+
+ if _, err := rand.Reader.Read(b); err != nil {
+ return errors.Wrapf(err, "failed to generate random netns name")
+ }
+
+ nsPath := fmt.Sprintf("/var/run/netns/cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
+
+ if err := os.MkdirAll(filepath.Dir(nsPath), 0711); err != nil {
+ return errors.Wrapf(err, "cannot create %s", filepath.Dir(nsPath))
+ }
+
+ mountPointFd, err := os.Create(nsPath)
+ if err != nil {
+ return errors.Wrapf(err, "cannot open %s", nsPath)
+ }
+ mountPointFd.Close()
+
+ if err := unix.Mount(nsProcess, nsPath, "none", unix.MS_BIND, ""); err != nil {
+ return errors.Wrapf(err, "cannot mount %s", nsPath)
+ }
+
+ netNS, err := ns.GetNS(nsPath)
+ if err != nil {
+ return err
+ }
+ return r.configureNetNS(ctr, netNS)
+}
+
+// iptablesDNS accepts an arg (-I|-D) and IP address of the container and then
+// generates an iptables command to either add or subtract the needed rule
+func iptablesDNS(arg, ip string) error {
+ iptablesCmd := []string{"-t", "filter", arg, "FORWARD", "-s", ip, "!", "-o", ip, "-j", "ACCEPT"}
+ logrus.Debug("Running iptables command: ", strings.Join(iptablesCmd, " "))
+ _, err := utils.ExecCmd("iptables", iptablesCmd...)
+ if err != nil {
+ logrus.Error(err)
+ }
+ return err
+}
+
+// Join an existing network namespace
+func joinNetNS(path string) (ns.NetNS, error) {
+ ns, err := ns.GetNS(path)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving network namespace at %s", path)
+ }
+
+ return ns, nil
+}
+
+// Get a container's IP address
+func (r *Runtime) getContainerIP(ctr *Container) (net.IP, error) {
+ if ctr.state.NetNS == nil {
+ return nil, errors.Wrapf(ErrInvalidArg, "container %s has no network namespace, cannot get IP", ctr.ID())
+ }
+
+ podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.PortMappings)
+
+ ipStr, err := r.netPlugin.GetPodNetworkStatus(podNetwork)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving network status of container %s", ctr.ID())
+ }
+
+ ip := net.ParseIP(ipStr)
+ if ip == nil {
+ return nil, errors.Wrapf(ErrInternal, "error parsing IP address %s for container %s", ipStr, ctr.ID())
+ }
+
+ return ip, nil
+}
+
+// Tear down a network namespace
+func (r *Runtime) teardownNetNS(ctr *Container) error {
+ if ctr.state.NetNS == nil {
+ // The container has no network namespace, we're set
+ return nil
+ }
+
+ // Because we are using iptables to allow the container to resolve DNS
+ // on per IP address, we also need to try to remove the iptables rule
+ // on cleanup. Remove when https://github.com/containernetworking/plugins/pull/75
+ // is merged.
+ for _, ip := range ctr.state.IPs {
+ if ip.Address.IP.To4() != nil {
+ iptablesDNS("-D", ip.Address.IP.String())
+ }
+ }
+
+ logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID())
+
+ podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.PortMappings)
+
+ // The network may have already been torn down, so don't fail here, just log
+ if err := r.netPlugin.TearDownPod(podNetwork); err != nil {
+ logrus.Errorf("Failed to tear down network namespace for container %s: %v", ctr.ID(), err)
+ }
+
+ nsPath := ctr.state.NetNS.Path()
+
+ if err := ctr.state.NetNS.Close(); err != nil {
+ return errors.Wrapf(err, "error closing network namespace for container %s", ctr.ID())
+ }
+
+ // We need to unconditionally try to unmount/remove the namespace
+ // because we may be in a separate process from the one that created the
+ // namespace, and Close() will only do that if it is the same process.
+ if err := unix.Unmount(nsPath, unix.MNT_DETACH); err != nil {
+ if err != syscall.EINVAL && err != syscall.ENOENT {
+ return errors.Wrapf(err, "error unmounting network namespace %s for container %s", nsPath, ctr.ID())
+ }
+ }
+ if err := os.RemoveAll(nsPath); err != nil && !os.IsNotExist(err) {
+ return errors.Wrapf(err, "error removing network namespace %s for container %s", nsPath, ctr.ID())
+ }
+
+ ctr.state.NetNS = nil
+
+ return nil
+}
+
+func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) {
+ var netStats *netlink.LinkStatistics
+ err := ns.WithNetNSPath(ctr.state.NetNS.Path(), func(_ ns.NetNS) error {
+ link, err := netlink.LinkByName(ocicni.DefaultInterfaceName)
+ if err != nil {
+ return err
+ }
+ netStats = link.Attrs().Statistics
+ return nil
+ })
+ return netStats, err
+}