{"id":48335880,"url":"https://github.com/leonardomb1/pulse","last_synced_at":"2026-04-08T03:08:53.994Z","repository":{"id":349240237,"uuid":"1201584357","full_name":"leonardomb1/pulse","owner":"leonardomb1","description":"An easy to configure encrypted mesh network with TUN VPN, tag-based ACLs, quality-routed multipath.","archived":false,"fork":false,"pushed_at":"2026-04-05T01:26:48.000Z","size":226,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-05T02:26:56.884Z","etag":null,"topics":["mesh-networks","networking","vnet","vpn"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leonardomb1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-04T21:54:37.000Z","updated_at":"2026-04-05T01:26:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/leonardomb1/pulse","commit_stats":null,"previous_names":["leonardomb1/pulse"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/leonardomb1/pulse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leonardomb1%2Fpulse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leonardomb1%2Fpulse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leonardomb1%2Fpulse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leonardomb1%2Fpulse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leonardomb1","download_url":"https://codeload.github.com/leonardomb1/pulse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leonardomb1%2Fpulse/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31537855,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"online","status_checked_at":"2026-04-08T02:00:06.127Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mesh-networks","networking","vnet","vpn"],"created_at":"2026-04-05T02:01:34.912Z","updated_at":"2026-04-08T03:08:53.977Z","avatar_url":"https://github.com/leonardomb1.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pulse\n\n[![CI](https://github.com/leonardomb1/pulse/actions/workflows/ci.yml/badge.svg)](https://github.com/leonardomb1/pulse/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/leonardomb1/pulse)](https://go.dev/)\n[![Release](https://img.shields.io/github/v/release/leonardomb1/pulse?include_prereleases)](https://github.com/leonardomb1/pulse/releases)\n[![Go Report Card](https://goreportcard.com/badge/github.com/leonardomb1/pulse)](https://goreportcard.com/report/github.com/leonardomb1/pulse)\n\nA zero-config encrypted mesh network in a single binary. Nodes discover each other via gossip, traffic is routed by measured link quality, and the whole thing is managed from the CLI or an interactive TUI.\n\n## What it does\n\n- **Encrypted mesh** between any number of nodes over QUIC (with WebSocket fallback)\n- **Layer 3 VPN** via TUN interface — mesh IPs just work like a LAN\n- **NAT traversal** — automatic QUIC hole punching for direct peer-to-peer links\n- **Weighted multipath routing** — traffic distributed across paths by inverse-score weighting (better paths get more traffic)\n- **Delta-gossip** — only changed entries are sent per gossip round (80-95% bandwidth reduction)\n- **FEC** — optional forward error correction on TUN pipes for lossy links (recovers single packet loss without retransmit)\n- **Exit nodes** — forward traffic to the internet through designated nodes\n- **Tag-based ACL policies** — `tag:dev` can't reach `tag:prod`, first-match firewall rules\n- **DNS** for the `.pulse` TLD — `ssh user@db-server.pulse`\n- **SOCKS5 proxy** — transparent mesh routing for any application\n- **Network isolation** — separate pulse deployments with `--network` IDs\n- **Interactive TUI** (`pulse top`) — k9s-style dashboard for managing the mesh\n- **Prometheus metrics** — `/metrics` endpoint for Grafana/alerting\n\n## Architecture\n\n```\n                    ┌──────────────┐\n                    │   CA Node    │  Signs certificates\n                    │  (--ca)      │  Handles join flow\n                    └──────┬───────┘\n                           │\n              ┌────────────┼────────────┐\n              │            │            │\n        ┌─────┴─────┐ ┌───┴────┐ ┌────┴──────┐\n        │  Scribe   │ │ Relay  │ │  Client   │\n        │ (--scribe)│ │        │ │(--socks   │\n        │ ACLs,DNS, │ │forwards│ │ --dns     │\n        │ tags,     │ │traffic │ │ --tun)    │\n        │ tokens    │ │        │ │           │\n        └───────────┘ └────────┘ └───────────┘\n```\n\n**Two control roles (can be on the same or different machines):**\n\n- **CA** — certificate authority. Signs node certificates, handles the join flow.\n- **Scribe** — network config authority. Manages ACLs, DNS zones, tags, names, revocations, and join tokens. Distributes signed config to all nodes.\n\nEvery node is also a **relay** that forwards traffic for other nodes. Roles are composable — a single node can be CA + scribe + relay + exit.\n\n## Quickstart\n\nNo config file needed. Everything is flags.\n\n```bash\n# Build\ngo build -o pulse ./cmd/pulse/\n\n# On a server with a public IP — start as CA:\npulse --ca --scribe --addr relay.example.com:443 --listen :443 \\\n      --network mynet --token $(openssl rand -hex 32)\n\n# On another machine — join the mesh:\npulse join relay.example.com:443 --token \u003ctoken\u003e\n\n# Start with services enabled:\npulse start --socks --dns --tun --network mynet relay.example.com:443\n\n# Check status:\npulse status\npulse top     # interactive TUI\n```\n\n## CLI Reference\n\n### Lifecycle\n\n```\npulse [flags] [peers...]              Start in foreground\npulse start [flags] [peers...]        Start as background daemon\npulse stop                            Graceful shutdown\npulse id                              Print node ID and mesh IP\npulse cert                            Show certificate expiry and status\npulse top                             Interactive TUI dashboard\n```\n\n### Mesh\n\n```\npulse join \u003crelay\u003e --token \u003ctok\u003e      Join a mesh (one-time)\npulse status                          Show mesh status table\npulse tag \u003cnode-id\u003e \u003ctag\u003e             Add a tag to a node\npulse untag \u003cnode-id\u003e \u003ctag\u003e           Remove a tag\npulse name \u003cnode-id\u003e \u003cname\u003e           Set a friendly name\npulse revoke --node \u003cid\u003e              Revoke a node's certificate\n```\n\n### Policy\n\n```\npulse acl list                        Show ACL rules\npulse acl add --from \u003cpat\u003e --to \u003cpat\u003e Add a rule (--deny, --ports 22,443)\npulse acl remove \u003cindex\u003e              Remove a rule by index\n```\n\nACL patterns: node ID globs (`a3f2*`), tags (`tag:prod`), names (`db-server`), or `*`.\n\nRules are evaluated top-to-bottom, first match wins. No rules = open by default. Adding any rule activates policy mode (unmatched = deny).\n\n### Tokens\n\n```\npulse token                           Show legacy master token (CA only)\npulse token create --ttl 1h           Create a time-limited token\npulse token create --max-uses 1       Create a single-use token\npulse token list                      List all tokens\npulse token revoke \u003cprefix\u003e           Revoke a token\n```\n\n### Networking\n\n```\npulse connect --node \u003cid\u003e --dest \u003caddr\u003e   SSH ProxyCommand tunnel\npulse forward --node \u003cid\u003e --dest \u003caddr\u003e --local \u003caddr\u003e   Port forward\npulse dns list|add|remove             Manage DNS records\npulse route list|add|remove           Manage exit routes\n```\n\n### Admin\n\n```\npulse ca log                          View CA audit log\npulse ca sign --ca-dir \u003cdir\u003e ...      Offline cert signing\npulse setup dns                       Configure systemd-resolved for .pulse\n```\n\n## Node Flags\n\n```\n--config \u003cfile\u003e        Path to config.toml (optional)\n--data-dir \u003cpath\u003e      Data directory (default ~/.pulse)\n--addr \u003caddr\u003e          Advertised address (default :8443)\n--listen \u003caddr\u003e        Bind address (default: same as --addr)\n--tcp \u003caddr\u003e           TCP tunnel listener (default :7000)\n--network \u003cid\u003e         Network isolation ID\n--join \u003caddr\u003e          CA relay address (auto-join on startup)\n--token \u003csecret\u003e       Join token\n--log-level \u003clevel\u003e    debug, info, warn, error (default: info)\n```\n\n## Feature Flags\n\n```\n--ca                   Certificate authority\n--ca-token \u003csecret\u003e    Token the CA accepts (defaults to --token)\n--scribe               Control plane (ACLs, DNS, tags, dashboard)\n--scribe-listen \u003caddr\u003e Scribe HTTP API (default 127.0.0.1:8080)\n--socks                SOCKS5 proxy\n--socks-listen \u003caddr\u003e  SOCKS5 address (default 127.0.0.1:1080)\n--dns                  DNS server for .pulse TLD\n--dns-listen \u003caddr\u003e    DNS address (default 127.0.0.1:5353)\n--tun                  TUN interface for layer 3 routing (Linux)\n--fec                  Forward error correction on TUN pipes (lossy links)\n--exit                 Exit node (forwards traffic to internet)\n```\n\n## How it works\n\n### Transport\n\nNodes try QUIC first (no head-of-line blocking, 0-RTT reconnect), fall back to WebSocket+yamux if UDP is blocked. Transport selection is transparent — both return the same `Session` interface. QUIC sessions support connection migration — when a node's NAT rebinds (wifi to 4G), the session survives without reconnecting.\n\n### Gossip (Delta)\n\nEvery 10 seconds, each node sends **only changed entries** to its neighbors (delta-gossip). Each table entry is stamped with a version counter; peers track which version they last received. A full table push is forced every 60 seconds as a fallback. Stale entries (not seen in 5 minutes) are pruned. Max hop count: 16.\n\n### Routing (Weighted Multipath)\n\nThe router scores each path using:\n\n```\nscore = latency_ms * (1 + 5*loss_rate) * (1 + 0.3*hop_count)\n```\n\nA 2-hop path at 5ms beats a 1-hop path at 200ms. When multiple viable paths exist, traffic is distributed using **inverse-score weighted random selection** — a 5ms path gets ~10x more streams than a 50ms path.\n\n### NAT Traversal\n\nNodes discover their public address via `/whoami` on a relay, then coordinate simultaneous UDP punches to establish direct QUIC links. The relay remains as fallback.\n\n### TUN (Layer 3 VPN)\n\nEach node gets a deterministic mesh IP (`10.100.x.x`) derived from its node ID. The `pulse0` TUN interface handles routing at the kernel level. Exit node CIDRs are auto-learned from gossip and installed as kernel routes. The packet path uses a single TUN reader with per-peer write queues and batch draining to avoid contention.\n\nOptional **FEC** (forward error correction) can be enabled for lossy links (`[tun] fec = true`). For every 10 data packets, 1 XOR parity packet is sent. The receiver can reconstruct any single lost packet without retransmission — 10% bandwidth overhead for significant latency improvement on lossy links.\n\n### Certificate Lifecycle\n\n- CA cert: 10 years\n- Node certs: 90 days, auto-renewed when \u003c30 days remain\n- Renewal happens through the mesh (re-join flow) — no downtime\n- TLS configs use dynamic callbacks — renewed certs are picked up without restart\n\n### Security\n\n- **mTLS** between all peers using CA-signed ed25519 certificates\n- **Constant-time** token comparison (timing attack resistant)\n- **Peer identity verification** — nodeID must match SHA256 of public key\n- **ACL enforcement at every hop** — not just the terminating relay\n- **Signed network config** — ACLs, DNS, tags distributed via ed25519-signed NetworkConfig from the scribe\n- **SSRF protection** — tunnel DestAddr validated, cloud metadata IPs blocked\n- **Network isolation** — `--network` ID checked in handshake, mismatched peers rejected\n- **Audit log** — all CA operations (join attempts, cert issuance, revocations) logged with fsync\n\n## HTTP API\n\nThe scribe exposes a REST API (default `127.0.0.1:8080`):\n\n| Endpoint | Methods | Purpose |\n|----------|---------|---------|\n| `GET /api/status` | GET | Full mesh state |\n| `GET /api/nodes` | GET | Peer list |\n| `GET/PUT /api/config` | GET, PUT | Raw NetworkConfig |\n| `GET/POST/DELETE /api/dns` | * | DNS zone CRUD |\n| `GET/POST/DELETE /api/acls` | * | ACL rule CRUD |\n| `POST/DELETE /api/tags` | * | Node tag management |\n| `PUT /api/name` | PUT | Set node name |\n| `GET /api/routes` | GET | Exit route table |\n| `POST /api/revoke` | POST | Revoke a node |\n| `GET/POST/DELETE /api/tokens` | * | Token management |\n| `GET /metrics` | GET | Prometheus metrics |\n\n## Prometheus Metrics\n\n```\npulse_peers_total                      # known peers\npulse_peers_connected                  # peers with active sessions\npulse_peer_latency_ms{node_id,name}    # per-peer RTT\npulse_peer_loss_ratio{node_id,name}    # per-peer packet loss\npulse_cert_expiry_seconds              # seconds until cert expires\npulse_acl_rules_total                  # ACL rule count\npulse_tokens_valid                     # usable join tokens\npulse_node_info{node_id,network_id}    # node metadata labels\n```\n\n## Config File (optional)\n\nAll settings can be passed as flags. A TOML config file is optional:\n\n```toml\n[node]\naddr       = \"relay.example.com:443\"\nlisten     = \":443\"\nnetwork_id = \"prod\"\nlog_level  = \"info\"\n\n[ca]\nenabled    = true\njoin_token = \"your-secret-token\"\n\n[scribe]\nenabled = true\n\n[tun]\nenabled = true\n\n[socks]\nenabled = true\n\n[dns]\nenabled = true\nlisten  = \"127.0.0.1:5353\"\n```\n\n## Examples\n\n### Home + relay setup\n\n```bash\n# Remote server (CA + relay):\npulse --ca --addr relay.example.com:443 --listen :443 \\\n      --network home --token $(openssl rand -hex 32)\n\n# Home machine (scribe + all services):\npulse join relay.example.com:443 --token \u003ctoken\u003e\npulse start --scribe --socks --dns --tun --network home relay.example.com:443\n\n# Name your nodes:\npulse name \u003crelay-id\u003e relay-01\npulse name \u003chome-id\u003e home-desktop\npulse tag \u003crelay-id\u003e infra\n```\n\n### SSH through the mesh\n\n```bash\n# Direct (with TUN enabled):\nssh user@10.100.247.82\n\n# Via DNS:\nssh user@relay-01.pulse\n\n# Via ProxyCommand (no TUN needed):\nssh -o ProxyCommand=\"pulse connect --node \u003cid\u003e --dest localhost:22\" user@relay\n```\n\n### Access control\n\n```bash\n# Allow infra nodes SSH everywhere:\npulse acl add --from \"tag:infra\" --to \"*\" --ports 22\n\n# Block dev from prod:\npulse acl add --from \"tag:dev\" --to \"tag:prod\" --deny\n\n# Allow DB access on postgres port only:\npulse acl add --from \"*\" --to \"tag:db\" --ports 5432\n```\n\n### Time-limited invite\n\n```bash\n# Create a 1-hour, single-use token:\npulse token create --ttl 1h --max-uses 1\n# Share the token — it self-destructs after one use or one hour\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleonardomb1%2Fpulse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleonardomb1%2Fpulse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleonardomb1%2Fpulse/lists"}