wip - dvpn directory cache
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
Json, Router,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use serde::Deserialize;
|
||||
use tracing::instrument;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::http::{error::{HttpError, HttpResult}, models::{Gateway}, state::AppState};
|
||||
use crate::http::models::DVpnGateway;
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", axum::routing::get(gateways))
|
||||
.route(
|
||||
"/country/:two_letter_country_code",
|
||||
axum::routing::get(gateways_by_country),
|
||||
)
|
||||
}
|
||||
|
||||
async fn get_gateways_from_cache(
|
||||
State(state): State<AppState>,
|
||||
) -> Vec<DVpnGateway> {
|
||||
let db = state.db_pool();
|
||||
let res = state.cache().get_gateway_list(db).await;
|
||||
|
||||
// TODO: parse
|
||||
let MINIMUM_NYM_NODE_VERSION = "1.6.2";
|
||||
|
||||
// TODO: cache the output of this filter
|
||||
let output: Vec<DVpnGateway> = res.iter()
|
||||
.filter(|g| {
|
||||
// gateways must be bonded and not blacklisted
|
||||
if !g.bonded {
|
||||
return false;
|
||||
}
|
||||
|
||||
// gateways must meet minimum semver
|
||||
// if g.self_described.something_something < MINIMUM_NYM_NODE_VERSION {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
true
|
||||
})
|
||||
.map(|d| d.try_into())
|
||||
.filter_map(Result::ok)
|
||||
.filter(|g: &DVpnGateway| {
|
||||
// gateways must have a country
|
||||
g.location.two_letter_iso_country_code.len() == 2
|
||||
})
|
||||
// TODO: sort by two-letter country code, then by identity key
|
||||
// .sorted_by(...)
|
||||
.collect();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
path = "/directory/gateways",
|
||||
summary = "Gets available entry and exit gateways from the Nym network directory",
|
||||
context_path = "/dvpn/v1",
|
||||
responses(
|
||||
(status = 200, body = Vec<DVpnGateway>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip_all)]
|
||||
async fn gateways(
|
||||
state: State<AppState>,
|
||||
) -> HttpResult<Json<Vec<DVpnGateway>>> {
|
||||
let res = get_gateways_from_cache(state).await;
|
||||
Ok(Json(res))
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // clippy doesn't detect usage in utoipa macros
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
struct TwoLetterCountryCodeParam {
|
||||
#[param(minimum = 2, maximum = 2)]
|
||||
two_letter_country_code: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "dVPN Directory Cache",
|
||||
get,
|
||||
params(
|
||||
TwoLetterCountryCodeParam
|
||||
),
|
||||
path = "/directory/gateways/country/{two_letter_country_code}",
|
||||
summary = "Gets available gateways from the Nym network directory by country",
|
||||
context_path = "/dvpn/v1",
|
||||
responses(
|
||||
(status = 200, body = Vec<DVpnGateway>)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip(state), fields(two_letter_country_code = two_letter_country_code))]
|
||||
async fn gateways_by_country(
|
||||
Path(TwoLetterCountryCodeParam { two_letter_country_code}): Path<TwoLetterCountryCodeParam>,
|
||||
state: State<AppState>,
|
||||
) -> HttpResult<Json<Vec<DVpnGateway>>> {
|
||||
match two_letter_country_code.len() {
|
||||
2 => {
|
||||
let res = get_gateways_from_cache(state).await;
|
||||
Ok(Json(res))
|
||||
}
|
||||
_ => Err(HttpError::invalid_input("Only two letter country code is allowed")),
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ pub(crate) mod nym_nodes;
|
||||
pub(crate) mod services;
|
||||
pub(crate) mod summary;
|
||||
pub(crate) mod testruns;
|
||||
pub(crate) mod dvpn;
|
||||
|
||||
pub(crate) struct RouterBuilder {
|
||||
unfinished_router: Router<AppState>,
|
||||
@@ -44,6 +45,10 @@ impl RouterBuilder {
|
||||
"/explorer/v3",
|
||||
Router::new().nest("/nym-nodes", nym_nodes::routes()),
|
||||
)
|
||||
.nest(
|
||||
"/dvpn/v1",
|
||||
Router::new().nest("/directory/gateways", dvpn::routes()),
|
||||
)
|
||||
.nest(
|
||||
"/internal",
|
||||
Router::new().nest("/testruns", testruns::routes()),
|
||||
|
||||
@@ -23,6 +23,140 @@ pub struct Gateway {
|
||||
pub config_score: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub enum Role {
|
||||
// a properly active mixnode
|
||||
Mixnode {
|
||||
layer: u8,
|
||||
},
|
||||
|
||||
#[serde(alias = "entry", alias = "gateway")]
|
||||
EntryGateway,
|
||||
|
||||
#[serde(alias = "exit")]
|
||||
ExitGateway,
|
||||
|
||||
// equivalent of node that's in rewarded set but not in the inactive set
|
||||
Standby,
|
||||
|
||||
Inactive,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct BuildInformation {
|
||||
pub build_version: String,
|
||||
pub commit_branch: String,
|
||||
pub commit_sha: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct IpPacketRouter {
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct Authenticator {
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct EntryInformation {
|
||||
pub hostname: Option<String>,
|
||||
pub ws_port: u16,
|
||||
pub wss_port: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct Location {
|
||||
pub two_letter_iso_country_code: String,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct DVpnGateway {
|
||||
pub identity_key: String,
|
||||
pub name: String,
|
||||
pub ip_packet_router: Option<IpPacketRouter>,
|
||||
pub authenticator: Option<Authenticator>,
|
||||
pub location: Location,
|
||||
pub last_probe: Option<serde_json::Value>,
|
||||
pub ip_addresses: Vec<String>,
|
||||
pub mix_port: u16,
|
||||
pub role: Role,
|
||||
pub entry: EntryInformation,
|
||||
// The performance data here originates from the nym-api, and is effectively mixnet performance
|
||||
// at the time of writing this
|
||||
pub performance: u8,
|
||||
pub build_information: Option<BuildInformation>,
|
||||
}
|
||||
|
||||
impl TryFrom<&Gateway> for DVpnGateway
|
||||
{
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &Gateway) -> Result<Self, Self::Error> {
|
||||
|
||||
// TODO: try to parse out values from gateway and fail when unable to parse
|
||||
|
||||
// TODO: polyfill `last_probe`, see below from VPN API
|
||||
/**
|
||||
|
||||
const last_testrun_utc = item.last_testrun_utc;
|
||||
|
||||
const last_probe: DirectoryGatewayProbe | undefined =
|
||||
last_testrun_utc && item.last_probe_result?.outcome
|
||||
? {
|
||||
last_updated_utc: last_testrun_utc,
|
||||
outcome: item.last_probe_result.outcome,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
//
|
||||
// reshape test probe
|
||||
//
|
||||
if (
|
||||
last_probe?.outcome?.wg &&
|
||||
isDirectoryGatewayProbeOutcome_WG_V2(last_probe?.outcome?.wg) &&
|
||||
last_probe?.outcome?.wg?.can_handshake === undefined
|
||||
) {
|
||||
last_probe.outcome.wg = {
|
||||
...last_probe.outcome.wg,
|
||||
can_handshake: last_probe.outcome.wg.can_handshake_v4,
|
||||
can_resolve_dns: last_probe.outcome.wg.can_resolve_dns_v4,
|
||||
ping_hosts_performance:
|
||||
last_probe.outcome.wg.ping_hosts_performance_v4,
|
||||
ping_ips_performance: last_probe.outcome.wg.ping_ips_performance_v4,
|
||||
};
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
Ok(Self {
|
||||
identity_key: value.gateway_identity_key.clone(),
|
||||
name: value.description.moniker.clone(),
|
||||
ip_packet_router: None, // value.ip_packet_router,
|
||||
authenticator: None, // value.authenticator,
|
||||
location: Location {
|
||||
latitude: 0.0f64,
|
||||
longitude: 0.0f64,
|
||||
two_letter_iso_country_code: "".to_string(),
|
||||
},
|
||||
last_probe: value.last_probe_result.clone(),
|
||||
ip_addresses: vec![], // value.ip_addresses,
|
||||
mix_port: 0u16, // value.mix_port,
|
||||
role: Role::EntryGateway, // value.role,
|
||||
entry: EntryInformation {
|
||||
ws_port: 0u16,
|
||||
wss_port: None,
|
||||
hostname: None,
|
||||
},
|
||||
performance: value.performance,
|
||||
build_information: None, // value.build_information,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub struct GatewaySkinny {
|
||||
pub gateway_identity_key: String,
|
||||
|
||||
Reference in New Issue
Block a user