Files
nym/explorer-api/src/ping/http.rs
T
Mark Sinclair e52fe65985 Network Explorer: updates to API and UI to show the active set (#1056)
* Add identicons package

* Tidy up styling and move methods into component directories with better naming

* Add mixnode status colours to theme

* Mixnode status and icon components

* Add status to mixnode types

* Add API method to get mixnode details

* Add mixnode details to state

* Add status and name+description section to mixnode detail page

* Wrap with div instead of p

* Limit width of description and link to new tab

* Limit length of link button and truncate with elipsis

* Replace `filter` with `find`

* Move mix node detail components to a location that is better named

* Refactor mixnode detail state and separate into an independent context from main state.

This prevents the mixnode detail page from showing stale data when switching between mix nodes.

* Tidy up mixnode detail page adding new state provider and a guard component to handle loading, error and not found states

* Layout changes to mixnode description header section

* Add methods to Explorer API client to get a mixnode by id, active set by status and overview summary

* Add color prop to StatsCard and make count optional

* Add optional start and end children to TableToolbar

* Tidy up naming

* Add summary overview and getting mixnodes by active set status to main state

* Add mix node status overview cards

* Add mix node status to routes

* Mixnode list has a dropdown component to select the active set status

* Clean up caching code

* Add resource to get a single mixnode by id

* Add API resources to get `active`, `inactive` and `standby` mixnodes

* Add mixnode summary to API

* Add overview summary endpoint to API

* Fix OpenAPI/swagger base url

* Make clippy happy

* Add method to get validators

* Add methods to get active and rewarded mixnodes

* Fix naming

* Move client creation to crate root

* Move cache to module

* Delete unused files

* Add validators API resource

* Add gateways API resource

* Move tasks to crate root

* Add new HTTP resources for validators and gateways to routes

* Tidy up naming and locations for mixnodes

* Add validator and gateways to state, and tidy up naming

* Add gateways and validator modules to main

* Overview shows validator and gateway summaries from state

* Bundle variable weight Open Sans fonts

* Fix up font weights and sizes

* Fix up typing

* Fix up social icons

* Fix navbar colour

* Fix paper colour in dark mode and border radius

* Fix up stats card

* Tidy up Nym icons

* Fix up overview

* Fix up spacing and padding for overview

* Add light mode shades that are darker for mixnode status values

* Review feedback
2022-01-21 11:28:59 +00:00

179 lines
5.4 KiB
Rust

use std::collections::HashMap;
use std::net::{SocketAddr, ToSocketAddrs};
use std::time::Duration;
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use mixnet_contract_common::MixNodeBond;
use crate::ping::models::PingResponse;
use crate::state::ExplorerApiStateContext;
const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10);
pub fn ping_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: index]
}
#[openapi(tag = "ping")]
#[get("/<pubkey>")]
pub(crate) async fn index(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Option<Json<PingResponse>> {
match state.inner.ping.clone().get(pubkey).await {
Some(cache_value) => {
trace!("Returning cached value for {}", pubkey);
Some(Json(PingResponse {
pending: cache_value.pending,
ports: cache_value.ports,
}))
}
None => {
trace!("No cache value for {}", pubkey);
match state.inner.get_mix_node(pubkey).await {
Some(bond) => {
// set status to pending, so that any HTTP requests are pending
state.inner.ping.set_pending(pubkey).await;
// do the check
let ports = Some(port_check(&bond).await);
trace!("Tested mix node {}: {:?}", pubkey, ports);
let response = PingResponse {
ports,
pending: false,
};
// cache for 1 min
trace!("Caching value for {}", pubkey);
state.inner.ping.set(pubkey, response.clone()).await;
// return response
Some(Json(response))
}
None => None,
}
}
}
}
async fn port_check(bond: &MixNodeBond) -> HashMap<u16, bool> {
let mut ports: HashMap<u16, bool> = HashMap::new();
let ports_to_test = vec![
bond.mix_node.http_api_port,
bond.mix_node.mix_port,
bond.mix_node.verloc_port,
];
trace!(
"Testing mix node {} on ports {:?}...",
bond.mix_node.identity_key,
ports_to_test
);
for port in ports_to_test {
ports.insert(port, do_port_check(&bond.mix_node.host, port).await);
}
ports
}
fn sanitize_and_resolve_host(host: &str, port: u16) -> Option<SocketAddr> {
// trim the host
let trimmed_host = host.trim();
// host must be at least one non-whitespace character
if trimmed_host.is_empty() {
return None;
}
// the host string should hopefully parse and resolve into a valid socket address
let parsed_host = format!("{}:{}", trimmed_host, port);
match parsed_host.to_socket_addrs() {
Ok(mut addrs) => addrs.next(),
Err(e) => {
warn!(
"Failed to resolve {}:{} -> {}. Error: {}",
host, port, parsed_host, e
);
None
}
}
}
async fn do_port_check(host: &str, port: u16) -> bool {
match sanitize_and_resolve_host(host, port) {
Some(addr) => match tokio::time::timeout(
CONNECTION_TIMEOUT_SECONDS,
tokio::net::TcpStream::connect(addr),
)
.await
{
Ok(Ok(_stream)) => {
// didn't timeout and tcp stream is open
trace!("Successfully pinged {}", addr);
true
}
Ok(Err(_stream_err)) => {
warn!("{} ping failed {:}", addr, _stream_err);
// didn't timeout but couldn't open tcp stream
false
}
Err(_timeout) => {
// timed out
warn!("{} timed out {:}", addr, _timeout);
false
}
},
None => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_host_with_valid_ip_address_returns_some() {
assert!(sanitize_and_resolve_host("8.8.8.8", 1234).is_some());
assert!(sanitize_and_resolve_host("2001:4860:4860::8888", 1234).is_some());
}
#[test]
fn resolve_host_with_valid_hostname_returns_some() {
assert!(sanitize_and_resolve_host("nymtech.net", 1234).is_some());
}
#[test]
fn resolve_host_with_malformed_ip_address_returns_none() {
// these are invalid ip addresses
assert!(sanitize_and_resolve_host("192.168.1.999", 1234).is_none());
assert!(sanitize_and_resolve_host("10.999.999.999", 1234).is_none());
}
#[test]
fn resolve_host_with_unknown_hostname_returns_none() {
assert!(sanitize_and_resolve_host(
"some-unknown-hostname-that-will-never-resolve.nymtech.net",
1234
)
.is_none());
}
#[test]
fn resolve_host_with_bad_strings_return_none() {
assert!(sanitize_and_resolve_host("", 1234).is_none());
assert!(sanitize_and_resolve_host(" ", 1234).is_none());
assert!(sanitize_and_resolve_host(" 🤘 ", 1234).is_none());
assert!(sanitize_and_resolve_host("🤘", 1234).is_none());
assert!(sanitize_and_resolve_host("@", 1234).is_none());
assert!(sanitize_and_resolve_host("*", 1234).is_none());
}
}