debian-forge-composer/internal/boot/netns.go
Martin Sehnoutka 125fce92db internal/boot: Make some function public
More specifically only those that are needed in
/cmd/osbuild-image/tests.

This patch can be merged with the previous one if we want to make sure
every commit can be built, but I'm going to keep it like this for now so
that we can easily see the changes.
2020-09-03 15:12:59 +01:00

148 lines
4 KiB
Go

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