feature: create netns and execute sh commands into it

This commit is contained in:
RouxAntoine 2024-01-01 10:55:49 +01:00
parent c7514d60b8
commit f58fc5b102
Signed by: antoine
GPG Key ID: 098FB66FC0475E70
10 changed files with 340 additions and 8 deletions

View File

@ -1,11 +1,37 @@
.PHONY: build run
.PHONY: build run ci get-alpine-rootfs
.EXPORT_ALL_VARIABLES:
build:
go build -o ./out/main cmd/main.go
GOARCH=amd64
#GOARCH=arm
#GOOS=darwin
GOOS=linux
run:
@chmod +x ./out/main
./out/main
LDFLAGS=-w -s -X antoine-roux.tk/projects/go/firecracker-netns/internal/version.Version=$$(git rev-list -1 HEAD)
GOBUILDFLAGS=-tags dev
EXEC=out/main
build: out/alpine-minirootfs-3.19.0-x86_64.tar.gz $(EXEC)
run: $(EXEC)
@chmod +x $(EXEC)
$(EXEC)
ci:
golangci-lint run --fix
get-alpine-rootfs: out/alpine-minirootfs-3.19.0-x86_64.tar.gz
publish:
scp $(EXEC) sf314:~/firecracker/
dependencies:
go mod download
go mod verify
$(EXEC): cmd/main.go dependencies
@echo "build for os $$GOOS and arch $$GOARCH"
go build -o $@ -ldflags="$(LDFLAGS)" $(GOBUILDFLAGS) $<
out/alpine-minirootfs-3.19.0-x86_64.tar.gz:
wget -O $@ https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.0-x86_64.tar.gz

View File

View File

@ -1,7 +1,61 @@
//go:build linux
package main
import "fmt"
import (
"antoine-roux.tk/projects/go/firecracker-netns/internal/netns"
"fmt"
"net"
"os"
"os/exec"
"runtime"
)
func main() {
fmt.Println("Hello world !")
// Lock the OS Thread, so we don't accidentally switch namespaces
runtime.LockOSThread()
defer runtime.UnlockOSThread()
handle, err := netns.New()
if err != nil {
fmt.Println("new ns error", err)
}
defer func(handle *netns.NsHandle) {
err := handle.Close()
if err != nil {
fmt.Println("close ns error", err)
}
}(&handle)
err = netns.Set(handle)
if err != nil {
fmt.Println("set ns error", err)
}
// Do something with the network namespace
interfaces, _ := net.Interfaces()
fmt.Printf("Interfaces: %v\n", interfaces)
cmd := exec.Command("/bin/sh")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = []string{"PS1=-[ns-process]- # "}
/* cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
}*/
if err := cmd.Run(); err != nil {
fmt.Printf("Error running the /bin/sh command - %s\n", err)
os.Exit(1)
}
err = netns.Delete(handle)
if err != nil {
fmt.Println("delete ns error", err)
}
}

6
go.mod
View File

@ -1,3 +1,9 @@
module antoine-roux.tk/projects/go/firecracker-netns
go 1.21.5
require (
github.com/docker/docker v24.0.7+incompatible
github.com/hashicorp/go-version v1.6.0
golang.org/x/sys v0.15.0
)

6
go.sum Normal file
View File

@ -0,0 +1,6 @@
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View File

87
internal/netns/netns.go Normal file
View File

@ -0,0 +1,87 @@
package netns
// inspired from https://github.com/vishvananda/netns/blob/master/netns_linux.go#L95
import (
"fmt"
"github.com/docker/docker/pkg/namesgenerator"
"golang.org/x/sys/unix"
"os"
"path"
)
// New create and persist a new namespace with random name
func New() (handle NsHandle, error error) {
origins, _ := Get()
defer deferClose(&origins, &error)
if err := unix.Unshare(unix.CLONE_NEWNET); err != nil {
return None(), err
}
newNs, err := Get()
if err != nil {
return None(), err
}
err = Set(origins)
if err != nil {
return NsHandle{fd: 0}, err
}
err = newNs.persist()
if err != nil {
defer deferClose(&newNs, &error)
return newNs, err
}
return newNs, nil
}
func deferClose(origins *NsHandle, e *error) {
// return main error, or defer action error if occurred
if err := origins.Close(); e == nil && err != nil {
e = &err
}
}
// Delete deletes a named network namespace
func Delete(ns NsHandle) error {
namedPath := path.Join(bindMountPath, ns.name)
err := unix.Unmount(namedPath, unix.MNT_DETACH)
if err != nil {
return err
}
return os.Remove(namedPath)
}
// Get gets a handle to the current threads network namespace.
func Get() (NsHandle, error) {
return GetFromPath(GetPath())
}
// GetPath gets path to the current threads network namespace.
func GetPath() string {
tid := unix.Gettid()
pid := os.Getpid()
return fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid)
}
// GetFromPath gets a handle to a network namespace
// identified by the path
func GetFromPath(path string) (NsHandle, error) {
fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC, 0)
if err != nil {
return None(), err
}
name := namesgenerator.GetRandomName(0)
return NsHandle{fd, name}, nil
}
// Set sets the current network namespace to the namespace represented
// by NsHandle.
func Set(ns NsHandle) error {
return unix.Setns(ns.fd, unix.CLONE_NEWNET)
}

View File

@ -0,0 +1,99 @@
package netns
// inspired from https://github.com/vishvananda/netns/blob/master/nshandle_linux.go#L11
import (
"fmt"
"golang.org/x/sys/unix"
"os"
"path"
)
const bindMountPath = "/run/netns"
// NsHandle is a handle to a network namespace. It can be cast directly
// to an int and used as a file descriptor.
type NsHandle struct {
fd int
name string
}
// persist and bind mount net namespace to `/run/netns`
func (ns NsHandle) persist() error {
if _, err := os.Stat(bindMountPath); os.IsNotExist(err) {
err = os.MkdirAll(bindMountPath, 0o755)
if err != nil {
return err
}
}
namedPath := path.Join(bindMountPath, ns.name)
f, err := os.OpenFile(namedPath, os.O_CREATE|os.O_EXCL, 0o444)
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
nsPath := GetPath()
err = unix.Mount(nsPath, namedPath, "bind", unix.MS_BIND, "")
if err != nil {
return err
}
return nil
}
// Equal determines if two network handles refer to the same network
// namespace. This is done by comparing the device and inode that the
// file descriptors point to.
func (ns NsHandle) Equal(other NsHandle) bool {
if ns == other {
return true
}
var s1, s2 unix.Stat_t
if err := unix.Fstat(ns.fd, &s1); err != nil {
return false
}
if err := unix.Fstat(other.fd, &s2); err != nil {
return false
}
return (s1.Dev == s2.Dev) && (s1.Ino == s2.Ino)
}
// String shows the file descriptor number and its dev and inode.
func (ns NsHandle) String() string {
if ns.fd == -1 {
return "NS(none)"
}
var s unix.Stat_t
if err := unix.Fstat(ns.fd, &s); err != nil {
return fmt.Sprintf("NS(%d: unknown)", ns)
}
return fmt.Sprintf("NS(%d: %d, %d)", ns, s.Dev, s.Ino)
}
// IsOpen returns true if Close() has not been called.
func (ns NsHandle) IsOpen() bool {
return ns.fd != -1
}
// Close closes the NsHandle and resets its file descriptor to -1.
// It is not safe to use an NsHandle after Close() is called.
func (ns NsHandle) Close() error {
if err := unix.Close(ns.fd); err != nil {
return err
}
ns.fd = -1
return nil
}
// None gets an empty (closed) NsHandle.
func None() NsHandle {
return NsHandle{
fd: -1,
}
}

24
internal/version/dev.go Normal file
View File

@ -0,0 +1,24 @@
//go:build dev
package version
import (
"fmt"
)
// Version ...
var Version = "0.1.0"
// Prerelease such as "dev" (in development), "beta", "rc1", etc.
var Prerelease = "dev"
// Header is the header name used to send the current in http requests.
const Header = "Weather-Version"
// String returns the complete version string, including prerelease
func String() string {
if Prerelease != "" {
return fmt.Sprintf("%s-%s", Version, Prerelease)
}
return Version
}

30
internal/version/prod.go Normal file
View File

@ -0,0 +1,30 @@
//go:build prod
package version
import (
"fmt"
version "github.com/hashicorp/go-version"
)
// Version ...
var Version = "1.0.0"
// Prerelease such as "dev" (in development), "beta", "rc1", etc.
var Prerelease = "prod"
// SemVer management
var SemVer *version.Version
func init() {
SemVer = version.Must(version.NewSemver(fmt.Sprintf("%s-%s", Version, Prerelease)))
}
// Header is the header name used to send the current in http requests.
const Header = "Weather-Version"
// String returns the complete version string, including prerelease
func String() string {
return SemVer.String()
}