Make the overlay dual stack
Every member now also derives an IPv4 address, from the same inputs as its IPv6 one, into 100.64.0.0/10 by default. The range is configurable and IPv4 can be turned off with --no-ipv4. IPv4 is honestly weaker than IPv6 here and the code says so. A 64 bit interface identifier makes an IPv6 collision impossible in practice; IPv4 has nothing like that room, and in a /10 with 50 members two will derive the same address about 0.03% of the time. A mesh with no coordinator cannot allocate around that, so a collision is detected and resolved instead: the member whose public key sorts lower keeps the address, a rule every member computes identically and therefore agrees on. The other keeps IPv6 and is flagged in the status. IPv6 always works; IPv4 almost always works and degrades predictably. Routing and address-ownership enforcement now cover both families: a packet goes to the peer that owns its destination, and a decrypted packet is dropped unless its source is an address derived for the peer that sent it, IPv4 included. Six new tests, among them a real IPv4 packet crossing a tunnel next to an IPv6 one, a spoofed IPv4 source being dropped, and an IPv6-only overlay. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
be459e5bd0
commit
cfab38824d
12 files changed
+638
-46
No files matched your search
@@ -25,7 +25,7 @@
|
||||
//! A failure here is reported and retried. It never stops the control plane.
|
||||
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::net::IpAddr;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::time::Duration;
|
||||
@@ -43,7 +43,9 @@ use super::announcement::{ValidatedAnnouncement, WgAnnouncement};
|
||||
use super::config::{DEFAULT_INTERFACE_PREFIX, interface_name};
|
||||
use super::device::{PeerSummary, WireguardDevice};
|
||||
use super::keys::{WgPublicKey, WgSecretKey};
|
||||
use super::overlay::{OVERLAY_PREFIX_LEN, overlay_address, overlay_prefix};
|
||||
use super::overlay::{
|
||||
DEFAULT_IPV4_RANGE, OVERLAY_PREFIX_LEN, overlay_address, overlay_address_v4, overlay_prefix,
|
||||
};
|
||||
use super::store::WgKeyStore;
|
||||
use super::tun::{TunFactory, TunRequest};
|
||||
|
||||
@@ -86,6 +88,12 @@ pub struct WireguardConfig {
|
||||
pub keepalive: Option<u16>,
|
||||
/// Interface MTU. See [`DEFAULT_MTU`].
|
||||
pub mtu: u32,
|
||||
/// IPv4 overlay range, or `None` for an IPv6-only overlay.
|
||||
///
|
||||
/// IPv6 addresses are derived collision-free; IPv4 ones cannot be, so a
|
||||
/// collision is detected and resolved deterministically instead. See
|
||||
/// `docs/wireguard.md`.
|
||||
pub ipv4_range: Option<(Ipv4Addr, u8)>,
|
||||
/// How long to coalesce changes before reconciling.
|
||||
pub reconcile_debounce: Duration,
|
||||
/// How often to reconcile anyway, which is also when a packet interface
|
||||
@@ -101,6 +109,7 @@ impl WireguardConfig {
|
||||
interface_prefix: DEFAULT_INTERFACE_PREFIX.to_string(),
|
||||
keepalive: Some(25),
|
||||
mtu: DEFAULT_MTU,
|
||||
ipv4_range: Some(DEFAULT_IPV4_RANGE),
|
||||
reconcile_debounce: Duration::from_millis(200),
|
||||
reconcile_interval: Duration::from_secs(15),
|
||||
}
|
||||
@@ -120,6 +129,12 @@ impl WireguardConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the IPv4 overlay range, or disables IPv4 with `None`.
|
||||
pub fn with_ipv4_range(mut self, range: Option<(Ipv4Addr, u8)>) -> Self {
|
||||
self.ipv4_range = range;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the reconciliation timings.
|
||||
pub fn with_reconcile(mut self, debounce: Duration, interval: Duration) -> Self {
|
||||
self.reconcile_debounce = debounce;
|
||||
@@ -150,6 +165,10 @@ pub struct NetworkOverview {
|
||||
pub overlay_prefix: IpAddr,
|
||||
/// Prefix length of the overlay subnet.
|
||||
pub overlay_prefix_len: u8,
|
||||
/// This agent's IPv4 overlay address, when the overlay is dual stack.
|
||||
pub overlay_address_v4: Option<Ipv4Addr>,
|
||||
/// The IPv4 overlay range in use.
|
||||
pub ipv4_range: Option<(Ipv4Addr, u8)>,
|
||||
/// Peers this agent knows about.
|
||||
pub peers: Vec<PeerOverview>,
|
||||
/// Unicast packets the operating system sent to an address no peer owns.
|
||||
@@ -174,6 +193,8 @@ pub struct PeerOverview {
|
||||
pub public_key: WgPublicKey,
|
||||
/// The overlay address derived for it locally.
|
||||
pub overlay_address: IpAddr,
|
||||
/// Its IPv4 overlay address, once a tunnel exists and it won the address.
|
||||
pub overlay_address_v4: Option<Ipv4Addr>,
|
||||
/// Whether a data plane link to it exists.
|
||||
pub has_link: bool,
|
||||
/// The running tunnel, once there is a link.
|
||||
@@ -309,6 +330,9 @@ impl WireguardPlugin {
|
||||
endpoint_id: *endpoint_id,
|
||||
public_key: announcement.public_key,
|
||||
overlay_address: IpAddr::V6(announcement.overlay_address),
|
||||
overlay_address_v4: tunnels
|
||||
.get(&announcement.public_key)
|
||||
.and_then(|tunnel| tunnel.overlay_address_v4),
|
||||
has_link: state.links.contains_key(endpoint_id),
|
||||
tunnel: tunnels.get(&announcement.public_key).cloned(),
|
||||
})
|
||||
@@ -323,6 +347,12 @@ impl WireguardPlugin {
|
||||
overlay_address: IpAddr::V6(overlay_address(network, &state.key.public())),
|
||||
overlay_prefix: IpAddr::V6(overlay_prefix(network)),
|
||||
overlay_prefix_len: OVERLAY_PREFIX_LEN,
|
||||
overlay_address_v4: self
|
||||
.worker
|
||||
.config
|
||||
.ipv4_range
|
||||
.and_then(|range| overlay_address_v4(network, &state.key.public(), range)),
|
||||
ipv4_range: self.worker.config.ipv4_range,
|
||||
peers,
|
||||
unroutable_packets: state
|
||||
.device
|
||||
@@ -437,10 +467,18 @@ impl Worker {
|
||||
name: name.clone(),
|
||||
address: overlay_address(network, &key.public()),
|
||||
prefix_len: OVERLAY_PREFIX_LEN,
|
||||
address_v4: self.config.ipv4_range.and_then(|range| {
|
||||
overlay_address_v4(network, &key.public(), range).map(|address| (address, range.1))
|
||||
}),
|
||||
mtu: self.config.mtu,
|
||||
};
|
||||
let tun = self.tun_factory.create(request).await?;
|
||||
let device = Arc::new(WireguardDevice::start(network, key, tun));
|
||||
let device = Arc::new(WireguardDevice::start(
|
||||
network,
|
||||
key,
|
||||
tun,
|
||||
self.config.ipv4_range,
|
||||
));
|
||||
|
||||
let mut shared = self.lock_shared();
|
||||
if let Some(state) = shared.networks.get_mut(&network) {
|
||||
|
||||
Reference in new issue
Block a user