Documentation
¶
Overview ¶
Package uis (Userspace Internet Simulation) provides basic building blocks for userspace networking tests using gVisor.
The package models a virtual internet where multiple network stacks can communicate. It provides direct control over packet flow, leaving routing policy and network conditions to the caller.
The typical usage is to create a *Internet and use *Internet.NewStack to create two or more *Stack instances. The created instances are already configured for sending and receiving raw internet packets.
The Connector type is a stdlib-like dialer for IP literal endpoints only. The ListenConfig type is a stdlib-like listener config for IP literal endpoints only. Use these types to plug this package into higher-level code that expects the net package interfaces.
To route packets, you need to read packets using *Internet.InFlight. If you choose to forward the read packets, then you can deliver them to the right destination using *Internet.Deliver. We don't model L2 frames (we just move raw IP packets around) and we don't model multiple hops. These choices keep this package focused on fundamental primitives rather than full frameworks.
The *PCAPTrace type allows you to capture packets in flight in a PCAP format so that you can inspect what happened using tools such as wireshark.
Example (TcpDownloadIPv4) ¶
This example creates a client and the server and the client downloads a small number of bytes from the server.
package main
import (
"context"
"fmt"
"net/netip"
"os"
"sync"
"github.com/bassosimone/runtimex"
"github.com/bassosimone/uis"
)
func main() {
// create the internet instance.
ix := uis.NewInternet(uis.InternetOptionMaxInflight(256))
// create the server and client stacks
const mtu = uis.MTUJumbo
srv := runtimex.PanicOnError1(ix.NewStack(mtu, netip.MustParseAddr("10.0.0.1")))
defer srv.Close()
clnt := runtimex.PanicOnError1(ix.NewStack(mtu, netip.MustParseAddr("10.0.0.2")))
defer clnt.Close()
// create a context used by connector and listener
ctx := context.Background()
// run the server in the background
wg := &sync.WaitGroup{}
ready := make(chan struct{})
wg.Go(func() {
listenCfg := uis.NewListenConfig(srv)
listener := runtimex.PanicOnError1(listenCfg.Listen(ctx, "tcp", "10.0.0.1:80"))
close(ready)
conn := runtimex.PanicOnError1(listener.Accept())
message := []byte("Hello, world!\n")
_ = runtimex.PanicOnError1(conn.Write(message))
runtimex.PanicOnError0(conn.Close())
runtimex.PanicOnError0(listener.Close())
})
// run the client in the background
messagech := make(chan []byte, 1)
wg.Go(func() {
<-ready
connector := uis.NewConnector(clnt)
conn := runtimex.PanicOnError1(connector.DialContext(ctx, "tcp", "10.0.0.1:80"))
buffer := make([]byte, 1024)
count := runtimex.PanicOnError1(conn.Read(buffer))
messagech <- buffer[:count]
runtimex.PanicOnError0(conn.Close())
})
// know when both goroutines have stopped
stopped := make(chan struct{})
go func() {
wg.Wait()
close(stopped)
}()
// route and capture packets in the foreground
traceFile := runtimex.PanicOnError1(os.Create("tcpDownloadIPv4.pcap"))
trace := uis.NewPCAPTrace(traceFile, uis.MTUJumbo)
loop:
for {
select {
case frame := <-ix.InFlight():
trace.Dump(frame.Packet)
_ = ix.Deliver(frame)
case <-stopped:
break loop
}
}
runtimex.PanicOnError0(trace.Close())
// receive and print the server message
message := <-messagech
fmt.Printf("%s", string(message))
}
Output: Hello, world!
Example (UdpEchoIPv4) ¶
This example creates a client and server using UDP over IPv4. The server echoes back whatever it receives.
package main
import (
"context"
"fmt"
"net/netip"
"os"
"sync"
"github.com/bassosimone/runtimex"
"github.com/bassosimone/uis"
)
func main() {
// create the internet instance.
ix := uis.NewInternet(uis.InternetOptionMaxInflight(256))
// create the server and client stacks
const mtu = uis.MTUJumbo
srv := runtimex.PanicOnError1(ix.NewStack(mtu, netip.MustParseAddr("10.0.0.1")))
defer srv.Close()
clnt := runtimex.PanicOnError1(ix.NewStack(mtu, netip.MustParseAddr("10.0.0.2")))
defer clnt.Close()
// create a context used by connector and listener
ctx := context.Background()
// run the server in the background
wg := &sync.WaitGroup{}
ready := make(chan struct{})
wg.Go(func() {
listenCfg := uis.NewListenConfig(srv)
pconn := runtimex.PanicOnError1(listenCfg.ListenPacket(ctx, "udp", "10.0.0.1:53"))
defer pconn.Close()
close(ready)
buffer := make([]byte, 2048)
count, addr := runtimex.PanicOnError2(pconn.ReadFrom(buffer))
_ = runtimex.PanicOnError1(pconn.WriteTo(buffer[:count], addr))
})
// run the client in the background
messagech := make(chan []byte, 1)
wg.Go(func() {
<-ready
connector := uis.NewConnector(clnt)
conn := runtimex.PanicOnError1(connector.DialContext(ctx, "udp", "10.0.0.1:53"))
message := []byte("Hello, IPv4!\n")
_ = runtimex.PanicOnError1(conn.Write(message))
buffer := make([]byte, 1024)
count := runtimex.PanicOnError1(conn.Read(buffer))
messagech <- buffer[:count]
runtimex.PanicOnError0(conn.Close())
})
// know when both goroutines have stopped
stopped := make(chan struct{})
go func() {
wg.Wait()
close(stopped)
}()
// route and capture packets in the foreground
traceFile := runtimex.PanicOnError1(os.Create("udpEchoIPv4.pcap"))
trace := uis.NewPCAPTrace(traceFile, uis.MTUJumbo)
loop:
for {
select {
case frame := <-ix.InFlight():
trace.Dump(frame.Packet)
_ = ix.Deliver(frame)
case <-stopped:
break loop
}
}
runtimex.PanicOnError0(trace.Close())
// receive and print the echoed message
message := <-messagech
fmt.Printf("%s", string(message))
}
Output: Hello, IPv4!
Example (UdpEchoIPv6) ¶
This example creates a client and server using UDP over IPv6. The server echoes back whatever it receives.
package main
import (
"context"
"fmt"
"net/netip"
"os"
"sync"
"github.com/bassosimone/runtimex"
"github.com/bassosimone/uis"
)
func main() {
// create the internet instance.
ix := uis.NewInternet(uis.InternetOptionMaxInflight(256))
// create the server and client stacks
const mtu = uis.MTUJumbo
srv := runtimex.PanicOnError1(ix.NewStack(mtu, netip.MustParseAddr("2001:db8::1")))
defer srv.Close()
clnt := runtimex.PanicOnError1(ix.NewStack(mtu, netip.MustParseAddr("2001:db8::2")))
defer clnt.Close()
// create a context used by connector and listener
ctx := context.Background()
// run the server in the background
wg := &sync.WaitGroup{}
ready := make(chan struct{})
wg.Go(func() {
listenCfg := uis.NewListenConfig(srv)
pconn := runtimex.PanicOnError1(listenCfg.ListenPacket(ctx, "udp", "[2001:db8::1]:53"))
defer pconn.Close()
close(ready)
buffer := make([]byte, 2048)
count, addr := runtimex.PanicOnError2(pconn.ReadFrom(buffer))
_ = runtimex.PanicOnError1(pconn.WriteTo(buffer[:count], addr))
})
// run the client in the background
messagech := make(chan []byte, 1)
wg.Go(func() {
<-ready
connector := uis.NewConnector(clnt)
conn := runtimex.PanicOnError1(connector.DialContext(ctx, "udp", "[2001:db8::1]:53"))
message := []byte("Hello, IPv6!\n")
_ = runtimex.PanicOnError1(conn.Write(message))
buffer := make([]byte, 1024)
count := runtimex.PanicOnError1(conn.Read(buffer))
messagech <- buffer[:count]
runtimex.PanicOnError0(conn.Close())
})
// know when both goroutines have stopped
stopped := make(chan struct{})
go func() {
wg.Wait()
close(stopped)
}()
// route and capture packets in the foreground
traceFile := runtimex.PanicOnError1(os.Create("udpEchoIPv6.pcap"))
trace := uis.NewPCAPTrace(traceFile, uis.MTUJumbo)
loop:
for {
select {
case frame := <-ix.InFlight():
trace.Dump(frame.Packet)
_ = ix.Deliver(frame)
case <-stopped:
break loop
}
}
runtimex.PanicOnError0(trace.Close())
// receive and print the echoed message
message := <-messagech
fmt.Printf("%s", string(message))
}
Output: Hello, IPv6!
Index ¶
- Constants
- type Connector
- type Internet
- type InternetOption
- type ListenConfig
- type PCAPTrace
- type PCAPTraceOption
- type Stack
- func (sx *Stack) Close()
- func (sx *Stack) DialTCP(ctx context.Context, addr netip.AddrPort) (*gonet.TCPConn, error)
- func (sx *Stack) DialUDP(addr netip.AddrPort) (*gonet.UDPConn, error)
- func (sx *Stack) ListenTCP(addr netip.AddrPort) (*gonet.TCPListener, error)
- func (sx *Stack) ListenUDP(addr netip.AddrPort) (*gonet.UDPConn, error)
- type VNIC
- func (n *VNIC) ARPHardwareType() header.ARPHardwareType
- func (n *VNIC) AddHeader(pbuf *stack.PacketBuffer)
- func (n *VNIC) Attach(disp stack.NetworkDispatcher)
- func (n *VNIC) Capabilities() stack.LinkEndpointCapabilities
- func (n *VNIC) Close()
- func (n *VNIC) InjectFrame(frame VNICFrame) bool
- func (n *VNIC) IsAttached() bool
- func (n *VNIC) LinkAddress() tcpip.LinkAddress
- func (n *VNIC) MTU() uint32
- func (n *VNIC) MaxHeaderLength() uint16
- func (n *VNIC) ParseHeader(pbuf *stack.PacketBuffer) bool
- func (n *VNIC) SetLinkAddress(addr tcpip.LinkAddress)
- func (n *VNIC) SetMTU(mtu uint32)
- func (n *VNIC) SetOnCloseAction(action func())
- func (n *VNIC) Wait()
- func (n *VNIC) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error)
- type VNICFrame
- type VNICNetwork
Examples ¶
Constants ¶
const ( // MTUEthernet is the MTU used by Ethernet. MTUEthernet = 1500 // MTUMinimumIPv6 is the minimum MTU required by IPv6. MTUMinimumIPv6 = 1280 // MTUJumo is the MTU used by jumbo frames. MTUJumbo = 9000 )
Enumerate common MTU values.
const DefaultMaxInflight = 1024
DefaultMaxInflight is the default maximum number of inflight packets.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Connector ¶
type Connector struct {
// contains filtered or unexported fields
}
Connector allows to dial net.Conn connections pretty much like *net.Dialer except that here we use a *Stack implementation as the networking backend.
The zero value is invalid. Construct using NewConnector.
Only IP literal endpoints are supported. Dialing a hostname will fail.
func NewConnector ¶
NewConnector creates a new *Connector instance.
type Internet ¶
type Internet struct {
// contains filtered or unexported fields
}
Internet models the entire internet.
Construct using NewInternet.
func NewInternet ¶
func NewInternet(options ...InternetOption) *Internet
NewInternet creates and returns a new *Internet instance.
func (*Internet) AddRoute ¶
AddRoute registers the given *VNIC to have the given addresses such that it is possible to route packets to it.
This method fails if the claimed addresses are already in use.
func (*Internet) Deliver ¶
Deliver routes a frame to the appropriate host based on destination IP.
It parses the destination IP from the raw packet, looks up the registered host for that address, and injects the frame into that host stack.
Returns false if the destination IP cannot be parsed, is not routable (no host registered for that address), or injection fails.
func (*Internet) NewStack ¶
NewStack creates and attaches a *Stack to the *Internet.
The mtu parameter sets the MTU in bytes. Common values:
- MTUEthernet - MTUMinimumIPv6 - MTUJumbo
The addrs argument contains the IPv4/IPv6 addresses to configure.
This method implementation combines:
1. *Internet.NewVNIC to create a virtual NIC
2. NewStack to create a *Stack associated to a virtual NIC
3. [*Internet.AddrRoute] to create the return routes
type InternetOption ¶
type InternetOption func(cfg *internetConfig)
InternetOption is an option for NewInternet.
func InternetOptionMaxInflight ¶
func InternetOptionMaxInflight(max int) InternetOption
InternetOptionMaxInflight sets the maximum number of inflight packets.
The default is DefaultMaxInflight packets. When the channel is full, additional packets are silently dropped.
type ListenConfig ¶
type ListenConfig struct {
// contains filtered or unexported fields
}
ListenConfig allows to listen pretty much like *net.ListenConfig except that here we use a *Stack implementation as the networking backend.
The zero value is invalid. Construct using NewListenConfig.
Only IP literal endpoints are supported. Listening on a hostname will fail.
func NewListenConfig ¶
func NewListenConfig(stack *Stack) *ListenConfig
NewListenConfig creates a new *ListenConfig instance.
func (*ListenConfig) ListenPacket ¶
func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (net.PacketConn, error)
ListenPacket creates a listening packet conn.
type PCAPTrace ¶
type PCAPTrace struct {
// contains filtered or unexported fields
}
PCAPTrace is an open PCAP trace.
func NewPCAPTrace ¶
func NewPCAPTrace(wc io.WriteCloser, snapSize uint16, options ...PCAPTraceOption) *PCAPTrace
NewPCAPTrace creates a new *PCAPTrace instance.
Takes ownership of the io.WriteCloser and ensures the file is closed and flushed when you invoke the *PCAPTrace.Close method.
We recommend using a large snapshot size for inspecting the full packets that are exchanged by the *Stack you are using in your tests.
func (*PCAPTrace) Close ¶
Close interrupts the background goroutine and waits for it to join before closing the packet capture file.
type PCAPTraceOption ¶
type PCAPTraceOption func(cfg *pcapTraceConfig)
PCAPTraceOption is an option for NewPCAPTrace.
func PCAPTraceOptionBuffer ¶
func PCAPTraceOptionBuffer(bufferSize int) PCAPTraceOption
PCAPTraceOptionBuffer sets the buffer size for the internal packet channel.
The default is 4096 snapshots. When the buffer is full, new snapshots are dropped and counted using *PCAPTrace.Dropped.
A zero or negative value is silently ignored.
type Stack ¶
Stack is a wrapper for *stack.Stack allowing basic network operations with gVisor's TCP and UDP conns.
Construct using NewStack.
func NewStack ¶
func NewStack(vnic stack.LinkEndpoint, addrs ...netip.Addr) *Stack
NewStack creates a new *Stack using a stack.LinkEndpoint.
func (*Stack) Close ¶
func (sx *Stack) Close()
Close shuts down the stack and waits for the NIC teardown to finish.
func (*Stack) DialTCP ¶
DialTCP establishes a new *gonet.TCPConn.
func (*Stack) DialUDP ¶
DialUDP creates a new connected *gonet.UDPConn.
func (*Stack) ListenTCP ¶
ListenTCP creates a new *gonet.TCPListener.
type VNIC ¶
type VNIC struct {
// contains filtered or unexported fields
}
VNIC models a virtual NIC. This type is compatible with stack.Stack because it implements the stack.LinkEndpoint interface.
To send packets, stack.Stack invokes *VNIC.WritePackets, which, in turn, invokes the attached VNICNetwork SendFrame.
To receive packets, the attached VNICNetwork invokes *VNIC.InjectFrame, which invokes the stack.NetworkDispatcher do dispatch it.
The stack.Stack configures the *VNIC stack.NetworkDispatcher used for dispatching via the *VNIC.Attach method.
Construct using NewVNIC.
func NewVNIC ¶
func NewVNIC(mtu uint32, network VNICNetwork) *VNIC
NewVNIC creates a new *VNIC instance.
The mtu parameter sets the MTU in bytes. Common values:
- MTUEthernet - MTUMinimumIPv6 - MTUJumbo
The network parameter is the *VNICNetwork to use.
func (*VNIC) ARPHardwareType ¶
func (n *VNIC) ARPHardwareType() header.ARPHardwareType
ARPHardwareType implements stack.LinkEndpoint.
func (*VNIC) AddHeader ¶
func (n *VNIC) AddHeader(pbuf *stack.PacketBuffer)
AddHeader implements stack.LinkEndpoint.
func (*VNIC) Attach ¶
func (n *VNIC) Attach(disp stack.NetworkDispatcher)
Attach implements stack.LinkEndpoint.
func (*VNIC) Capabilities ¶
func (n *VNIC) Capabilities() stack.LinkEndpointCapabilities
Capabilities implements stack.LinkEndpoint.
func (*VNIC) InjectFrame ¶
InjectFrame injects an inbound raw IPv4/IPv6 packet into the stack.
func (*VNIC) IsAttached ¶
IsAttached implements stack.LinkEndpoint.
func (*VNIC) LinkAddress ¶
func (n *VNIC) LinkAddress() tcpip.LinkAddress
LinkAddress implements stack.LinkEndpoint.
func (*VNIC) MaxHeaderLength ¶
MaxHeaderLength implements stack.LinkEndpoint.
func (*VNIC) ParseHeader ¶
func (n *VNIC) ParseHeader(pbuf *stack.PacketBuffer) bool
ParseHeader implements stack.LinkEndpoint.
func (*VNIC) SetLinkAddress ¶
func (n *VNIC) SetLinkAddress(addr tcpip.LinkAddress)
SetLinkAddress implements stack.LinkEndpoint.
func (*VNIC) SetOnCloseAction ¶
func (n *VNIC) SetOnCloseAction(action func())
SetOnCloseAction implements stack.LinkEndpoint.
func (*VNIC) WritePackets ¶
WritePackets implements stack.LinkEndpoint.
type VNICFrame ¶
type VNICFrame struct {
// Packet contains a raw IP packet (IPv4 or IPv6).
Packet []byte
}
VNICFrame models a virtual link-layer frame without addressing.
type VNICNetwork ¶
VNICNetwork models the network that a VNIC sends packets to.
The *Internet implements this interface.