8a6216bccb392ee9e9d625bc23a7eaf541506c81
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
23af1a2d9d |
Add shared TCP and UDP virtual services
Provide named VIPs with independent protocol/port listeners and node/port backends so applications can use one DNS endpoint without a separate load balancer. Every authenticated member may create, edit or delete any service. Client agents select the eligible backend with the lowest measured encrypted data-plane RTT, including relayed paths, and pin existing flows. UDP recovers on another backend after failure; established TCP is never replayed or moved. An unanswered empty TCP SYN may retry another backend before establishment. Keep signed multi-writer field registers in state/vip.rs. Independent backend edits merge, same-field conflicts use Lamport/author/canonical ordering, and listener generations prevent old backend entries returning on port recreation. Permanent service tombstones protect replay while allowing obsolete fields to be compacted. Active-service and deletion-history limits are separate. Commit complete snapshots and the author's durable counter atomically before local routing changes or publication. Restore counters from newer genuine author updates learned from replicas after an old backup is restored. Implement bounded client-side address/port NAT at overlay ingress and reverse translation only after authenticated source/destination admission. Reserve OS source ports to avoid direct-flow collisions and quarantine retired ports for late replies. Handle a backend on the client itself, scoped fragment assembly, checksums, re-fragmentation and quoted ICMP/PMTU headers. Plugins and encrypted transit retain their existing contracts. Data-path probes measure RTT; bounded fair TCP connect checks are enabled by default and configurable per listener. Arbitrary UDP application readiness remains unknown; path checks and explicit port errors drive eligibility without protocol-specific probe payloads. Expose the shared draft API through local IPC, simple vip/listener/backend CLI commands, JSON, editor-based atomic updates and watch output. Add a VIP button on each network tile and a separate tray editor. Edits and removals stay local until Save; stale revisions retain the draft, and closing a dirty window offers explicit discard. Report local routing choices, health reasons, RTT age, active and draining bindings, IP counters, rates, drops and UDP failovers. Extend DNS with service names; keep numeric addresses secondary and preserve existing bindings while VIP addresses or listeners change. This changes control wire ALPN/version to tsunagi/ctrl/8 and local IPC to 22; all members and local clients must upgrade together. Data ALPN remains 5. SQLite schema 10 migrates existing mandatory state without changing identity, secrets, network ids, derivation labels, handshake encoding or node records. Crate versions and release tags are unchanged. No installed agent or tray was replaced. Commands, limits and operational semantics are in docs/vip.md. Checks on Linux in nix develop: - cargo fmt --all -- --check - cargo clippy --locked --workspace --all-targets -- -D warnings - cargo test --locked --workspace --all-targets: 559 passed, 3 ignored - git diff --cached --check The full suite includes real encrypted TCP/UDP VIP traffic, backend-stop UDP failover, real loopback TCP check recovery, source admission, signed merges, stale/deleted state, SQLite migration and rollback, restart/author recovery, network isolation, actual CLI/DNS processes, and headless text-entry/Save-button interaction. Discovery and memory TUNs are the existing test substitutions; authentication, transport, encryption and SQLite remain real. Initial checks outside Nix failed for missing GTK pkg-config metadata, and an initial CLI test inherited TSUNAGI_CONTROL_SOCKET. Final checks use the Nix environment with that variable unset so tests address their temporary sockets. Root/internet-dependent tests remain ignored. No live host TUN, native desktop session, macOS or Windows validation was performed. |
||
|
|
2631eb6975 |
Protect Linux exit-node connections and tidy the device table
Using an exit node could destroy direct peer connections: the agent's UID bypass protected outbound routing, but reverse-path checks look up replies without that UID and selected the overlay default. A real-host A/B check confirmed that relaxing only strict reverse-path filtering restored direct connections and exit HTTP traffic; the diagnostic host changes were removed. Keep strict filtering and protect the Linux underlay with a dedicated masked mark bit, 0x40000000. Mark the dedicated service UID in OUTPUT, save only that bit in conntrack and restore it before PREROUTING reverse-path checks. Install IPv4/IPv6 mark and UID main-table bypass rules before Tailscale's priorities; leave application-specific and exit-default routing after them. Preserve foreign mark bits, main-table routes and foreign firewall/policy rules. Protect the first inbound TCP/UDP packet using bounded, locally owned socket addresses/ports from /proc and an actual socket match. Refresh socket protection before sending private transport negotiations and on discovery ticks, without firewall writes for an unchanged socket set. Remote announcements never become OS arguments. A failed refresh leaves active exit routing in place. Install protection before routes, remove routes before protection, roll back partial activation, and remove only exact interface tags, including legacy UID rules. Reject strict kernel rp_filter without src_valid_mark with an actionable error; never weaken filtering or change host sysctls. Document mark-aware Netfilter requirements and provide the trusted ip6tables path in the NixOS module. Other platforms retain their existing exit implementation. Peer wire formats, identity derivation, local IPC and persistent storage are unchanged. Make the tray devices grid fill and shrink with the window, constrain the first cell and give remaining width to traffic plots. Keep displayed end-to-end protocol and its short encryption tooltip consistent, independent of carrier protocol. Move handshake/session age into its own column beside the existing iroh control RTT. Use stable per-device hover identities and concise tooltips; missing protocols/measurements remain explicit rather than invented. Validation on Linux through nix develop: - cargo fmt --all -- --check - cargo clippy --locked --workspace --all-targets -- -D warnings - cargo test --locked --workspace --all-targets: 529 passed, 3 ignored; inherited TSUNAGI_CONTROL_SOCKET and TSUNAGI_CONTROL_GROUP were unset - explicit privileged kernel regression: 1 passed, using disposable namespaces, real TCP/UDP IPv4/IPv6, strict kernel and Netfilter RPF, first inbound packets, dynamic UDP binding, application exit routing/IPv6 blocking, repeated apply and exact cleanup with foreign Tailscale-priority rules retained - headless egui grid/scroll-area rendering at multiple sizes and control RTT, mark masks/order, passive socket parsing, partial failure and cleanup tests - git diff --check Earlier iterations caught rtnetlink family API and nonexhaustive initializer errors, Clippy warnings, egui's implicit minimum column width, and kernel-test fixture issues (wildcard echo source, blocked IPv6 neighbour discovery and a closed child stdout pipe). Corrected before final checks; the stdout-only test fix was checked with fmt, Clippy and the explicit kernel regression afterward. Native tray rendering and macOS/Windows checks were not run. The running host agent was not replaced or restarted. Requested release tag: v0.1.0-rc.32. |
||
|
|
e701986a39 |
Add ephemeral pairwise Amnezia transport and runtime fallback
Plain WG and QUIC can be blocked or severely degraded while the iroh control connection still works. Add an awg carrier with one independent UDP socket, WireGuard engine and randomized masking profile per network/peer attempt. The smaller endpoint id negotiates fresh outer keys, PSK, header protection, header ranges, padding and signature templates through private authenticated control messages. Restart or fallback reentry generates a new profile; ordinary rekeying preserves the current profile. No pair parameters enter signed state, capabilities, persistent storage or public message events. Private payloads and serialized control buffers redact and zeroize themselves. Adapt the bounded packet masking engine from RunaRoo/AmneziaWG-RS at commit 51579a7d6e8882d1d8d6d00822f09e18a9423b73, retaining its Apache license and provenance. Declare WTFPL AND Apache-2.0 in package metadata to cover both new glue and the adapted source. Reuse boringtun instead of upstream's Node/global executors and interface machinery. This is Tsunagi AWG wire version 1, with standard WG headers restored before authentication; external Amnezia server compatibility is not implemented. DNS/SIP signature generators vary fields instead of replaying captures. Each attempt closes independently, bounds allocation, queues, candidates and fragment assemblies, and supports the full 64 KiB carrier contract using UDP packets at most 1200 bytes. Yield between fragment batches so large messages do not starve local socket readers. Keep carrier encryption separate from end-to-end tunnels so multihop relays still forward opaque ciphertext. The system owns the sole interface, signed addresses and admission. Plugins receive neither network secrets nor paths. Expose a bounded opaque control sender at the transport boundary; core control messages remain outside protocol crates. Optional STUN on the pair's actual socket adds IPv4 public candidates in CLI direct/relay mode, with a finite query deadline and an off/alternate-server option. Local mode remains offline. Symmetric NAT, IPv6-only paths and arbitrary UDP blocking can still require fallback; no AWG-over-QUIC or hidden AWG relay is introduced. Default carrier order is wg, awg, wg-quic, tcp-tls. Advance the fallback cycle after setup failures and sustained runtime loss, even after earlier cooldowns expire. Authenticated delivery probes traverse the actual encrypted tunnel, fit the interface MTU and never enter the TUN. Retire after a 20-second warmup and at least 60% loss over at least 15 completed recent observations. Preserve logical tunnels and control sessions while explicitly closing physical links, including TCP reader/writer tasks. Retain the newest private probe settings across independently queued control negotiation and inbound installation. CLI/tray reports keep end-to-end protocol and actual carrier separate. Control ALPN/version becomes tsunagi/ctrl/6 for the new private message; upgrade all members together. Data ALPN/envelopes remain version 5. Network identity derivation, transcript encoding, device identities, signed records, saved network configuration and release versions are unchanged. Validation on Linux through nix develop: - cargo fmt --all -- --check - cargo clippy --locked --workspace --all-targets -- -D warnings - cargo test --locked --workspace --all-targets, with the inherited TSUNAGI_CONTROL_SOCKET override removed: 504 passed, 2 pre-existing ignored Tests use real iroh membership, SQLite, UDP sockets and WireGuard encryption with memory TUNs. Cover three independent pairs, full-size IP and carrier frames, restart freshness/socket release, 80% carrier datagram loss, fallback to real iroh, fresh retries with working delivery checks, unequal small MTUs, malformed negotiations/profiles, role and resource bounds, probe forgery and STUN parsing. Earlier compile/lint iterations and focused tests caught RNG API/initializer mistakes, a retry assertion racing path installation, and a large-fragment burst overflowing loopback buffers; corrected before these successful checks. Normalized upstream license line endings for diff checks. Public NAT/STUN connectivity, actual DPI effectiveness, WAN performance, per-pair memory measurement, macOS/Windows, privileged TUN operations and manual tray interaction were not exercised. |
||
|
|
ed28e86748 |
Derive default network ranges and switch overlapping networks
Every first network previously proposed 10.13.37.0/24. Independently created networks therefore collided when a device joined more than one of them. Use the existing stable NetworkId-derived /24 in 10/8 for every new network, independent of activation order or peer availability. Legacy and explicit signed ranges outrank the derived default; remaining range disagreements still use author-id ordering. The derivation, identity labels, handshake and signed record encoding stay unchanged. There is no creator, timestamp or vote. Reserve ranges before starting network actors and serialise start, stop and switching through the agent lifecycle lock. Refuse equal or nested overlaps without changing a network's subnet locally. A late authenticated range that collides suspends only its network before updating plugins or the interface. Coalesced watch notifications let the agent clean up outside the network actor and reconsider suspended networks when ranges change. An explicit switch stops every blocker before activating the chosen network. Suspended networks retain their startup request and resume whenever their ranges become free, including switching from a wide range to a narrow one. A manual stop cancels that request even for an already suspended network. Persist preference order and enabling together in an SQLite transaction so the user's selection survives restart; no database schema migration is needed. CLI status and network listing show blocker names, ids, ranges and the start command. Network start switches explicitly and reports suspended networks; joining an overlapping network reports it as configured but inactive. The tray adds conflict details, an amber icon, Switch to this network and Keep stopped actions. Local IPC wire version is 19 for the extended reports, so restart the agent and tray together. Release versions and peer wire formats are unchanged. Update the user documentation and compatibility tests. Validation on Linux through nix develop: - cargo fmt --all -- --check - cargo clippy --locked --workspace --all-targets -- -D warnings - cargo test --locked --workspace --all-targets with TSUNAGI_CONTROL_SOCKET removed from the test environment: 486 passed, 2 pre-existing ignored tests - final focused range_conflicts integration suite after making discovery fixtures deterministic: 6 passed Earlier validation attempts caught outdated diagnostic assertions, a new test helper scope error, and the inherited system control socket override. Those were corrected before the successful runs. Plain cargo was unavailable in the host shell, so validation used the Nix development toolchain. macOS, Windows, privileged host TUN operations and manual tray interaction were not exercised. |
||
|
|
7c176e0b74 |
Exit nodes on Windows: use a member as exit node via destination bypass routes
Choosing an exit node on Windows did nothing: the using side was refused, because Windows has no per-user routing and the agent's own traffic would loop into the tunnel it carries. Now the overlay interface gets 0.0.0.0/1 and 128.0.0.0/1 routes (and ::/1, 8000::/1 as an IPv6 blackhole), and every underlay address the agent talks to directly gets a /32 route through the physical default gateway, marked with route metric 7373 so stale ones are found and removed. Bypass routes are written before the halves and removed after them; a failed step removes what was written. All routes are in the active store. The addresses come from the runtime: each network runtime hands the interface the verified direct IPv4 paths of its control connections and the relay hosts in use (Interface::set_underlay, refreshed every dial round); relay names are resolved when the rules are applied. ExitHostRules::needs_bypass (true only on Windows) gates it, so Linux and macOS plans never carry or re-apply on path changes. ExitHostPlan gains `bypass` and `bypass_hosts`; networks leaving forget their entry. Limits: a path that appears after the last apply goes through the tunnel until the next apply; data-plane-only addresses are not collected separately (the data links ride the same iroh paths). Nothing was run on Windows. Checks: cargo fmt --check, cargo clippy --locked --workspace --all-targets -D warnings and cargo test --locked --workspace --all-targets passed on macOS; scripts and ordering are unit-tested with an injected runner and resolver, and Interface::set_underlay with the mock host. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
637915600e |
Exit nodes on Windows: offer through WinNAT; using is refused
Windows reported exit nodes as unsupported. The offering side now creates one WinNAT object per offered range (New-NetNat, named tsunagi-exit:<if>:<range>), prunes this interface's stale ones, reads (never writes) IPv4 forwarding of the overlay interface, and removes everything on clear. All values are single-quoted PowerShell literals. The using side is deliberately not implemented: Windows has no per-user or per-process routing, so the agent's own QUIC/relay traffic cannot be kept out of a default route through the overlay and would loop. It reports that reason and installs no route. The CLI/tray forwarding hint and the kernel-forwarding warning name the Windows command; docs/exit-node.md has a Windows section. Checks: cargo fmt --check, cargo clippy --locked --workspace --all-targets -D warnings and cargo test --locked --workspace --all-targets passed on macOS. The script builders and plan logic are unit-tested with an injected runner on every platform; nothing was run against a real Windows host or cross-compiled (no Windows target installed). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
47c5d38d0c |
Exit nodes on macOS: pf anchor for offering, routes plus route-to for using
The exit-node host rules existed only for Linux (iptables + policy routing); macOS reported "only supported on Linux yet". Offering: a pf anchor com.apple/tsunagi-exit-<if> (evaluated by the stock pf.conf) holds a nat rule per offered range and a pass-in rule. pf is enabled if needed and the pfctl -E reference is released when the rules are removed. net.inet.ip.forwarding is only read, never written, as on Linux. Using: macOS has no uidrange policy routing and the agent is root, so the overlay gets 0.0.0.0/1 and 128.0.0.0/1 routes and a pf route-to rule (matched by the agent's uid) sends the agent's own QUIC/relay traffic back out the physical interface, translated to its address. The rules are loaded before the routes, so a failed load never sends traffic into a tunnel it cannot exempt the agent from. IPv6 is blocked with rejecting ::/1 and 8000::/1 routes. ExitHostPlan gains `overlay` (every network range) for the route-to exemption; Linux ignores it. Known limits: the egress is the default route's interface at apply time; other root processes also bypass the tunnel. Forwarding hints in the CLI and tray name the macOS sysctl. The rule text and command order are unit-tested with a scripted host; nothing was run against a live pf/route (needs root), so the route-to + nat second-pass behaviour is unverified. Checks: cargo fmt --check, cargo clippy --locked --workspace --all-targets -D warnings and cargo test --locked --workspace --all-targets, run on macOS only. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
0cfc1cbafc |
Tray state icon and window picture; firewall rules without the comment match; macOS app bundle
The tray icon is dark red with no connection (or a lost exit node), blue when connected and green while an exit node is in use; the windows get the logo and an app id that matches the desktop entry, and the exit icon moves to the bottom left of the tile. Exit-node and broadcast firewall rules fall back to being recognised by their shape on kernels that have no `comment` match, as small router builds do. The macOS archive carries Tsunagi.app for the Homebrew cask, and the daemon plist opens the control socket to the admin group. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
9966b50407 |
Rename the CLI package to tsunagi and its executable to tsng
The command line package is now `tsunagi` and builds the `tsng` executable, which the packages, the unit, the installers and the workflows install and run. The core library package becomes `tsunagi-core`, keeping the library name `tsunagi`, so no code changes `use tsunagi::...`. Directories follow (crates/tsunagi-core, crates/tsunagi). Every command shown to a person is now `tsng ...`. Co-Authored-By: Claude <noreply@anthropic.com> |