feature: create netns and execute sh commands into it
This commit is contained in:
parent
c7514d60b8
commit
f58fc5b102
38
Makefile
38
Makefile
@ -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
|
||||
|
58
cmd/main.go
58
cmd/main.go
@ -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
6
go.mod
@ -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
6
go.sum
Normal 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=
|
87
internal/netns/netns.go
Normal file
87
internal/netns/netns.go
Normal 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)
|
||||
}
|
99
internal/netns/nshandle.go
Normal file
99
internal/netns/nshandle.go
Normal 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
24
internal/version/dev.go
Normal 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
30
internal/version/prod.go
Normal 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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user