Skip to content

NullNet-ai/nullnet

Repository files navigation

nullnet

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.

Layout

.
├── 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

Prerequisites

  • 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.

Usage

nullnet-server

  • 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 called my-app, create services/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_dependencies is a linear dep chain walked when the service is reached via a Proxy RPC 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 a BackendTrigger RPC 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


nullnet-proxy

  • set environment variables (in members/nullnet-proxy/.env; set CONTROL_SERVICE_ADDR to the IP of nullnet-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


nullnet-client

  • set environment variables (in members/nullnet-client/.env; set CONTROL_SERVICE_ADDR to the IP of nullnet-server, ETH_NAME to 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 its stack (which must match a services/<stack>.toml on 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
    

About

Nullnet control plane

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors