diff --git a/cmd/main.go b/cmd/main.go
index ea1cf12..02cd9c7 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -3,6 +3,7 @@
 package main
 
 import (
+	"antoine-roux.tk/projects/go/firecracker-netns/internal/netlink"
 	"antoine-roux.tk/projects/go/firecracker-netns/internal/netns"
 	"fmt"
 	"net"
@@ -11,26 +12,43 @@ import (
 	"runtime"
 )
 
-func main() {
-	// Lock the OS Thread, so we don't accidentally switch namespaces
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	handle, err := netns.New()
+func setupEnv() int {
+	newNs, err := netns.New()
 	if err != nil {
 		fmt.Println("new ns error", err)
+		return 1
 	}
 
-	defer func(handle *netns.NsHandle) {
+	defer func(handle netns.NsHandle) {
 		err := handle.Close()
 		if err != nil {
 			fmt.Println("close ns error", err)
 		}
-	}(&handle)
+	}(newNs)
 
-	err = netns.Set(handle)
+	defer func(ns netns.NsHandle) {
+		err := netns.Delete(ns)
+		if err != nil {
+			fmt.Println("delete ns error", err)
+		}
+	}(newNs)
+
+	vethPair, err := netlink.NewVethPair(newNs)
 	if err != nil {
-		fmt.Println("set ns error", err)
+		fmt.Println("new Veth error", err)
+		return 1
+	}
+
+	defer func(veth *netlink.PairLink) {
+		err = veth.DeleteLink()
+		if err != nil {
+			fmt.Println("delete vethPair error", err)
+		}
+	}(vethPair)
+
+	err = netns.Set(newNs)
+	if err != nil {
+		return 1
 	}
 
 	// Do something with the network namespace
@@ -45,17 +63,18 @@ func main() {
 
 	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)
-	}
+	return 0
+}
+
+func main() {
+	// Lock the OS Thread, so we don't accidentally switch namespaces
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	os.Exit(setupEnv())
 }
diff --git a/go.mod b/go.mod
index 3e4e3e1..99556f0 100644
--- a/go.mod
+++ b/go.mod
@@ -5,5 +5,8 @@ go 1.21.5
 require (
 	github.com/docker/docker v24.0.7+incompatible
 	github.com/hashicorp/go-version v1.6.0
+	github.com/vishvananda/netlink v1.2.1-beta.2
 	golang.org/x/sys v0.15.0
 )
+
+require github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
diff --git a/go.sum b/go.sum
index c9d1414..561652b 100644
--- a/go.sum
+++ b/go.sum
@@ -2,5 +2,11 @@ github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig
 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=
+github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
+github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
+github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
+github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
+golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/internal/netlink/netlink.go b/internal/netlink/netlink.go
new file mode 100644
index 0000000..a115f86
--- /dev/null
+++ b/internal/netlink/netlink.go
@@ -0,0 +1,103 @@
+package netlink
+
+import (
+	"antoine-roux.tk/projects/go/firecracker-netns/internal/netns"
+	"crypto/rand"
+	"fmt"
+	"github.com/vishvananda/netlink"
+)
+
+// inspired by https://github.com/containernetworking/plugins/blob/main/pkg/ip/link_linux.go#L57
+
+type Peer struct {
+	link  netlink.Link
+	netNs netns.NsHandle
+}
+
+type PairLink struct {
+	host  Peer
+	guest Peer
+}
+
+func NewVethPair(ns netns.NsHandle) (pair *PairLink, error error) {
+
+	origins, err := netns.Get()
+	if err != nil {
+		return nil, err
+	}
+
+	err = netns.Set(ns)
+	if err != nil {
+		return nil, err
+	}
+
+	name, err := randomVethName()
+	if err != nil {
+		return nil, err
+	}
+
+	peerName, err := randomVethName()
+	if err != nil {
+		return nil, err
+	}
+
+	attrs := netlink.NewLinkAttrs()
+	attrs.Name = name
+	veth := &netlink.Veth{
+		LinkAttrs:     attrs,
+		PeerName:      peerName,
+		PeerNamespace: netlink.NsFd(origins.Fd),
+	}
+
+	if err := netlink.LinkAdd(veth); err != nil {
+		return nil, err
+	}
+
+	// Re-fetch the container link to get its creation-time parameters, e.g. index and mac
+	fmt.Println(name)
+	veth2, err := netlink.LinkByName(name)
+	if err != nil {
+		netlink.LinkDel(veth)
+		return nil, err
+	}
+
+	err = netns.Set(origins)
+	if err != nil {
+		return nil, err
+	}
+
+	fmt.Println(peerName)
+	veth1, err := netlink.LinkByName(peerName)
+	if err != nil {
+		netlink.LinkDel(veth)
+		return nil, err
+	}
+
+	return &PairLink{host: Peer{veth1, origins}, guest: Peer{veth2, ns}}, nil
+}
+
+// DeleteLink removes an interface link.
+func (p *PairLink) DeleteLink() error {
+	err := netns.Set(p.host.netNs)
+	if err != nil {
+		return fmt.Errorf("set original Ns for deleting veth %q: %v", p.host.link.Attrs().Name, err)
+	}
+
+	if err := netlink.LinkDel(p.host.link); err != nil {
+		return fmt.Errorf("failed to delete %q: %v", p.host.link.Attrs().Name, err)
+	}
+
+	return nil
+}
+
+// randomVethName returns string "veth" with random prefix (hashed from entropy)
+func randomVethName() (string, error) {
+	entropy := make([]byte, 4)
+	_, err := rand.Read(entropy)
+	if err != nil {
+		return "", fmt.Errorf("failed to generate random veth name: %v", err)
+	}
+
+	// NetworkManager (recent versions) will ignore veth devices that start with "veth"
+	return fmt.Sprintf("veth%x", entropy), nil
+}
diff --git a/internal/netns/netns.go b/internal/netns/netns.go
index 9dff324..0666b53 100644
--- a/internal/netns/netns.go
+++ b/internal/netns/netns.go
@@ -3,16 +3,19 @@ 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"
+	"fmt"
+	"github.com/docker/docker/pkg/namesgenerator"
+	"golang.org/x/sys/unix"
+	"os"
+	"path"
 )
 
-// New create and persist a new namespace with random name
+// New create and persist a new namespace with random Name
 func New() (handle NsHandle, error error) {
-	origins, _ := Get()
+	origins, err := Get()
+	if err != nil {
+		return None(), err
+	}
 	defer deferClose(&origins, &error)
 
 	if err := unix.Unshare(unix.CLONE_NEWNET); err != nil {
@@ -26,7 +29,7 @@ func New() (handle NsHandle, error error) {
 
 	err = Set(origins)
 	if err != nil {
-		return NsHandle{fd: 0}, err
+		return NsHandle{Fd: 0}, err
 	}
 
 	err = newNs.persist()
@@ -47,7 +50,7 @@ func deferClose(origins *NsHandle, e *error) {
 
 // Delete deletes a named network namespace
 func Delete(ns NsHandle) error {
-	namedPath := path.Join(bindMountPath, ns.name)
+	namedPath := path.Join(bindMountPath, ns.Name)
 
 	err := unix.Unmount(namedPath, unix.MNT_DETACH)
 	if err != nil {
@@ -83,5 +86,5 @@ func GetFromPath(path string) (NsHandle, error) {
 // 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)
+	return unix.Setns(ns.Fd, unix.CLONE_NEWNET)
 }
diff --git a/internal/netns/nshandle.go b/internal/netns/nshandle.go
index 8880a95..8aafabe 100644
--- a/internal/netns/nshandle.go
+++ b/internal/netns/nshandle.go
@@ -3,10 +3,10 @@ package netns
 // inspired from https://github.com/vishvananda/netns/blob/master/nshandle_linux.go#L11
 
 import (
-  "fmt"
-  "golang.org/x/sys/unix"
-  "os"
-  "path"
+	"fmt"
+	"golang.org/x/sys/unix"
+	"os"
+	"path"
 )
 
 const bindMountPath = "/run/netns"
@@ -14,12 +14,12 @@ 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
+	Fd   int
+	Name string
 }
 
 // persist and bind mount net namespace to `/run/netns`
-func (ns NsHandle) persist() error {
+func (ns *NsHandle) persist() error {
 	if _, err := os.Stat(bindMountPath); os.IsNotExist(err) {
 		err = os.MkdirAll(bindMountPath, 0o755)
 		if err != nil {
@@ -27,7 +27,7 @@ func (ns NsHandle) persist() error {
 		}
 	}
 
-	namedPath := path.Join(bindMountPath, ns.name)
+	namedPath := path.Join(bindMountPath, ns.Name)
 
 	f, err := os.OpenFile(namedPath, os.O_CREATE|os.O_EXCL, 0o444)
 	if err != nil {
@@ -50,50 +50,50 @@ func (ns NsHandle) persist() error {
 // 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 {
+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 {
+	if err := unix.Fstat(ns.Fd, &s1); err != nil {
 		return false
 	}
-	if err := unix.Fstat(other.fd, &s2); err != nil {
+	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 {
+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 {
+	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
+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 {
+func (ns *NsHandle) Close() error {
+	if err := unix.Close(ns.Fd); err != nil {
 		return err
 	}
-	ns.fd = -1
+	ns.Fd = -1
 	return nil
 }
 
 // None gets an empty (closed) NsHandle.
 func None() NsHandle {
 	return NsHandle{
-		fd: -1,
+		Fd: -1,
 	}
 }