discover · compare · collections · requirements · contribute · feed · watching · stats · help with your tokens
Yacin1102/Knoten · source
Snapshot 0 · 2026-11-04T12:47:28Z · Go · MIT
Content fingerprint
24fd6d5ee47dff1c5895adeef3295554e4b739c39fd4d0116ad3fe7d16adee65GitHub description at capture
Connect everything with direct, encrypted WireGuard tunnels. Self-hostable, with no central traffic point.
README at capture
<p align="center">
<img src="./assets/Knoten_README_banner.png" alt="Knoten Banner" width="100%">
</p>
<p align="center">
<a href="#license"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
<img alt="Go 1.26+" src="https://img.shields.io/badge/go-1.26%2B-00ADD8.svg?logo=go&logoColor=white">
<img alt="Platform: Linux" src="https://img.shields.io/badge/platform-linux-lightgrey.svg?logo=linux&logoColor=white">
<img alt="Version: 0.2.0-alpha" src="https://img.shields.io/badge/version-0.3.0--alpha-orange.svg">
<img alt="Status: alpha" src="https://img.shields.io/badge/status-alpha-orange.svg">
<img alt="Built on WireGuard" src="https://img.shields.io/badge/built%20on-WireGuard-88171A.svg">
</p>
---
Knoten connects your machines (servers, cloud instances, VMs, laptops) over **direct, end-to-end encrypted WireGuard tunnels**, instead of funnelling every packet through one central VPN server.
A coordination server may exist, but it only handles **discovery, identity, and address assignment**. It never carries your data.
<p align="center">
<img src="./assets/Knoten_demo.gif" alt="Knoten_demo.gif">
</p>
*This demo was recorded with the previous version (`v0.2.0-alpha`). Using Knoten works the same way; the change is under the hood: `meshd` used to write a WireGuard config file and hand it to the kernel module through `wg` and `wg-quick`, and now it runs WireGuard itself through an embedded [wireguard-go](https://pkg.go.dev/golang.zx2c4.com/wireguard). Some log lines differ, and stopping `meshd` now removes the tunnel. You can still check the tunnel with `sudo wg show knoten-wg` if `wireguard-tools` is installed.*
> [!NOTE]
> **Ongoing development**
> **Peer memory and unified config file:** `meshd` will remember the last peer list it received, so a restarted node has its peers right away, even while the coordination server is unreachable. Standalone and coordinated mode will also merge into a single configuration file.
> [!NOTE]
> Status: **Alpha** (latest version `v0.3.0-alpha`)
>
> **Knoten is still in its alpha phase.** The current working version runs: a coordination server, a node daemon, automatic key generation, IP address management, and live tunnel updates.
> **This is a student-driven project under active development**. Command-line flags, the HTTP API, and on-disk formats may still change between versions, without a compatibility guarantee.
> [!IMPORTANT]
> **Knoten does not currently support nodes behind Network Address Translation (NAT)**. All nodes must have directly reachable IP addresses, whether static or dynamic.
> [!WARNING]
> **`meshd` does not remember its peers yet.** The tunnel lives inside `meshd`, so every restart builds it from scratch with no peers, until the next successful sync with the coordination server. If the server is unreachable at that moment, the node stays cut off from the mesh until it comes back. The upcoming release will add peer memory to fix this.
## Contents
- [Why Knoten](#why-knoten)
- [How it works](#how-it-works)
- [Deployment modes](#deployment-modes)
- [Requirements](#requirements)
- [Quick start](#quick-start)
- [Coordinated mode](#coordinated-mode)
- [Standalone mode](#standalone-mode)
- [Command-line reference](#command-line-reference)
- [Files and paths](#files-and-paths)
- [Security model](#security-model)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
## Why Knoten
**Direct peer-to-peer by default.** Traffic goes straight from one machine to another. The coordination layer is consulted only when something needs to change: who exists, who is allowed in, and which address each machine holds.
**Resilient by design.** Tunnels never depend on the coordination layer to stay alive. Existing connections are sustained by WireGuard itself, so losing a coordinator restricts network *administration*, not connectivity. This assumes relatively stable peers; radical, simultaneous changes across the fleet cannot be reconciled until coordination is restored.
**Access is a lease, never a possession.** Every grant is designed to carry an expiry and persist only through renewal. Revocation is simply expiry-now, so removing access is never a special operation, and abandoned access decays to nothing on its own. *(Design principle not yet implemented; the lease mechanism itself is on the [roadmap](#roadmap).)*
**Small and genuinely self-hostable.** Two static Go binaries and one SQLite file. **You can even run the coordserver and meshd binaries on the same machine.**
## How it works
<p align="center">
<img src="./assets/Simplified_architecture.png" alt="Knoten Architecture Diagram">
</p>
On each machine, `meshd`:
1. **Generates an identity** on first run: a keypair is created locally. The private key is written to a `0600` state file and never leaves the machine.
2. **Registers** with the coordination server, which assigns it a stable VPN address out of `10.10.0.0/16` and records the public endpoint it was seen from.
3. **Polls `/v1/sync`** on an interval the server dictates to the whole fleet, receiving the list of peers that have checked in recently.
4. **Applies the peer list** to its own WireGuard tunnel, `knoten-wg`, which runs inside `meshd` itself.
5. **Stays in the background.** All actual traffic flows peer to peer, encrypted end to end by WireGuard.
**The tunnel lives inside `meshd`: stopping `meshd` removes it, and starting it again brings it back. Run it as a service (for example systemd with `Restart=always`) so it comes back on its own.**
**If the coordination server becomes unreachable, `meshd` retries with exponential backoff (1s to 60s, with jitter).**
## Deployment modes
| Mode | When to use it | What you run |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| **Standalone** | Every node has a static, reachable IP address, and you are happy to list peers yourself. Pure peer-to-peer, no control plane at all. | `meshd` on each node, with a `static_peers` list in its config. |
| **Coordinated** | Peers sit behind NAT or have changing IPs, or you want identity and permissions managed centrally. | `coordserver` on one reachable host, `meshd` on every node. A static node can do both jobs. |
Machines are given addresses from a fixed `10.10.0.0/16` range in coordinated mode. *(A future version will let you choose your own address pool.)*
## Requirements
- **Linux** on every node, with `/dev/net/tun` available (it is on standard distributions). `meshd` creates the `knoten-wg` interface itself, so it must run as root.
- **One reachable host** for `coordserver` if you are using coordinated mode.
## Quick start
Prebuilt Linux binaries are attached to each [release](https://github.com/Yacin1102/Knoten/releases).
### Coordinated mode
This walks through **coordinated mode**: one coordination server plus a fleet of nodes. Skip to [Standalone mode](#standalone-mode) if every node already has a static IP.
#### 1. Start the coordination server
Generate a join token first. Every machine must present it to enrol. (Skip if you don't want a join token)
```bash
sudo mkdir -p /etc/knoten
openssl rand -base64 32 | sudo tee /etc/knoten/token > /dev/null
sudo chmod 600 /etc/knoten/token
```
Create the database directory:
```bash
sudo mkdir /var/lib/knoten
```
Give the binary the right permissions:
```bash
chmod +x ./coordserver
```
Then start the server:
```bash
sudo ./coordserver \
-token-file /etc/knoten/token #skip if you didn't generate one
```
It should show something like this:
```
coordserver: listening on :8080 (plain HTTP)
coordserver: VPN range: 10.10.0.0/16
coordserver: database: /var/lib/knoten/coord.db
coordserver: machines poll every 30s; considered gone after 1m30s of silence
coordserver: join token is set (44 characters)
```
The database file and its directory are created if missing. Open TCP **8080** on the coordination server, and UDP **51820** (the WireGuard port) on every node.
> ⚠️ `coordserver` speaks **plain HTTP** and has no TLS of its own. Put it behind a reverse proxy that terminates TLS before exposing it to the internet. (*Native HTTPS support is planned for a future version.*) See [Security model](#security-model).
#### 2. Join your first machine
Give the binary the right permissions:
```bash
chmod +x ./meshd
```
Run interactive setup on the node:
```bash
sudo ./meshd -setup
```
Setup asks a short list of questions and writes `/etc/knoten/meshd.json`:
```
ListenPort (default 51820):
Use a coordination server? (y/N): y
Coordination server URL (e.g. https://coord.example.com:8443): http://<server IP>:8080
Join token (leave empty if the server does not require one): <paste the token from step 1 if you generated one>
Name for this machine (leave empty for "Hostname-01"):
```
Setup stores the token you paste inline in `meshd.json`, as `join_token`. To keep the token in its own file instead, edit `/etc/knoten/meshd.json` after setup and swap the field:
```json
{
"use_coordination_server": true,
"server_url": "http://198.51.100.10:8080",
"token_file": "/etc/knoten/token"
}
```
`token_file` wins when both are present: if it is set, `join_token` is ignored entirely. Setup never writes `token_file` for you, so this is always a manual edit.
`-setup` writes the config and exits. Then start `meshd`; on its first run it creates an identity, registers, and brings the tunnel up:
```
meshd: no key found at /var/lib/knoten/meshd-State_file.json... generating a new identity for this machine
meshd: generated key pair; this machine's PUBLIC key is: 9Kx...=
meshd: coordination server: http://198.51.100.10:8080
meshd: this machine: "node-1", public key 9Kx...=
meshd: registering with http://198.51.100.10:8080
meshd: registered: our VPN address is 10.10.0.2 in 10.10.0.0/16
meshd: brought knoten-wg up
meshd: synced knoten-wg: 0 added, 0 updated, 0 removed
```
Now run it continuously:
```bash
sudo meshd
```
> You can run it in the background, or install it as a systemd service.
#### 3. Join the rest of the fleet
Repeat step 2 on every other machine. Each one gets its own key and its own address, and within one sync interval (30s by default) every node sees every other node:
```
meshd: synced knoten-wg: 2 added, 0 updated, 0 removed
```
#### 4. Verify
```bash
# What does the control plane think?
curl -s http://<server IP>:8080/v1/health
# {"status":"ok","machines_total":3,"machines_alive":3,"uptime_seconds":412,
# "vpn_cidr":"10.10.0.0/16","sync_interval_seconds":30}
# What does WireGuard think? (You will need wireguard tools installed to use this command)
sudo wg show knoten-wg
# Can you reach a peer over the mesh?
ping 10.10.0.3
```
Send `SIGHUP` to any daemon to force an immediate sync instead of waiting for the next poll:
```bash
sudo pkill -HUP meshd
```
### Standalone mode
If every node has a static, reachable address, skip the server entirely. Run `meshd -setup` and answer **no** to *"Use a coordination server?"*, then enter this node's address and each peer by hand:
```
This node's Address (e.g. 10.0.0.1/24): 10.0.0.1/24
Peer PublicKey (leave empty to finish): xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
AllowedIPs (e.g. 10.0.0.2/32): 10.0.0.2/32
Endpoint (e.g. 192.168.56.102:51820) (leave empty if this peer has no fixed address): 203.0.113.7:51820
Name for this peer (optional): db-01
Peer PublicKey (leave empty to finish):
```
Start `meshd` once on each node to create its key, then run `meshd -show-key` to read the public key you need to paste into the others.
In standalone mode `meshd` brings the tunnel up with the configured peers and **keeps running**, since the tunnel lives inside it. After editing the peer list, restart `meshd`.
## Command-line reference
For a quick look without leaving the terminal:
```bash
coordserver -h
meshd -h
```
**Signals:** `SIGHUP` makes `meshd` sync immediately (coordinated mode only). `SIGINT` and `SIGTERM` stop it cleanly and remove the tunnel.
## Files and paths
| Path | Written by | Mode | Contents |
| ----------------------------------- | -------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `/etc/knoten/meshd.json` | `meshd -setup` | `0600` | Node configuration. |
| `/var/lib/knoten/meshd-State_file.json` | `meshd` | `0600` | Private key, public key, assigned VPN address. **Back this up.** |
| `/etc/knoten/token` | you | `0600` | Shared join token. Read by `coordserver -token-file`, and by `meshd` only when `token_file` is set in its config. |
| `/var/lib/knoten/coord.db` | `coordserver` | - | SQLite database of enrolled machines. |
| `/var/run/wireguard/knoten-wg.sock` | `meshd` | `0600`-equivalent (root only) | WireGuard control socket. Exists while `meshd` runs. |
> **Deleting `meshd-State_file.json` gives the machine a brand-new identity and a brand-new VPN address on the next register.** The old registration lingers on the server until it times out.
The interface is always named `knoten-wg`.
## Security model
**What Knoten protects today**
- **Traffic is end-to-end encrypted by WireGuard**, peer to peer. The coordination server sees metadata: public keys, names, addresses, endpoints, and last-seen times.
- **Private keys never leave the machine.** They are generated locally and written `0600`.
- **The join token is compared in constant time**, so it cannot be recovered by timing the response.
- **The coordination server is defensive at the edges**: bounded request bodies, strict JSON decoding, request timeouts, panic recovery, and a single-writer SQLite store with `WAL` and `synchronous = FULL`.
**Known limitations**
- **No TLS.** `coordserver` serves plain HTTP. Terminate TLS in front of it (nginx, Caddy, Traffic) before exposing it publicly, and set `-trust-proxy` only when your proxy overwrites `X-Forwarded-For`.
- **One shared token for the whole fleet.** Any machine holding it can enroll and read the peer list. There is no per-machine credential and no revocation yet. See the [roadmap](#roadmap).
- **No NAT traversal.** Endpoints come from whatever address the server observed. Peers behind symmetric NAT with no port forwarding may not reach each other directly, and there is no relay fallback yet.
- **No admin surface yet.** Removing a machine means deleting its row from the SQLite database by hand.
## Roadmap
As a student, I am constantly **learning while building** Knoten. This is the project's planned roadmap:
| Stage | What we’ll be building | Progress | **Details** |
| ----- | ------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | WireGuard config generator \| v0.1 | ✅ | - |
| 2 | Coordination server \| v0.2.0-alpha | ✅ | - |
| 3 | Wg-go + Light optimizations \| v0.3.0-alpha | ✅ | - **Embed Wireguard into knoten through wg-go package**, replacing the config file writing mechanism.<br>- Replace the current shell-out to `wg` and `wg-quick`. |
| 4 | Peer memory & single configuration \| v0.3.1-alpha | ⚒️ | - **Remember the last peer list** across restarts, so a node comes back with its peers even while the coordination server is unreachable.<br>- **One configuration file** instead of separate standalone and coordinated modes: a coordination server, manually listed peers, or both. |
| 5 | windows support | ⚒️ | - |
| 6 | Security, features and stability improvements \| v0.3.2-alpha | ⚒️ | - **Protocol and communication improvements.**<br>- **Native HTTPS support.**<br>- **Optimizations (e.g. the IP allocation mechanism and database iterations)**<br> |
| 7 | Relay | - | - **Last-resort relay** for peers that genuinely cannot reach each other. (Even then, relayed packets stay sealed and end-to-end encrypted, unreadable by the relay.) |
| 8 | NAT support | - | - |
| 9 | Redundant control-plane coordination | - | - **Multiple coordination servers**, so the control plane has no single point of failure. |
| 10 | Identity & access management | - | - **Per-machine credentials, leases and revocation**, replacing the single shared join token and realising the "access is a lease" principle. |
| 11 | Management console (UI) | - | - |
| 12 | Hardening | - | - |
## Contributing
This is a student-driven project, and contributions are genuinely welcome: bug reports, suggestions, design critique, and documentation just as much as code.
Please open an issue to discuss anything substantial before sending a large pull request. Because the project documents its own development journey, PRs that change design decisions are more useful when they explain the reasoning, not only the diff.
Thank you.
## License
Released under the [MIT License](LICENSE).