Nullnet is a gRPC-based control plane that lets clients on different hosts expose services to each other on-demand, building the required network infrastructure (VLAN / VXLAN) only when a service is actually requested and tearing it down when it is no longer needed.
This repository is a Cargo workspace holding the three binaries that make up the architecture plus the shared gRPC interface.
.
├── members/
│ ├── nullnet-client/ # runs on each host, exposes local services to the control plane
│ ├── nullnet-server/ # control plane: orchestrates VLAN/VXLAN setup and tears them down
│ ├── nullnet-proxy/ # ingress proxy: maps `service_name:80` requests to the right host
│ └── nullnet-grpc-lib/ # shared gRPC interface + generated types (proto + build.rs live here)
├── ebpf/ # eBPF program loaded by nullnet-client (nightly toolchain)
└── xtask/ # builds the eBPF program + the client userspace
- install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
The repository should be cloned under /root so the provided setup-*.sh scripts and
.service units work without changes.
-
set environment variables (in
members/nullnet-server/.env)NET_TYPE=VXLAN TIMEOUT=0 -
service configuration is split per stack — one TOML file per stack under
members/nullnet-server/services/. The filename (minus.toml) is the stack name. For example, to define a stack calledmy-app, createservices/my-app.toml:[[services]] name = "color.com" timeout = 0 proxy_dependencies = ["fs.color.com"] [[services.triggers]] port = 5555 chain = ["ts.color.com"] [[services]] name = "fs.color.com" ... -
proxy_dependenciesis a linear dep chain walked when the service is reached via aProxyRPC from nullnet-proxy -
each
[[services.triggers]]block pairs a port observed on the initiator's host with a linear chain walked when the service is reached via aBackendTriggerRPC from nullnet-client (one chain per port) -
service names are unique within a stack; dependency chains stay intra-stack. Service names may be reused across different stacks
-
run the project as a daemon (from the repo root)
./setup-server.sh -
the server regularly renders one Graphviz file per stack under
members/nullnet-server/graphs/<stack>.dot
-
set environment variables (in
members/nullnet-proxy/.env; setCONTROL_SERVICE_ADDRto the IP ofnullnet-server)CONTROL_SERVICE_ADDR=192.168.1.100 CONTROL_SERVICE_PORT=50051 -
run the project as a daemon (from the repo root)
./setup-proxy.sh -
the proxy will run on port 80 and receive requests in the form
service_name:80
-
set environment variables (in
members/nullnet-client/.env; setCONTROL_SERVICE_ADDRto the IP ofnullnet-server,ETH_NAMEto the ethernet interface to monitor)CONTROL_SERVICE_ADDR=192.168.1.100 CONTROL_SERVICE_PORT=50051 ETH_NAME=ens18 -
service configuration must be stored at
members/nullnet-client/services.toml. Each entry must declare itsstack(which must match aservices/<stack>.tomlon the server, otherwise the declaration is dropped):# services = [] # use this if you don't want to declare any service [[services]] name = "color.com" port = 3001 docker_container = "stack-name_container-name" # should correspond to the label "com.docker.swarm.service.name" stack = "my-app" [[services]] ... -
run the project as a daemon (from the repo root)
./setup-client.sh