Blame internal/boot/netns.go

Packit Service 509fd4
// +build integration
Packit Service 509fd4
Packit Service 509fd4
package boot
Packit Service 509fd4
Packit Service 509fd4
import (
Packit Service 509fd4
	"context"
Packit Service 509fd4
	"fmt"
Packit Service 509fd4
	"io/ioutil"
Packit Service 509fd4
	"log"
Packit Service 509fd4
	"os"
Packit Service 509fd4
	"os/exec"
Packit Service 509fd4
	"path"
Packit Service 509fd4
	"runtime"
Packit Service 509fd4
	"syscall"
Packit Service 509fd4
Packit Service 509fd4
	"golang.org/x/sys/unix"
Packit Service 509fd4
)
Packit Service 509fd4
Packit Service 509fd4
const netnsDir = "/var/run/netns"
Packit Service 509fd4
Packit Service 509fd4
// Network namespace abstraction
Packit Service 509fd4
type NetNS string
Packit Service 509fd4
Packit Service 509fd4
// newNetworkNamespace returns a new network namespace with a random
Packit Service 509fd4
// name. The calling goroutine remains in the same namespace
Packit Service 509fd4
// as before the call.
Packit Service 509fd4
func newNetworkNamespace() (NetNS, error) {
Packit Service 509fd4
	// This method needs to unshare the current thread. Go runtime can switch
Packit Service 509fd4
	// the goroutine to run on a different thread at any point, so we need
Packit Service 509fd4
	// to ensure that this method runs in the same thread for its whole
Packit Service 509fd4
	// lifetime.
Packit Service 509fd4
	runtime.LockOSThread()
Packit Service 509fd4
	defer runtime.UnlockOSThread()
Packit Service 509fd4
Packit Service 509fd4
	_, err := os.Stat(netnsDir)
Packit Service 509fd4
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		if os.IsNotExist(err) {
Packit Service 509fd4
			err := os.Mkdir(netnsDir, 0755)
Packit Service 509fd4
			if err != nil {
Packit Service 509fd4
				return "", fmt.Errorf("cannot create %s: %v", netnsDir, err)
Packit Service 509fd4
			}
Packit Service 509fd4
		} else {
Packit Service 509fd4
			return "", fmt.Errorf("cannot stat %s: %v", netnsDir, err)
Packit Service 509fd4
		}
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	f, err := ioutil.TempFile(netnsDir, "osbuild-composer-namespace")
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return "", fmt.Errorf("cannot create a tempfile: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	// We want to remove the temporary file if the namespace initialization fails.
Packit Service 509fd4
	// The best method I could thought of is to have the following variable
Packit Service 509fd4
	// denoting if the initialization was successful. It is set to true right
Packit Service 509fd4
	// before the end of this function.
Packit Service 509fd4
	initOK := false
Packit Service 509fd4
	defer func() {
Packit Service 509fd4
		if !initOK {
Packit Service 509fd4
			err := os.Remove(f.Name())
Packit Service 509fd4
			if err != nil {
Packit Service 509fd4
				log.Printf("cannot remove the temporary namespace: %v", err)
Packit Service 509fd4
			}
Packit Service 509fd4
		}
Packit Service 509fd4
	}()
Packit Service 509fd4
Packit Service 509fd4
	oldNS, err := os.Open("/proc/self/ns/net")
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return "", fmt.Errorf("cannot open the current namespace: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	err = syscall.Unshare(syscall.CLONE_NEWNET)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return "", fmt.Errorf("cannot unshare the network namespace")
Packit Service 509fd4
	}
Packit Service 509fd4
	defer func() {
Packit Service 509fd4
		err = unix.Setns(int(oldNS.Fd()), syscall.CLONE_NEWNET)
Packit Service 509fd4
		if err != nil {
Packit Service 509fd4
			// We cannot return to the original namespace.
Packit Service 509fd4
			// As we don't know nothing about affected threads, let's just
Packit Service 509fd4
			// quit immediately.
Packit Service 509fd4
			log.Fatalf("returning to the original namespace failed, quitting: %v", err)
Packit Service 509fd4
		}
Packit Service 509fd4
	}()
Packit Service 509fd4
Packit Service 509fd4
	cmd := exec.Command("ip", "link", "set", "up", "dev", "lo")
Packit Service 509fd4
	cmd.Stderr = os.Stderr
Packit Service 509fd4
	cmd.Stdout = os.Stderr
Packit Service 509fd4
	err = cmd.Run()
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return "", fmt.Errorf("cannot set up a loopback device in the new namespace: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	cmd = exec.Command("mount", "-o", "bind", "/proc/self/ns/net", f.Name())
Packit Service 509fd4
	cmd.Stderr = os.Stderr
Packit Service 509fd4
	cmd.Stdout = os.Stderr
Packit Service 509fd4
	err = cmd.Run()
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return "", fmt.Errorf("cannot bind mount the new namespace: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	ns := NetNS(path.Base(f.Name()))
Packit Service 509fd4
Packit Service 509fd4
	// Initialization OK, do not delete the namespace file.
Packit Service 509fd4
	initOK = true
Packit Service 509fd4
	return ns, nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// NamespaceCommand returns an *exec.Cmd struct with the difference
Packit Service 509fd4
// that it's prepended by "ip netns exec NAMESPACE_NAME" command, which
Packit Service 509fd4
// runs the command in a namespaced environment.
Packit Service 509fd4
func (n NetNS) NamespacedCommand(name string, arg ...string) *exec.Cmd {
Packit Service 509fd4
	args := []string{"netns", "exec", string(n), name}
Packit Service 509fd4
	args = append(args, arg...)
Packit Service 509fd4
	return exec.Command("ip", args...)
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// NamespaceCommand returns an *exec.Cmd struct with the difference
Packit Service 509fd4
// that it's prepended by "ip netns exec NAMESPACE_NAME" command, which
Packit Service 509fd4
// runs the command in a namespaced environment.
Packit Service 509fd4
func (n NetNS) NamespacedCommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
Packit Service 509fd4
	args := []string{"netns", "exec", string(n), name}
Packit Service 509fd4
	args = append(args, arg...)
Packit Service 509fd4
	return exec.CommandContext(ctx, "ip", args...)
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// Path returns the path to the namespace file
Packit Service 509fd4
func (n NetNS) Path() string {
Packit Service 509fd4
	return path.Join(netnsDir, string(n))
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// Delete deletes the namespaces
Packit Service 509fd4
func (n NetNS) Delete() error {
Packit Service 509fd4
	cmd := exec.Command("umount", n.Path())
Packit Service 509fd4
	cmd.Stderr = os.Stderr
Packit Service 509fd4
	cmd.Stdout = os.Stdout
Packit Service 509fd4
	err := cmd.Run()
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("cannot unmount the network namespace: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	err = os.Remove(n.Path())
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("cannot delete the network namespace file: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return nil
Packit Service 509fd4
}