Peers communicating on uncensored networks can benefit from raw WireGuard
performance over direct UDP without QUIC framing or double-encryption
overhead. This provides Tailscale-like direct tunnel performance while
preserving Tsunagi's decentralized mesh architecture.
Key changes:
- Create `tsunagi-wireguard` crate implementing the `IpPlugin` and
`PacketTransport` contracts:
- `WG_PROTOCOL = "wg"` with wire version 1.
- Bounded `WgAnnouncement` carrying WireGuard public keys, listening UDP
ports, and candidate IP addresses (filtering out overlay interfaces).
- `WgKeyStore` managing persistent per-network WireGuard X25519 keys
in SQLite with zeroization on drop.
- `WireguardTransport` providing direct UDP packet transmission, candidate
address tracking, inbound link delivery, and roaming support.
- `WireguardDevice` running userspace WireGuard encryption via `boringtun`,
timer maintenance, and AllowedIPs source enforcement.
- Integrate `wg` into `tsunagi-cli`:
- Set default protocols to `"wg,tcp-tls,wg-quic"` so all protocols are
enabled by default in order of throughput and performance.
- Support `"wireguard"` as a convenience alias for `"wg"` in `--protocol`.
- Bind `PureWgTransport` and launch the inbound datagram accept loop on `up`.
- Pass `pure_wg` plugin to `AgentControl` and update `build_report` so
active WireGuard tunnels and overlay metrics are reported in `tsunagi status`.
- Clean up WireGuard keys on network removal in `forget_protocol_state`.
- Comprehensive test coverage in `tsunagi-wireguard`:
- `announcement_tests`: round-trip serialization and verification of malformed,
mismatched network, zero port, or self-claiming announcements.
- `transport_tests`: loopback UDP link delivery between transports.
- `plugin_tests`: end-to-end two-peer handshake and bidirectional IP packet
transit over direct UDP.
Validation on Linux:
- cargo fmt --all -- --check passed.
- cargo clippy --locked --workspace --all-targets -- -D warnings passed.
- cargo test --locked --workspace --all-targets passed.
87 lines
2.9 KiB
Rust
87 lines
2.9 KiB
Rust
#![allow(missing_docs)]
|
|
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
|
|
use std::net::IpAddr;
|
|
|
|
use tsunagi::identity::NetworkId;
|
|
use tsunagi_wireguard::{ANNOUNCEMENT_VERSION, WgAnnouncement, WgPublicKey, WgSecretKey};
|
|
|
|
#[test]
|
|
fn announcement_round_trip() {
|
|
let network = NetworkId::from_bytes([42u8; 32]);
|
|
let key = WgSecretKey::generate().public();
|
|
let local = WgSecretKey::generate().public();
|
|
|
|
let addrs: Vec<IpAddr> = vec![
|
|
"192.168.1.100".parse().unwrap(),
|
|
"10.0.0.5".parse().unwrap(),
|
|
];
|
|
|
|
let announcement = WgAnnouncement::new(network, &key, 51820, addrs.clone());
|
|
let encoded = announcement.encode().expect("encode");
|
|
|
|
let validated = WgAnnouncement::decode_and_validate(&encoded, network, &local).expect("decode");
|
|
|
|
assert_eq!(validated.public_key, key);
|
|
assert_eq!(validated.port, 51820);
|
|
assert_eq!(validated.addrs, addrs);
|
|
}
|
|
|
|
#[test]
|
|
fn announcement_rejects_zero_key() {
|
|
let network = NetworkId::from_bytes([42u8; 32]);
|
|
let local = WgSecretKey::generate().public();
|
|
let zero = WgPublicKey::from_bytes([0u8; 32]);
|
|
|
|
let announcement = WgAnnouncement::new(network, &zero, 51820, vec![]);
|
|
let encoded = announcement.encode().unwrap();
|
|
|
|
let err = WgAnnouncement::decode_and_validate(&encoded, network, &local).unwrap_err();
|
|
assert!(err.to_string().contains("all zeroes"));
|
|
}
|
|
|
|
#[test]
|
|
fn announcement_rejects_own_key() {
|
|
let network = NetworkId::from_bytes([42u8; 32]);
|
|
let local = WgSecretKey::generate().public();
|
|
|
|
let announcement = WgAnnouncement::new(network, &local, 51820, vec![]);
|
|
let encoded = announcement.encode().unwrap();
|
|
|
|
let err = WgAnnouncement::decode_and_validate(&encoded, network, &local).unwrap_err();
|
|
assert!(err.to_string().contains("own WireGuard key"));
|
|
}
|
|
|
|
#[test]
|
|
fn announcement_rejects_wrong_network() {
|
|
let network_a = NetworkId::from_bytes([1u8; 32]);
|
|
let network_b = NetworkId::from_bytes([2u8; 32]);
|
|
let key = WgSecretKey::generate().public();
|
|
let local = WgSecretKey::generate().public();
|
|
|
|
let announcement = WgAnnouncement::new(network_a, &key, 51820, vec![]);
|
|
let encoded = announcement.encode().unwrap();
|
|
|
|
let err = WgAnnouncement::decode_and_validate(&encoded, network_b, &local).unwrap_err();
|
|
assert!(err.to_string().contains("different network"));
|
|
}
|
|
|
|
#[test]
|
|
fn announcement_rejects_zero_port() {
|
|
let network = NetworkId::from_bytes([42u8; 32]);
|
|
let key = WgSecretKey::generate().public();
|
|
let local = WgSecretKey::generate().public();
|
|
|
|
let announcement = WgAnnouncement {
|
|
version: ANNOUNCEMENT_VERSION,
|
|
public_key: *key.as_bytes(),
|
|
network: *network.as_bytes(),
|
|
port: 0,
|
|
addrs: vec![],
|
|
};
|
|
let encoded = announcement.encode().unwrap();
|
|
|
|
let err = WgAnnouncement::decode_and_validate(&encoded, network, &local).unwrap_err();
|
|
assert!(err.to_string().contains("cannot be 0"));
|
|
}
|