add ipv6 cli

This commit is contained in:
sijie.sun
2026-04-26 11:37:01 +08:00
parent 463228f1de
commit 67366aba55
9 changed files with 319 additions and 12 deletions
+185 -5
View File
@@ -51,13 +51,14 @@ use easytier::{
ListCredentialsRequest, ListCredentialsResponse, ListForeignNetworkRequest,
ListGlobalForeignNetworkRequest, ListMappedListenerRequest, ListPeerRequest,
ListPeerResponse, ListPortForwardRequest, ListPortForwardResponse,
ListRouteRequest, ListRouteResponse, MappedListener, MappedListenerManageRpc,
ListPublicIpv6InfoRequest, ListPublicIpv6InfoResponse, ListRouteRequest,
ListRouteResponse, MappedListener, MappedListenerManageRpc,
MappedListenerManageRpcClientFactory, MetricSnapshot, NodeInfo, PeerManageRpc,
PeerManageRpcClientFactory, PortForwardManageRpc,
PortForwardManageRpcClientFactory, RevokeCredentialRequest, ShowNodeInfoRequest,
StatsRpc, StatsRpcClientFactory, TcpProxyEntryState, TcpProxyEntryTransportType,
TcpProxyRpc, TcpProxyRpcClientFactory, TrustedKeySourcePb, VpnPortalInfo,
VpnPortalRpc, VpnPortalRpcClientFactory,
PortForwardManageRpcClientFactory, RevokeCredentialRequest, Route as ApiRoute,
ShowNodeInfoRequest, StatsRpc, StatsRpcClientFactory, TcpProxyEntryState,
TcpProxyEntryTransportType, TcpProxyRpc, TcpProxyRpcClientFactory,
TrustedKeySourcePb, VpnPortalInfo, VpnPortalRpc, VpnPortalRpcClientFactory,
instance_identifier::{InstanceSelector, Selector},
list_global_foreign_network_response, list_peer_route_pair,
},
@@ -193,6 +194,7 @@ struct PeerArgs {
#[derive(Subcommand, Debug)]
enum PeerSubCommand {
List,
Ipv6,
ListForeign {
#[arg(
long,
@@ -536,6 +538,12 @@ struct RouteListData {
peer_routes: Vec<PeerRoutePair>,
}
struct PeerIpv6DataRaw {
node_info: NodeInfo,
routes: Vec<ApiRoute>,
provider_info: ListPublicIpv6InfoResponse,
}
#[derive(serde::Serialize)]
struct PeerCenterRowData {
node_id: String,
@@ -963,6 +971,27 @@ impl<'a> CommandHandler<'a> {
})
}
async fn fetch_local_public_ipv6_info(&self) -> Result<ListPublicIpv6InfoResponse, Error> {
Ok(self
.get_peer_manager_client()
.await?
.list_public_ipv6_info(
BaseController::default(),
ListPublicIpv6InfoRequest {
instance: Some(self.instance_selector.clone()),
},
)
.await?)
}
async fn fetch_peer_ipv6_data(&self) -> Result<PeerIpv6DataRaw, Error> {
Ok(PeerIpv6DataRaw {
node_info: self.fetch_node_info().await?,
routes: self.list_routes().await?.routes,
provider_info: self.fetch_local_public_ipv6_info().await?,
})
}
async fn fetch_connector_list(&self) -> Result<Vec<Connector>, Error> {
Ok(self
.get_connector_manager_client()
@@ -1375,6 +1404,154 @@ impl<'a> CommandHandler<'a> {
})
}
async fn handle_peer_ipv6(&self) -> Result<(), Error> {
#[derive(tabled::Tabled, serde::Serialize)]
struct PeerIpv6NodeRow {
peer_id: u32,
hostname: String,
inst_id: String,
ipv4: String,
public_ipv6_addr: String,
provider_prefix: String,
}
#[derive(tabled::Tabled, serde::Serialize)]
struct ProviderLeaseRow {
peer_id: u32,
inst_id: String,
leased_addr: String,
valid_until: String,
reused: bool,
}
#[derive(serde::Serialize)]
struct ProviderLeaseSection {
provider_prefix: String,
leases: Vec<ProviderLeaseRow>,
}
#[derive(serde::Serialize)]
struct PeerIpv6View {
nodes: Vec<PeerIpv6NodeRow>,
local_provider: Option<ProviderLeaseSection>,
}
fn fmt_ipv6_inet(value: Option<easytier::proto::common::Ipv6Inet>) -> String {
value
.map(|value| value.to_string())
.unwrap_or_else(|| "-".to_string())
}
fn fmt_valid_until(unix_seconds: i64) -> String {
chrono::DateTime::<chrono::Utc>::from_timestamp(unix_seconds, 0)
.map(|ts| {
ts.with_timezone(&chrono::Local)
.format("%Y-%m-%d %H:%M:%S")
.to_string()
})
.unwrap_or_else(|| unix_seconds.to_string())
}
let build_view = |data: &PeerIpv6DataRaw| {
let mut nodes = Vec::with_capacity(data.routes.len() + 1);
nodes.push(PeerIpv6NodeRow {
peer_id: data.node_info.peer_id,
hostname: data.node_info.hostname.clone(),
inst_id: data.node_info.inst_id.clone(),
ipv4: data.node_info.ipv4_addr.clone(),
public_ipv6_addr: fmt_ipv6_inet(data.node_info.public_ipv6_addr),
provider_prefix: fmt_ipv6_inet(data.node_info.ipv6_public_addr_prefix),
});
nodes.extend(data.routes.iter().map(|route| {
PeerIpv6NodeRow {
peer_id: route.peer_id,
hostname: route.hostname.clone(),
inst_id: route.inst_id.clone(),
ipv4: route
.ipv4_addr
.map(|ipv4| ipv4.to_string())
.unwrap_or_else(|| "-".to_string()),
public_ipv6_addr: fmt_ipv6_inet(route.public_ipv6_addr),
provider_prefix: fmt_ipv6_inet(route.ipv6_public_addr_prefix),
}
}));
nodes.sort_by_key(|row| {
(
row.peer_id != data.node_info.peer_id,
row.peer_id,
row.inst_id.clone(),
)
});
let local_provider = data.provider_info.provider_prefix.map(|provider_prefix| {
let mut leases = data
.provider_info
.provider_leases
.iter()
.map(|lease| ProviderLeaseRow {
peer_id: lease.peer_id,
inst_id: lease.inst_id.clone(),
leased_addr: fmt_ipv6_inet(lease.leased_addr),
valid_until: fmt_valid_until(lease.valid_until_unix_seconds),
reused: lease.reused,
})
.collect::<Vec<_>>();
leases.sort_by_key(|lease| {
(
lease.peer_id,
lease.inst_id.clone(),
lease.leased_addr.clone(),
)
});
ProviderLeaseSection {
provider_prefix: provider_prefix.to_string(),
leases,
}
});
PeerIpv6View {
nodes,
local_provider,
}
};
let results = self
.collect_instance_results(|handler| Box::pin(handler.fetch_peer_ipv6_data()))
.await?;
if self.verbose || *self.output_format == OutputFormat::Json {
return self.print_json_results(
results
.into_iter()
.map(|result| result.map(|data| build_view(&data)))
.collect(),
);
}
self.print_results(&results, |data| {
let view = build_view(data);
print_output(&view.nodes, self.output_format, &[], &[], self.no_trunc)?;
if let Some(local_provider) = view.local_provider {
println!();
println!("Local provider prefix: {}", local_provider.provider_prefix);
if local_provider.leases.is_empty() {
println!("No active provider leases");
} else {
print_output(
&local_provider.leases,
self.output_format,
&[],
&[],
self.no_trunc,
)?;
}
}
Ok(())
})
}
async fn handle_route_dump(&self) -> Result<(), Error> {
let results = self
.collect_instance_results(|handler| Box::pin(handler.fetch_route_dump()))
@@ -2652,6 +2829,9 @@ async fn main() -> Result<(), Error> {
Some(PeerSubCommand::List) => {
handler.handle_peer_list().await?;
}
Some(PeerSubCommand::Ipv6) => {
handler.handle_peer_ipv6().await?;
}
Some(PeerSubCommand::ListForeign { trusted_keys }) => {
handler.handle_foreign_network_list(*trusted_keys).await?;
}
+4
View File
@@ -1299,6 +1299,10 @@ impl PeerManager {
self.get_route().get_my_public_ipv6_addr().await
}
pub async fn get_local_public_ipv6_info(&self) -> instance::ListPublicIpv6InfoResponse {
self.get_route().get_local_public_ipv6_info().await
}
pub async fn dump_route(&self) -> String {
self.get_route().dump().await
}
+34
View File
@@ -369,6 +369,7 @@ impl From<RoutePeerInfo> for crate::proto::api::instance::Route {
ipv6_addr: val.ipv6_addr,
public_ipv6_addr: val.ipv6_public_addr_lease,
ipv6_public_addr_prefix: val.ipv6_public_addr_prefix,
}
}
}
@@ -3953,6 +3954,39 @@ impl Route for PeerRoute {
self.public_ipv6_service.my_addr()
}
async fn get_local_public_ipv6_info(
&self,
) -> crate::proto::api::instance::ListPublicIpv6InfoResponse {
let Some((provider, leases)) = self.public_ipv6_service.local_provider_state() else {
return crate::proto::api::instance::ListPublicIpv6InfoResponse::default();
};
crate::proto::api::instance::ListPublicIpv6InfoResponse {
provider_prefix: Some(
Ipv6Inet::new(
provider.prefix.first_address(),
provider.prefix.network_length(),
)
.unwrap()
.into(),
),
provider_leases: leases
.into_iter()
.map(|lease| crate::proto::api::instance::PublicIpv6LeaseInfo {
peer_id: lease.peer_id,
inst_id: lease.inst_id.to_string(),
leased_addr: Some(lease.addr.into()),
valid_until_unix_seconds: lease
.valid_until
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64,
reused: lease.reused,
})
.collect(),
}
}
async fn get_peer_id_by_ipv4(&self, ipv4_addr: &Ipv4Addr) -> Option<PeerId> {
let route_table = &self.service_impl.route_table;
if let Some(p) = route_table.ipv4_peer_id_map.get(ipv4_addr) {
+14
View File
@@ -641,6 +641,20 @@ impl PublicIpv6Service {
pub(crate) fn my_addr(&self) -> Option<Ipv6Inet> {
*self.my_addr_cache.lock().unwrap()
}
pub(crate) fn local_provider_state(
&self,
) -> Option<(PublicIpv6Provider, Vec<PublicIpv6ProviderLease>)> {
let provider = self.selected_provider()?;
if provider.peer_id != self.my_peer_id() {
return None;
}
let state = Self::prune_expired_leases(&provider, self.current_provider_state());
let mut leases = state.leases.into_values().collect::<Vec<_>>();
leases.sort_by_key(|lease| (lease.peer_id, lease.inst_id, lease.addr));
Some((provider, leases))
}
}
#[derive(Clone)]
+10 -3
View File
@@ -9,9 +9,12 @@ use std::{
use crate::{
common::{PeerId, global_ctx::NetworkIdentity},
proto::peer_rpc::{
ForeignNetworkRouteInfoEntry, ForeignNetworkRouteInfoKey, PeerIdentityType,
RouteForeignNetworkInfos, RouteForeignNetworkSummary, RoutePeerInfo,
proto::{
api::instance::ListPublicIpv6InfoResponse,
peer_rpc::{
ForeignNetworkRouteInfoEntry, ForeignNetworkRouteInfoKey, PeerIdentityType,
RouteForeignNetworkInfos, RouteForeignNetworkSummary, RoutePeerInfo,
},
},
};
@@ -102,6 +105,10 @@ pub trait Route {
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
}
+13 -3
View File
@@ -13,9 +13,9 @@ use crate::{
GetWhitelistRequest, GetWhitelistResponse, ListCredentialsRequest,
ListCredentialsResponse, ListForeignNetworkRequest, ListForeignNetworkResponse,
ListGlobalForeignNetworkRequest, ListGlobalForeignNetworkResponse, ListPeerRequest,
ListPeerResponse, ListRouteRequest, ListRouteResponse, PeerInfo, PeerManageRpc,
RevokeCredentialRequest, RevokeCredentialResponse, ShowNodeInfoRequest,
ShowNodeInfoResponse,
ListPeerResponse, ListPublicIpv6InfoRequest, ListPublicIpv6InfoResponse,
ListRouteRequest, ListRouteResponse, PeerInfo, PeerManageRpc, RevokeCredentialRequest,
RevokeCredentialResponse, ShowNodeInfoRequest, ShowNodeInfoResponse,
},
rpc_types::{self, controller::BaseController},
},
@@ -99,6 +99,16 @@ impl PeerManageRpc for PeerManagerRpcService {
Ok(reply)
}
async fn list_public_ipv6_info(
&self,
_: BaseController,
_request: ListPublicIpv6InfoRequest,
) -> Result<ListPublicIpv6InfoResponse, rpc_types::error::Error> {
Ok(weak_upgrade(&self.peer_manager)?
.get_local_public_ipv6_info()
.await)
}
async fn list_route(
&self,
_: BaseController,
+18
View File
@@ -82,6 +82,7 @@ message Route {
common.Ipv6Inet ipv6_addr = 15;
common.Ipv6Inet public_ipv6_addr = 16;
common.Ipv6Inet ipv6_public_addr_prefix = 17;
}
message PeerRoutePair {
@@ -109,6 +110,21 @@ message ShowNodeInfoRequest { InstanceIdentifier instance = 1; }
message ShowNodeInfoResponse { NodeInfo node_info = 1; }
message PublicIpv6LeaseInfo {
uint32 peer_id = 1;
string inst_id = 2;
common.Ipv6Inet leased_addr = 3;
int64 valid_until_unix_seconds = 4;
bool reused = 5;
}
message ListPublicIpv6InfoRequest { InstanceIdentifier instance = 1; }
message ListPublicIpv6InfoResponse {
common.Ipv6Inet provider_prefix = 1;
repeated PublicIpv6LeaseInfo provider_leases = 2;
}
message ListRouteRequest { InstanceIdentifier instance = 1; }
message ListRouteResponse { repeated Route routes = 1; }
@@ -170,6 +186,8 @@ message GetForeignNetworkSummaryResponse {
service PeerManageRpc {
rpc ListPeer(ListPeerRequest) returns (ListPeerResponse);
rpc ListPublicIpv6Info(ListPublicIpv6InfoRequest)
returns (ListPublicIpv6InfoResponse);
rpc ListRoute(ListRouteRequest) returns (ListRouteResponse);
rpc DumpRoute(DumpRouteRequest) returns (DumpRouteResponse);
rpc ListForeignNetwork(ListForeignNetworkRequest)
+15 -1
View File
@@ -3,7 +3,10 @@ use std::sync::Arc;
use crate::{
instance_manager::NetworkInstanceManager,
proto::{
api::instance::{self, ListPeerRequest, ListPeerResponse, PeerManageRpc},
api::instance::{
self, ListPeerRequest, ListPeerResponse, ListPublicIpv6InfoRequest,
ListPublicIpv6InfoResponse, PeerManageRpc,
},
rpc_types::controller::BaseController,
},
};
@@ -34,6 +37,17 @@ impl PeerManageRpc for PeerManageRpcService {
.await
}
async fn list_public_ipv6_info(
&self,
ctrl: Self::Controller,
req: ListPublicIpv6InfoRequest,
) -> crate::proto::rpc_types::error::Result<ListPublicIpv6InfoResponse> {
super::get_instance_service(&self.instance_manager, &req.instance)?
.get_peer_manage_service()
.list_public_ipv6_info(ctrl, req)
.await
}
async fn list_route(
&self,
ctrl: Self::Controller,
+26
View File
@@ -807,6 +807,32 @@ pub async fn public_ipv6_auto_addr_end_to_end() {
.into()
)
);
let provider_info = provider
.get_peer_manager()
.get_local_public_ipv6_info()
.await;
let client_peer_id = client.get_peer_manager().get_my_info().await.peer_id;
assert_eq!(
provider_info.provider_prefix,
Some(
cidr::Ipv6Inet::new(
provider_prefix.first_address(),
provider_prefix.network_length()
)
.unwrap()
.into()
)
);
assert_eq!(provider_info.provider_leases.len(), 1);
assert_eq!(provider_info.provider_leases[0].peer_id, client_peer_id);
assert_eq!(
provider_info.provider_leases[0].inst_id,
client_id.to_string()
);
assert_eq!(
provider_info.provider_leases[0].leased_addr,
Some(leased.into())
);
assert!(
leased.address().segments()[0] & 0xfe00 != 0xfc00,
"leased address should not be unique-local: {leased}"