Adding network support

← Back to Index

Adding network support

Our container runtime creates "containers", great, and they can even connect to the internet, you can try this by running:

$ sudo ./bin/devoxx-docker run alpine /bin/sh
# ping 1.1.1.1

But what happens if we try and ping google.com for example? Of course, it doesn't work, the base alpine image doesn't contain the /etc/resolv.conf file, we don't know where to look for when we want to find the IP address of a host.

A second issue is that, while the container has networking, it's using the host network stack, and we don't want that, we want to isolate the processes in our container as much as we can. This includes the network stack.

There are many ways we could setup networking for containers, in this workshop we will try and keep it simple (but buckle up, nothing is easy when it comes to networking). We will create a pair of virtual Ethernet devices (veth) and link them.

While old, outdated and deprecated, we will be using iptables in this exercice. If you feel lucky you could also implement this functionnality with nftables.

Step 1: create the network namespace

  1. Add network namespace isolation in your main container creation code:
cmd.SysProcAttr = &syscall.SysProcAttr{
	// Add CLONE_NEWNET to your existing clone flags
	// This creates a new network namespace for the container
}

After adding the new network namespace flag, can we ping anything from inside the container?

Step 2: create the veth pair

Of course, when we created a new network namespace for our container, we lost all connectivity! This is normal and expected, we need to setup everything manually on the host and inside the container so that we can have network connectivity.

This setup should be done in the parent process before starting the child process:

func SetupVeth(pid int) error {
	// TODO: Use "ip link" commands to:
	// 1. Create a veth pair (veth0 and veth1)
	// 2. Move veth1 to the container's network namespace
	// 3. Configure veth0 in the host namespace
	// 4. Set up NAT rules using iptables

	return nil
}
Hint (veth)

This command creates a veth pair. A kind of virtual network cable with two ends

ip link add veth0 type veth peer name veth1
Hint (move to network namespace)

This command moves a veth to the network namespace of a process

ip link set veth1 netns <PID>
Hint (assign ip address)

Assign an IP address and subnet to our veth

ip addr add 10.0.0.1/24 dev veth0
Hint (NAT rule)
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j MASQUERADE
  • -t nat is the Network Address Translation table, it's used for rewriting packet addressses
  • -A POSTROUTING adds the rule to this chain, which alters the packets just before the packet leaves the system
  • -s 10.0.0.0/24 matches packets with a source IP in this subnet
  • -j MASQUERADE means it should masquerade the packet: replace its source IP with the host's outgoing interface IP

Create a cleanup function to remove the network configuration:

func CleanupVeth(vethName string) error {
	// TODO: Clean up:
	// 1. Remove NAT rules
	// 2. Delete the veth pair

	return nil
}

Step 3: configure container networking

We need to do a couple things before getting our networking connection:

This setup should be done in the child process after the namespace setup but before executing the command:

func SetupContainerNetworking() error {
	// TODO: Inside the container:
	// 1. Assign IP address to the container interface (veth1)
	// 2. Bring up the interface
	// 3. Configure the loopback interface
	// 4. Set up the default route

	return nil
}
Loopback interface

While this isn't really needed you can run this command inside the container

ip link set lo up

This sets up the loopback interface and makes it possible to ping 127.0.0.1 for example

Default gateway
ip route add default via 10.0.0.1

Step 4: DNS

It's always DNS, right?

We should hopefully have networking working now but one last little bit remains, we can't ping google.com, we need to create the /etc/resolv.conf file inside the rootfs of the container.

Step 5: test

  1. Test your network implementation:
$ sudo ./bin/devoxx-docker run alpine /bin/sh
# ping 1.1.1.1      # Should reach internet
# ping google.com   # Should resolve and reach

Step 7: Troubleshooting

If you're having trouble with the networking:

  1. Check that all interfaces are properly created and configured:

    # ip addr
    
  2. Check routing:

    # ip route
    
  3. Check DNS configuration:

    # cat /etc/resolv.conf
    
  4. Try to trace the network path:

    # traceroute google.com
    

Additional Resources

Previous step Next step