Files
Easytier/easytier/src/peers/route_trait.rs
T
KKRainbow 8f862997eb feat: support allocating public IPv6 addresses from a provider (#2162)
* feat: support allocating public IPv6 addresses from a provider

Add a provider/leaser architecture for public IPv6 address allocation
between nodes in the same network:

- A node with `--ipv6-public-addr-provider` advertises a delegable
  public IPv6 prefix (auto-detected from kernel routes or manually
  configured via `--ipv6-public-addr-prefix`).
- Other nodes with `--ipv6-public-addr-auto` request a /128 lease from
  the selected provider via a new RPC service (PublicIpv6AddrRpc).
- Leases have a 30s TTL, renewed every 10s by the client routine.
- The provider allocates addresses deterministically from its prefix
  using instance-UUID-based hashing to prefer stable assignments.
- Routes to peer leases are installed on the TUN device, and each
  client's own /128 is assigned as its IPv6 address.

Also includes netlink IPv6 route table inspection, integration tests,
and event-driven route/address reconciliation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 21:37:34 +08:00

237 lines
6.4 KiB
Rust

use cidr::Ipv6Inet;
use cidr::{Ipv4Cidr, Ipv6Cidr};
use dashmap::DashMap;
use std::{
collections::BTreeSet,
net::{Ipv4Addr, Ipv6Addr},
sync::Arc,
};
use crate::{
common::{PeerId, global_ctx::NetworkIdentity},
proto::{
api::instance::ListPublicIpv6InfoResponse,
peer_rpc::{
ForeignNetworkRouteInfoEntry, ForeignNetworkRouteInfoKey, PeerIdentityType,
RouteForeignNetworkInfos, RouteForeignNetworkSummary, RoutePeerInfo,
},
},
};
#[derive(Clone, Debug, Default)]
pub enum NextHopPolicy {
#[default]
LeastHop,
LeastCost,
}
pub type ForeignNetworkRouteInfoMap =
DashMap<ForeignNetworkRouteInfoKey, ForeignNetworkRouteInfoEntry>;
#[async_trait::async_trait]
pub trait RouteInterface {
async fn list_peers(&self) -> Vec<PeerId>;
fn my_peer_id(&self) -> PeerId;
fn need_periodic_requery_peers(&self) -> bool {
false
}
async fn close_peer(&self, _peer_id: PeerId) {}
async fn get_peer_public_key(&self, _peer_id: PeerId) -> Option<Vec<u8>> {
None
}
async fn get_peer_identity_type(&self, _peer_id: PeerId) -> Option<PeerIdentityType> {
None
}
async fn list_foreign_networks(&self) -> ForeignNetworkRouteInfoMap {
DashMap::new()
}
}
pub type RouteInterfaceBox = Box<dyn RouteInterface + Send + Sync>;
#[auto_impl::auto_impl(Box , &mut)]
pub trait RouteCostCalculatorInterface: Send + Sync {
fn begin_update(&mut self) {}
fn end_update(&mut self) {}
fn calculate_cost(&self, _src: PeerId, _dst: PeerId) -> i32 {
1
}
fn need_update(&self) -> bool {
false
}
fn dump(&self) -> String {
"All routes have cost 1".to_string()
}
}
#[derive(Clone, Debug, Default)]
pub struct DefaultRouteCostCalculator;
impl RouteCostCalculatorInterface for DefaultRouteCostCalculator {}
pub type RouteCostCalculator = Box<dyn RouteCostCalculatorInterface>;
#[async_trait::async_trait]
#[auto_impl::auto_impl(Box, Arc)]
pub trait Route {
async fn open(&self, interface: RouteInterfaceBox) -> Result<u8, ()>;
async fn close(&self);
async fn get_next_hop(&self, peer_id: PeerId) -> Option<PeerId>;
async fn get_next_hop_with_policy(
&self,
peer_id: PeerId,
_policy: NextHopPolicy,
) -> Option<PeerId> {
self.get_next_hop(peer_id).await
}
async fn list_routes(&self) -> Vec<crate::proto::api::instance::Route>;
// TODO: rewrite route management, remove this
async fn list_proxy_cidrs(&self) -> BTreeSet<Ipv4Cidr>;
// TODO: rewrite route management, remove this
async fn list_proxy_cidrs_v6(&self) -> BTreeSet<Ipv6Cidr>;
async fn list_public_ipv6_routes(&self) -> BTreeSet<Ipv6Inet> {
BTreeSet::new()
}
async fn get_my_public_ipv6_addr(&self) -> Option<Ipv6Inet> {
None
}
async fn get_public_ipv6_gateway_peer_id(&self) -> Option<PeerId> {
None
}
async fn get_local_public_ipv6_info(&self) -> ListPublicIpv6InfoResponse {
ListPublicIpv6InfoResponse::default()
}
async fn get_peer_id_by_ipv4(&self, _ipv4: &Ipv4Addr) -> Option<PeerId> {
None
}
async fn get_peer_id_by_ipv6(&self, _ipv6: &Ipv6Addr) -> Option<PeerId> {
None
}
async fn get_peer_id_by_ip(&self, ip: &std::net::IpAddr) -> Option<PeerId> {
match ip {
std::net::IpAddr::V4(v4) => self.get_peer_id_by_ipv4(v4).await,
std::net::IpAddr::V6(v6) => self.get_peer_id_by_ipv6(v6).await,
}
}
async fn list_peers_own_foreign_network(
&self,
_network_identity: &NetworkIdentity,
) -> Vec<PeerId> {
vec![]
}
async fn list_foreign_network_info(&self) -> RouteForeignNetworkInfos {
Default::default()
}
async fn get_foreign_network_summary(&self) -> RouteForeignNetworkSummary {
Default::default()
}
// my peer id in foreign network is different from the one in local network
// this function is used to get the peer id in local network
async fn get_origin_my_peer_id(
&self,
_network_name: &str,
_foreign_my_peer_id: PeerId,
) -> Option<PeerId> {
None
}
async fn set_route_cost_fn(&self, _cost_fn: RouteCostCalculator) {}
async fn get_peer_info(&self, peer_id: PeerId) -> Option<RoutePeerInfo>;
async fn get_peer_info_last_update_time(&self) -> std::time::Instant;
fn get_peer_groups(&self, peer_id: PeerId) -> Arc<Vec<String>>;
async fn refresh_acl_groups(&self) {}
async fn get_peer_groups_by_ip(&self, ip: &std::net::IpAddr) -> Arc<Vec<String>> {
match self.get_peer_id_by_ip(ip).await {
Some(peer_id) => self.get_peer_groups(peer_id),
None => Arc::new(Vec::new()),
}
}
async fn get_peer_groups_by_ipv4(&self, ipv4: &Ipv4Addr) -> Arc<Vec<String>> {
match self.get_peer_id_by_ipv4(ipv4).await {
Some(peer_id) => self.get_peer_groups(peer_id),
None => Arc::new(Vec::new()),
}
}
async fn dump(&self) -> String {
"this route implementation does not support dump".to_string()
}
}
pub type ArcRoute = Arc<Box<dyn Route + Send + Sync>>;
pub struct MockRoute {}
#[async_trait::async_trait]
impl Route for MockRoute {
async fn open(&self, _interface: RouteInterfaceBox) -> Result<u8, ()> {
panic!("mock route")
}
async fn close(&self) {
panic!("mock route")
}
async fn get_next_hop(&self, _peer_id: PeerId) -> Option<PeerId> {
panic!("mock route")
}
async fn list_routes(&self) -> Vec<crate::proto::api::instance::Route> {
panic!("mock route")
}
// TODO: rewrite route management, remove this
async fn list_proxy_cidrs(&self) -> BTreeSet<Ipv4Cidr> {
unimplemented!()
}
// TODO: rewrite route management, remove this
async fn list_proxy_cidrs_v6(&self) -> BTreeSet<Ipv6Cidr> {
unimplemented!()
}
async fn list_public_ipv6_routes(&self) -> BTreeSet<Ipv6Inet> {
unimplemented!()
}
async fn get_my_public_ipv6_addr(&self) -> Option<Ipv6Inet> {
panic!("mock route")
}
async fn get_peer_info(&self, _peer_id: PeerId) -> Option<RoutePeerInfo> {
panic!("mock route")
}
async fn get_peer_info_last_update_time(&self) -> std::time::Instant {
panic!("mock route")
}
fn get_peer_groups(&self, _peer_id: PeerId) -> Arc<Vec<String>> {
panic!("mock route")
}
}