- Rust 99.1%
- Nix 0.9%
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
| shell.nix | ||
SIGNET
Signet is a small program used to dynamically propagate route between some machine to a gateway running a BGP backend daemon like frr.
The whole point of this tool is to avoid running heavy BGP backend on a lot of machine for the sole purpose of propagating route.
This projet was created to satisfies the specific need for glaieul.fr and precisely to implement floating IP address between a multi-AS architecture.
Architecture
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SIGNET SERVER │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Server Core (server.rs) │ │
│ │ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ TLS Listener │ │ Client Manager │ │ Unix Socket API │ │ │
│ │ │ Port: 8084 │ │ (ClientInfo) │ │ /run/signet/server.sock │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌─────────────┐ │ │ ┌──────────────┐ │ │ Commands: │ │ │
│ │ │ │TLS Handshake│ │ │ │Active/Stale │ │ │ • status │ │ │
│ │ │ │& Auth │ │ │ │Tracking │ │ │ • flushstale │ │ │
│ │ │ │ │ │ │ │ │ │ │ • flushall │ │ │
│ │ │ └─────────────┘ │ │ └──────────────┘ │ │ │ │ │
│ │ └─────────────────┘ └──────────────────┘ └─────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Backend Manager (backends.rs) │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Route Management │ │ │
│ │ │ • Client Route Tracking │ │ │
│ │ │ • Route Add/Remove Operations │ │ │
│ │ │ • Health Check Coordination │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────────────────┼─────────────────────────┐ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ BGP Backend │ │ BGP Backend │ │ Future │ │ │
│ │ │ (FRR) │ │ (FRR) │ │ Backends │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • Route Mgmt │ │ • Route Mgmt │ │ • BIRD │ │ │
│ │ │ • Health Chk │ │ • Health Chk │ │ • OpenBGPD │ │ │
│ │ │ │ │ │ │ • Custom... │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
│
│ TLS Connections
│ (Encrypted subnet announcements)
│
│
┌───────────────────────────────────────────────────┐
│ │
▼ ▼
┌─────────────────────────────────────────────┐ ┌─────────────────────────────────────────────┐
│ SIGNET CLIENT #1 │ │ SIGNET CLIENT #2 │
│ │ │ │
│ ┌─────────────────────────────────────────┐ │ │ ┌─────────────────────────────────────────┐ │
│ │ Client Application │ │ │ │ Client Application │ │
│ │ │ │ │ │ │ │
│ │ ┌─────────────────────────────────────┐ │ │ │ │ ┌─────────────────────────────────────┐ │ │
│ │ │ HEALTH CHECKS │ │ │ │ │ │ HEALTH CHECKS │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ HTTP Check (port 80): │ │ │ │ │ │ HTTP Check (port 443): │ │ │
│ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ │ ┌─────────────────────────────────┐ │ │ │
│ │ │ │ Status: PASS │ │ │ │ │ │ │ │ Status: FAIL │ │ │ │
│ │ │ │ Response Time: 45ms │ │ │ │ │ │ │ │ Error: Connection refused │ │ │ │
│ │ │ │ Last Check: (6s ago) │ │ │ │ │ │ │ │ Last Check: (7s ago) │ │ │ │
│ │ │ └─────────────────────────────────┘ │ │ │ │ │ │ └─────────────────────────────────┘ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ SYSTEMD Service (nginx.service): │ │ │ │ │ │ DNS Check (port 53): │ │ │
│ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ │ ┌─────────────────────────────────┐ │ │ │
│ │ │ │ Status: PASS │ │ │ │ │ │ │ │ Status: PASS │ │ │ │
│ │ │ │ State: active (running) │ │ │ │ │ │ │ │ Query: example.com A │ │ │ │
│ │ │ │ Last Check: (16s ago) │ │ │ │ │ │ │ │ Response Time: 23ms │ │ │ │
│ │ │ └─────────────────────────────────┘ │ │ │ │ │ │ │ Last Check: (36s ago) │ │ │ │
│ │ │ │ │ │ │ │ │ └─────────────────────────────────┘ │ │ │
│ │ │ CUSTOM Check (app-health): │ │ │ │ │ │ │ │ │
│ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ │ CUSTOM Check (api-endpoint): │ │ │
│ │ │ │ Status: PASS │ │ │ │ │ │ │ ┌─────────────────────────────────┐ │ │ │
│ │ │ │ Script: /opt/check-app.sh │ │ │ │ │ │ │ │ Status: FAIL │ │ │ │
│ │ │ │ Exit Code: 0 │ │ │ │ │ │ │ │ Script: /usr/local/check.sh │ │ │ │
│ │ │ │ Last Check: (2s ago) │ │ │ │ │ │ │ │ Exit Code: 1 │ │ │ │
│ │ │ └─────────────────────────────────┘ │ │ │ │ │ │ │ Last Check: (1s ago) │ │ │ │
│ │ │ │ │ │ │ │ │ └─────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────┘ │ │ │ │ └─────────────────────────────────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌─────────────────────────────────────┐ │ │ │ │ ┌─────────────────────────────────────┐ │ │
│ │ │ OVERALL HEALTH STATUS │ │ │ │ │ │ OVERALL HEALTH STATUS │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ All Checks: PASSING │ │ │ │ │ │ One or More Checks: FAILING │ │ │
│ │ │ Client Status: HEALTHY │ │ │ │ │ │ Client Status: UNHEALTHY │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ └─────────────────────────────────────┘ │ │ │ │ └─────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────┘ │ │ └─────────────────────────────────────────┘ │
│ │ │ │
│ ┌─────────────────────────────────────────┐ │ │ ┌─────────────────────────────────────────┐ │
│ │ SUBNET ANNOUNCEMENTS │ │ │ │ SUBNET ANNOUNCEMENTS │ │
│ │ │ │ │ │ │ │
│ │ ANNOUNCING (All Checks Passing): │ │ │ │ NOT ANNOUNCING (Health Check Failed): │ │
│ │ │ │ │ │ │ │
│ │ • 10.0.1.0/24 │ │ │ │ • 10.0.2.0/24 [WITHDRAWN] │ │
│ │ • 172.16.0.0/20 │ │ │ │ • 192.168.1.0/24 [WITHDRAWN] │ │
│ │ • 203.0.113.0/24 │ │ │ │ │ │
│ │ │ │ │ │ Reason: HTTP check failed │ │
│ │ Last Updated: 2025-06-08 14:32:15 │ │ │ │ Last Updated: 2025-06-08 14:32:10 │ │
│ └─────────────────────────────────────────┘ │ │ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘ └─────────────────────────────────────────────┘
Each client runs a bunch of check (rules), if all PASS, they advert some subnet to the server. A single client can advert subnet to multiple server at once, for earch server a set of rules are defined.
The server gather subnet from a single or multiple clients, client connection are tracked. The server tracked and annouce the received subnet to one or multiple BGP backend daemon.
Client to server connections are tracked, if the client failed to advert its subnet in the define windows, it switch from ACTIVE to STALE, and routes are not longer annouce to the BGP backends.
Usage:
Daemon
To a run the client or server daemon use: signet daemon -f conf.toml, see configuration section below
CLI
If a daemon is running you can send command to it via a Unix socket (see the configuration section below for setup)
Here all the command available:
signet server status: Get the server statussignet server flush stale: Remove STALE client from the tracking tablesignet server flush all: Remove all the client from the tracking table (need a few seconds for the client to recover from it and automatically reconnect)signet client status: Get the client status (display rules checks, subnet sent, etc)
Configuration
Socket for CLI support
In order for CLI command to works, the folder /run/signet must be writtable by the user running the program.
Server
Example:
[general]
mode = "server"
listen = ["0.0.0.0", "::1"]
port = 8084
[security]
tls_cert = "./certs/server.crt"
tls_key = "./certs/server.key"
tls_ca = "./certs/ca.crt"
[backend]
[backend.main]
type = "frr"
asn = "64496"
General:
- mode: must be set to "server" for server-mode
- listen: list of all adress to listen to, you can add as much as you like (IPv4 or IPv6), default: [ "0.0.0.0" ]
- port: The port to listen to, default: 8080
- backend_monitor_interval: The interval between each health check of each backend, default: 10s
- announcement_interval: The interval between each subnet annoucement from a client, default 10. A client is marked as stale if no subnet where seen for twice this amount. Stale check are run at interval of half this amount.
security
- tls_cert = The server cert file
- tls_key = The server key file
- tls_ca = The CA cert to check client trustworthiness
Supported backends (as for now):
- frr
- requires ASN settings: can bes set to the AS number but also to "64496 vrf 100" to handle vrf
Client
Example:
[general]
mode = "client"
[security]
tls_cert = "./certs/client.crt"
tls_key = "./certs/client.key"
tls_ca = "./certs/ca.crt"
[network]
[network.test4]
to = "127.0.0.1:8084"
net = [ "10.16.0.0/12" ]
rule = [ "srvisrunning" ]
[network.test6]
to = "[::1]:8084"
net = [ "fc00:1::/32" ]
rule = [ "httpresponse", "somedns", "somescript" ]
[rule]
[rule.srvisrunning]
kind = "systemd"
service = "auth.service"
state = "running"
interval = 10
[rule.httpresponse]
kind = "http"
endpoint = "https://glaieul.fr:443"
state = "200"
interval = 10
[rule.somedns]
kind = "dns"
server = "10.2.0.1"
domain = "glaieul.fr"
interval = 10
[rule.somescript]
kind = "custom"
path = "./test-custom.sh"
state = "0"
interval = 60
General:
- mode: must be set to "client" for client-mode
- rule_monitor_interval: The main rule loop interval, should be less then the minimum interval amongs all rules, default: 5s
- announcement_interval: The interval between each subnet annoucement, default 10.
security
- tls_cert = The client cert file
- tls_key = The client key file
- tls_ca = The CA cert to check server trustworthiness
Network.name
- to: The server ip address and port using ip:port notation
- net: The list of subnet to annouced to the server
- rule: The list of rules to PASS for the "net" subnet to be annouced
Rule.name
- kind: One of:
- "systemd": Requires:
- service: the service name to check
- state: the expected state of the service, either "running" of "failed"
- "http": Requires:
- endpoint: the full URL endpoint to test
- state: the expected http code received
- "dns": Requires:
- server: the ip address of the DNS server to ask
- domain: The domain for each a A record will be ask: WARNING: the test will succeed as soon as the server respond, even if the response doesn't contains the A record for the given domain, even if recursion isn't available. The idea is just to test if there is a living DNS server at that IP on port 53
- "custom": Requires:
- path: The path of the script to run
- state: the expected exit code after the script execution
- "systemd": Requires:
- interval: The interval between each check.