Linux Network Namespace from scratch using Golang

This is the second part of the series, where I’ll demonstrate how to create a network namespace and add a process to it. Although this series focuses on containers, I’m simplifying things by experimenting with processes, as a container essentially comprises processes + obviously some underlying magic. Nevertheless, the same principles discussed here apply.

Source: Julia Evans Blog⚠️ Note: I really like Julia Evans’ “zines”, so excuse me if it seems like I’m just posting other people’s images.

Given the abundance of excellent online resources that delve deeper into Linux Namespaces, I recommend revisiting them if you’re not already familiar with the concepts. To keep this post concise, I’ll concentrate solely on the Network namespace implementation in Go, potentially experimenting with others in subsequent posts.

Code Example

While it might deviate slightly from the typical blog post format, I’ve decided to guide you through the technical details using code comments. I believe this approach makes it much simpler to understand the functionalities we’re employing.

Before, we dive into the code there are two functionalities I want to mention. Namely:

unshare: This command facilitates the creation of new namespaces, specifically the ones specified as arguments. It’s available as a Linux command but also has bindings to programming languages, as we’ll see below. For more info, refer to the docs.bind-mount: This command enables us to persist the newly created namespace. By default, a new namespace exists only as long as it has member processes. For more info, refer to the docs.package main

import (
“log”
“os”
“os/exec”
“syscall”
“fmt”
“strconv”
“golang.org/x/sys/unix”
)

// This path was arbitrarily chosen
func getNetNsPath() string {
return “/tmp/net-ns”
}

// Utility function to create a directory on the host if not present already
func createDirsIfDontExist(dirs []string) error {
for _, dir := range dirs {
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.MkdirAll(dir, 0755); err != nil {
log.Printf(“Error creating directory: %vn”, err)
return err
}
}
}
return nil
}

// This is the “main” function you are looking for
// which just creates another network namespace on the Linux host
func setupNewNetworkNamespace(processID int) {
_ = createDirsIfDontExist([]string{getNetNsPath()})
nsMount := getNetNsPath() + “/” + strconv.Itoa(processID)
if _, err := syscall.Open(nsMount,
syscall.O_RDONLY|syscall.O_CREAT|syscall.O_EXCL,
0644); err != nil {
log.Fatalf(“Unable to open bind mount file: :%vn”, err)
}

// open the processes’s network namespace file, which is in /proc/self/ns/net.
// This is to save the fd reference of the current namespace before we unshare
// (so we can retrieve the fd value back below)
fd, err := syscall.Open(“/proc/self/ns/net”, syscall.O_RDONLY, 0)
defer syscall.Close(fd)
if err != nil {
log.Fatalf(“Unable to open: %vn”, err)
}

// This disassociates the current process with the namespace it is part of,
// creates a fresh (new) network namespace and sets it as the network namespace for the process.
if err := syscall.Unshare(syscall.CLONE_NEWNET); err != nil {
log.Fatalf(“Unshare system call failed: %vn”, err)
}

// bind mount the (new) network namespace special file of this process to a known file name,
// which is /tmp/net-ns/<container-id>
// This file can then anytime be used to refer to this network namespace.
// But also since in the next step with remove this process from this namespace, we want to retain
// (so that’s why it is bind-mounted to nsMount)
if err := syscall.Mount(“/proc/self/ns/net”, nsMount,
“bind”, syscall.MS_BIND, “”); err != nil {
log.Fatalf(“Mount system call failed: %vn”, err)
}

// sets the namespace of the current process back to the one specified by the file descriptor obtained earlier.
// syscall.CLONE_NEWNET flag indicates that it’s setting the network namespace.
if err := unix.Setns(fd, syscall.CLONE_NEWNET); err != nil {
log.Fatalf(“Setns system call failed: %vn”, err)
}
}

// This function adds the process into the new network namespace
func joinContainerNetworkNamespace(processID int) error {
nsMount := getNetNsPath() + “/” + strconv.Itoa(processID)

// get file descriptor of the network namespace file
fd, err := unix.Open(nsMount, unix.O_RDONLY, 0)
if err != nil {
log.Printf(“Unable to open: %vn”, err)
return err
}

// sets the namespace of the current process to the one specified by the file descriptor
if err := unix.Setns(fd, unix.CLONE_NEWNET); err != nil {
log.Printf(“Setns system call failed: %vn”, err)
return err
}

return nil
}

func main () {
processID := os.Getpid()
log.Printf(“Process ID: %dn”, processID)

path := fmt.Sprintf(“/proc/%d/ns/net”, processID)
out, err := exec.Command(“readlink”, path).Output(); if err != nil {
log.Fatalf(“Error reading namespace file: %vn”, err)
}
log.Printf(“Process is now in the current Namespace: %s”, string(out))

setupNewNetworkNamespace(processID)
joinContainerNetworkNamespace(processID)

out2, err := exec.Command(“readlink”, path).Output(); if err != nil {
log.Fatalf(“Error reading namespace file: %vn”, err)
}
log.Printf(“Process is now in the new Namespace: %s”, string(out2))
}⚠️ Note: This program intentionally only sets up the network namespace and doesn’t perform any other network-related configurations, such as setting up virtual interfaces to enable the process to communicate with the host and the internet.

Conclusion

In conclusion, this article has explored the creation of a network namespace and the addition of a process to it, illustrating fundamental concepts applicable to containerization. While focusing on the Network namespace in Go, future posts may delve into other namespaces. The provided code snippets, along with detailed comments, aim to enhance understanding of the underlying functionalities.

To stay current with the latest cloud technologies, make sure to subscribe to my weekly newsletter, Cloud Chirp. 🚀

Container Internals Series Part 2: Network Namespace was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.

​ Level Up Coding – Medium

about Infinite Loop Digital

We support businesses by identifying requirements and helping clients integrate AI seamlessly into their operations.

Gartner
Gartner Digital Workplace Summit Generative Al

GenAI sessions:

  • 4 Use Cases for Generative AI and ChatGPT in the Digital Workplace
  • How the Power of Generative AI Will Transform Knowledge Management
  • The Perils and Promises of Microsoft 365 Copilot
  • How to Be the Generative AI Champion Your CIO and Organization Need
  • How to Shift Organizational Culture Today to Embrace Generative AI Tomorrow
  • Mitigate the Risks of Generative AI by Enhancing Your Information Governance
  • Cultivate Essential Skills for Collaborating With Artificial Intelligence
  • Ask the Expert: Microsoft 365 Copilot
  • Generative AI Across Digital Workplace Markets
10 – 11 June 2024

London, U.K.