RemoteNetstack provides utilities for running userspace network stacks using a data link layer implementation that only needs to implement the io.Reader
and io.Writer
interfaces. This single abstraction allows for interesting functionality, like the ability to dial through an existing connection, and out of a userspace networking stack located on another physical machine, effectively acting as a remote network interface. Think about it like TCP tunneling, except at layer 2 rather than layer 4.
This project maintains the core primitive that makes this work netstack.Endpoint
as well as some other useful utilities.
Some primitives in this project aim to be un-opinionated in order to be flexible at the cost of requiring a bit more manual plumbing to implement such as netstack.Endpoint
, while other primitives such as vni.Interface
handle all the netstack plumbing for you, but are harder to configure.
This project is extremely experimental. While most of the heavy lifting is being performed by production-ready projects like gvisor's netstack, and libp2p, the APIs exposed in this project will change until the first major release.
The following example demonstrates how to use the virtual network interface (VNI) to dial through an entrance netstack and out through an exit netstack. The link layer in this example is an in-memory pipe (net.Pipe
). In this example we're making HTTP requests (using a custom dialer that dials over the userspace networking stack) through the entrance networking stack, and the HTTP request is forwarded to the 192.168.1.1 IP address by the exit networking stack.
In this example both the VNIs are running in memory, however, given a LinkLayer
between them, these VNIs can be run on completely different machines.
package main
import (
"fmt"
netstackhttp "github.com/clarkmcc/remotenetstack/netstack/http"
"github.com/clarkmcc/remotenetstack/netstack/vni"
"io"
"net"
"net/http"
)
func main() {
// This is the IP address of some device on your local network. In this case, this
// is the IP address to my router. We're going to connect to this from the entrance
// virtual network interface, and out to the local network through the second virtual
// network interface.
ipAddress := "192.168.1.1"
// Set up an in-memory pipe. Packets sent to the entrance interface will flow
// through this pipe and exit the exit interface.
l1, l2 := net.Pipe()
// Set up the entrance interface
entrance, _ := vni.New(vni.Config{
Mode: vni.Entrance,
LinkLayer: l1,
})
// Set up the exit interface.
exit, _ := vni.New(vni.Config{
Mode: vni.Exit,
LinkLayer: l2,
})
exit.ExposeRoutes([]string{
"192.168.1.1/32",
})
// Get a new http.Client that dials using the netstack
client := netstackhttp.GetClient(entrance.Stack, 1)
req, _ := http.NewRequest(http.MethodGet, "http://"+ipAddress, nil)
res, _ := client.Do(req)
b, _ := io.ReadAll(res.Body)
fmt.Println(string(b))
}
The following data-link layer implementations are provided by this project. Other data-link layers that are coming-soon:
It's very simple to attach a userspace netstack to an existing libp2p host. The following example is not a fully-working example, but does show the basic idea. For a fully-working example, see examples/libp2p/main.go
// Create a netstack and channel endpoint
s := stack.New(stack.Options{})
e := channel.New(128, 1024, "")
s.CreateNIC(1, e)
// Create a libp2p host
host, err := libp2p.New()
if err != nil {
panic(err)
}
// Initialize the p2p netstack protocol. This effectively attaches the netstack
// above to the libp2p host. Packets sent to this libp2p host using the appropriate
// protocol will be forwarded and handled by the netstack.
_, err = transportp2p.New(host, e)
if err != nil {
panic(err)
}
This projects is built on, or was inspired by the work in these great projects: