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.
148 lines
4 KiB
Go
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
|
|
}
|