Gont

A Go network testing toolkit

Introduction

What does Gont do?

  • Software-defined virtual networking for testing
  • Define hosts, switches, routers in a single host
  • Reentrancy
  • Reproducibility
  • Inspired by Mininet

Mininet

Mininet creates a realistic virtual network, running real kernel, switch and application code, on a single machine (VM, cloud or native)

-- mininet.org

Mininet

  • Written in Python 2
  • Lacking active maintainer
  • Focus on SDN: OpenFlow controllers
  • No SSL cert on homepage?!
We need something better

Why?

  • Describe network toplogies quickly in code
  • Automate contruction of complex topolgies
  • Unit / CI testing
  • Parallel test execution
  • Example use-cases
    • VPN / network tools development
    • SDN Openflow controller development
    • Wireguard Interactive Connectivity Establishment (WICE)

A little detour:

Namespaces

What are Linux namespaces?

They..

  • partition kernel resources
    from a process-perspective.
  • power most of Linux containerization tools.
  • appear in many Linux subsystems.
  • are used by many sandboxing solutions in browsers.

Available Linux namespaces

  • mnt: Mounts
  • pid: Process IDs
  • net: Networking
  • ipc: Interproces Communication
  • uts: Unix Timesharing (hostname)
  • user: User identification & privileges
  • cgroup: Process Control Groups
  • time: System time

Buts what exactly is a namespace?

  • Can be identified by a file descriptor
  • A namespace can have multiple processes assigned to it
  • It lives as long as there is still at least one remaining process
  • Child processes inherit parent namespaces

How do I create a namespace?

unshare(2)


							func main() {
								err := syscall.Unshare(syscall.CLONE_NEWNS);
							}
						

How do I create a namespace?


							static int child(void *arg) {
								struct utsname uts;

								sethostname(arg, "ernie")

								uname(&uts)
								printf("nodename in child:  %s\n", uts.nodename);

								return 0;
							}

							int main() {
								struct utsname uts;

								/* Allocate stack for child */
								char *stack = malloc(STACK_SIZE);
								if (stack == NULL)
									return -1;

								/* Start new kernel task in new UTS namespace */
								pid_t child_pid = clone(child, stack + STACK_SIZE, CLONE_NEWUTS | SIGCHLD, NULL);

								/* Output hostname */
								uname(&uts)
								printf("nodename in parent:  %s\n", uts.nodename);
							}
						

How can I share a namespace with other processes?

Joining namespace of another process by /proc/{pid}/ns/*


							fd := syscall.Open("/proc/1234/ns/uts", syscall.O_RDONLY);
							
							err := unix.Setns(fd, syscall.CLONE_NEWUTS);
						
Note: Can only set a single namespace per netns(2) invocation.

Joining namespace of another process by pidfd_open(2)


							pid_t pid = 1234;
							int fd = pidfd_open(pid, 0);
							
							setns(fd, CLONE_NEWUTS | CLONE_NEWNET);
						
Note: Can only set a multiple namespaces per netns(2) invocation.

Persisting namespaces


							err := syscall.Mount("/proc/self/ns/uts",
							                     "/home/acs/my_uts_namespace", "", syscall.MS_BIND, nil);
						
In other processs

							fd := syscall.Open("/home/acs/my_uts_namespace", syscall.O_RDONLY);
							
							err := unix.Setns(fd, syscall.CLONE_NEWUTS);
						

Back to Gont...

First some definitions

network
A set of nodes and links between them
node
A network namespace which represents any device in the network
link
A pair of veth network interfaces which are associated to separate nodes
interface
an endpoint with one or more assigned IP addresses
switch
A node containing a Linux layer 2 bridge and attached interfaces
host
A node with several configured interfaces
router
A host with IP forwarding enabled
nat
A special type of router which implements network address translation between a set of south- and north-bound interfaces

Relationships

Gont and Namespaces

  • for each node:
    • a separate network namespace
    • a separate uts namespace
      • for a unique hostname within each node
  • for each network:
    • a separate mount namespace
      • for a unique /etc/hosts files between networks

Examples: show my some code!

Two directly connected hosts


							n, _ := g.NewNetwork("mynet")

							h1, _ := n.AddHost("h1")
							h2, _ := n.AddHost("h2")

							n.AddLink(
								o.Interface("eth0", h1, o.AddressIPv4(10, 0, 0, 1, 24)),
								o.Interface("eth0", h2, o.AddressIPv4(10, 0, 0, 2, 24)),
							)

							h1.Ping(h2)
						

Running things inside a node


							// Run a simple process synchronously
							h1.Run("ping", "h2")

							// And asynchronously
							_, _, e, _ := h1.Start("ping", "h2")
							
							time.Sleep(5 * time.Second)

							e.Process.Kill()
							e.Wait()

							// Call a function inside a network namespace
							h1.RunFunc(func() {
								r := http.Get("http://h2:8080")
								io.Copy(os.Stdout, r.Body)
							})
						

Lets add a switch


							sw, _ := n.AddSwitch("sw")

							h1, _ := n.AddHost("h1", o.Interface("eth0", sw,
							                    o.AddressIPv4(10, 0, 0, 1, 24)))
							h2, _ := n.AddHost("h2", o.Interface("eth0", sw,
							                    o.AddressIPv4(10, 0, 0, 2, 24)))

							h1.Ping(h2)
						

How about a router?


							sw1, _ := n.AddSwitch("sw1")
							sw2, _ := n.AddSwitch("sw2")

							h1, _ := n.AddHost("h1", o.Interface("eth0", sw1,
							                    o.AddressIPv4(10, 0, 0, 2, 24)))
							h2, _ := n.AddHost("h2", o.Interface("eth0", sw2,
							                    o.AddressIPv4(10, 0, 1, 2, 24)))

							n.AddRouter("r1",
								o.Interface("eth0", sw, o.AddressIPv4(10, 0, 0, 1, 24)),
								o.Interface("eth1", sw, o.AddressIPv4(10, 0, 1, 1, 24))
							)

							h1.Ping(h2)
						

Lets do some evil NATing


							sw1, _ := n.AddSwitch("sw1")
							sw2, _ := n.AddSwitch("sw2")

							h1, _ := n.AddHost("h1", o.Interface("eth0", sw1,
							                    o.AddressIPv4(10, 0, 0, 2, 24)))
							h2, _ := n.AddHost("h2", o.Interface("eth0", sw2,
							                    o.AddressIPv4(10, 0, 1, 2, 24)))

							n.AddNAT("n1",
								o.Interface("eth0", sw, o.SouthBound,
								             o.AddressIPv4(10, 0, 0, 1, 24)),
								o.Interface("eth1", sw, o.NorthBound,
								             o.AddressIPv4(10, 0, 1, 1, 24))
							)

							h1.Ping(h2)
						

How about a whole chain of routers?


							var firstSwitch *g.Switch = n.AddSwitch("sw0")
							var lastSwitch  *g.Switch = nil

							for i := 1; i < 100; i++ {
								swName := fmt.Printf("sw%d", i)
								rtrName := fmt.Printf("r%d", i)

								newSwitch, _ := n.AddSwitch(swName)

								n.AddRouter(rtrName,
									o.Interface("eth0", lastSwitch,
									             o.AddressIPv4(10, 0, 0, 1, 24)),
									o.Interface("eth1", newSwitch,
									             o.AddressIPv4(10, 0, 1, 1, 24))
								)

								lastSwitch = newSwitch
							}

							h1, _ := n.AddHost("h1", o.Interface("eth0", firstSwitch,
							                    o.AddressIPv4(10, 0, 0, 2, 24)))
							h2, _ := n.AddHost("h2", o.Interface("eth0", lastSwitch, 
							                    o.AddressIPv4(10, 0, 1, 2, 24)))
							
							h1.Ping(h2)
						

A topology factory (WIP)


							createHost := func(pos int) (*g.Host. error) {
								return n.AddHost(fmt.Sprintf("h%d", pos))
							}

							linkHosts := func(a, b *g.Node) error {
								_, err := n.AddRouter(fmt.Sprintf("r%d", pos),
									o.Interface("eth0", a, o.AddressIPv4(10, 0, 0, a.Position, 24),
									o.Interface("eth1", b, o.AddressIPv4(10, 0, 0, b.Position, 24)
								)
								return err
							}

							topo.Linear(n, 100, createHost, linkHosts)

							n.Nodes["h0"].Traceroute(n.Nodes["h99"])
						

CLI utility

CLI Example

Make network persistent


							n, _ := g.NewNetwork("mynet", o.Persistent(true))

							/* ... */
						

Introspect network after creation with gontc


							$ gontc list
							mynet

							$ gontc list mynet
							mynet/h1
							mynet/h2

							$ gontc exec mynet/h1 hostname
							h1.mynet.gont

							$ gontc shell mynet/h1
							$ mynet/h1: ip address show
						

CLI Usage


							Usage: gontc [flags] <command>

								Supported <commands> are:
							   
								  identify                                   return the network and node name if gontc
								                                             is executed within a network namespace
								  shell [<network>/]<node>       get an interactive shell inside <node>
								  exec [<network>/]<node> <command> [args]   executes a <command> in the
								                                             namespace of <node> with optional [args]
								  list [<network>]                           list all active Gont networks or nodes
								                                             of a given network
								  clean [<network>]                          removes the all or just the
								                                             specified Gont network
								  help                                       show this usage information
								  version                                    shows the version of Gont
							   
							   Example:
							   
								  gontc exec zorn/h1 ping h2
							   
							   Gont - The Go network tester
								  Author Steffen Vogel <post@steffenvogel>
						

How do I use it?

Gont ...

Requirements

  • Go 1.17
  • A moderate recent Linux kernel (>= 4.9)
    • mnt and net namespace support
  • root access / NET_ADMIN caps
  • traceroute userspace tool

Roadmap

  • Use pure Go implementation of traceroute
  • More topology factories
  • Design a nice logo
  • A Graphviz output for topology visualization
  • Automatic address assignment
  • Parser for network topologies from Dot, YAML or JSON description.

Dot example


									digraph D {
										/* network options */
										persistent = true

										/* nodes */
										h1 [type=host, exec="ping h2"]
										h2 [type=host]
										r1 [type=router]
									  
										/* links */
										h1 -> r1 [address="10.0.0.1/24",
										          mtu=9000]
										h2 -> r1 [address="10.0.0.2/24",
										          mtu=9000]
									}
								

Thanks for your attention

Steffen Vogel

@stv0g, stv0g@0l.de

EONERC Logo
European Flag

The development of Gont has been supported by the ERIGrid 2.0 project of the H2020 Programme under Grant Agreement No. 870620.