mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-08-08 23:25:43 +00:00
Update node locations when they come in, and get the real IP addr of nodes
This commit is contained in:
@@ -153,7 +153,7 @@ impl Aggregator {
|
||||
},
|
||||
ToAggregator::FromWebsocket(conn_id, FromWebsocket::Add { message_id, ip, node, genesis_hash }) => {
|
||||
// Don't bother doing anything else if we're disconnected, since we'll force the
|
||||
// ndoe to reconnect anyway when the backend does:
|
||||
// node to reconnect anyway when the backend does:
|
||||
if !connected_to_telemetry_core { continue }
|
||||
|
||||
// Generate a new "local ID" for messages from this connection:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
mod aggregator;
|
||||
mod connection;
|
||||
mod real_ip;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use structopt::StructOpt;
|
||||
use http::Uri;
|
||||
@@ -11,6 +12,7 @@ use warp::Filter;
|
||||
use warp::filters::ws;
|
||||
use common::{json, node, log_level::LogLevel};
|
||||
use aggregator::{ Aggregator, FromWebsocket };
|
||||
use real_ip::real_ip;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||
@@ -82,8 +84,8 @@ async fn start_server(opts: Opts) -> anyhow::Result<()> {
|
||||
let ws_route =
|
||||
warp::path("submit")
|
||||
.and(warp::ws())
|
||||
.and(warp::filters::addr::remote())
|
||||
.map(move |ws: ws::Ws, addr: Option<SocketAddr>| {
|
||||
.and(real_ip())
|
||||
.map(move |ws: ws::Ws, addr: Option<IpAddr>| {
|
||||
// Send messages from the websocket connection to this sink
|
||||
// to have them pass to the aggregator.
|
||||
let tx_to_aggregator = aggregator.subscribe_node();
|
||||
@@ -106,7 +108,7 @@ async fn start_server(opts: Opts) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
/// This takes care of handling messages from an established socket connection.
|
||||
async fn handle_websocket_connection<S>(mut websocket: ws::WebSocket, mut tx_to_aggregator: S, addr: Option<SocketAddr>) -> (S, ws::WebSocket)
|
||||
async fn handle_websocket_connection<S>(mut websocket: ws::WebSocket, mut tx_to_aggregator: S, addr: Option<IpAddr>) -> (S, ws::WebSocket)
|
||||
where S: futures::Sink<FromWebsocket, Error = anyhow::Error> + Unpin
|
||||
{
|
||||
// This could be a oneshot channel, but it's useful to be able to clone
|
||||
@@ -155,7 +157,7 @@ async fn handle_websocket_connection<S>(mut websocket: ws::WebSocket, mut tx_to_
|
||||
if let node::Payload::SystemConnected(info) = payload {
|
||||
let _ = tx_to_aggregator.send(FromWebsocket::Add {
|
||||
message_id,
|
||||
ip: addr.map(|a| a.ip()),
|
||||
ip: addr,
|
||||
node: info.node,
|
||||
genesis_hash: info.genesis_hash,
|
||||
}).await;
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
use std::net::{ SocketAddr, IpAddr };
|
||||
use warp::filters::header;
|
||||
use warp::filters::addr;
|
||||
use warp::Filter;
|
||||
|
||||
/**
|
||||
A warp filter to extract the "real" IP address of the connection by looking at headers
|
||||
set by proxies (this is inspired by Actix Web's implementation of the feature).
|
||||
|
||||
First, check for the standardised "Forwarded" header. This looks something like:
|
||||
|
||||
"Forwarded: for=12.34.56.78;host=example.com;proto=https, for=23.45.67.89"
|
||||
|
||||
Each proxy can append to this comma separated list of forwarded-details. We'll look for
|
||||
the first "for" address and try to decode that.
|
||||
|
||||
If this doesn't yield a result, look for the non-standard but common X-Forwarded-For header,
|
||||
which contains a comma separated list of addresses; each proxy in the potential chain possibly
|
||||
appending one to the end. So, take the first of these if it exists.
|
||||
|
||||
If still no luck, look for the X-Real-IP header, which we expect to contain a single IP address.
|
||||
|
||||
If that _still_ doesn't work, fall back to the socket address of the connection.
|
||||
|
||||
Return `None` if all of this fails to yield an address.
|
||||
*/
|
||||
pub fn real_ip() -> impl warp::Filter<Extract = (Option<IpAddr>,), Error = warp::Rejection> + Clone {
|
||||
header::optional("forwarded")
|
||||
.and(header::optional("x-forwarded-for"))
|
||||
.and(header::optional("x-real-ip"))
|
||||
.and(addr::remote())
|
||||
.map(|forwarded: Option<String>, forwarded_for: Option<String>, real_ip: Option<String>, addr: Option<SocketAddr>| {
|
||||
let realip = forwarded.as_ref().and_then(|val| get_first_addr_from_forwarded_header(val))
|
||||
.or_else(|| {
|
||||
// fall back to X-Forwarded-For
|
||||
forwarded_for.as_ref().and_then(|val| get_first_addr_from_x_forwarded_for_header(val))
|
||||
})
|
||||
.or_else(|| {
|
||||
// fall back to X-Real-IP
|
||||
real_ip.as_ref().map(|val| val.trim())
|
||||
})
|
||||
.and_then(|ip| {
|
||||
// Trim the port if it exists
|
||||
ip.split(":").next()
|
||||
})
|
||||
.and_then(|ip| {
|
||||
// Attempt to parse to a socket address
|
||||
ip.parse::<IpAddr>().ok()
|
||||
})
|
||||
// Fall back to local IP address if the above fails
|
||||
.or(addr.map(|a| a.ip()));
|
||||
|
||||
realip
|
||||
})
|
||||
}
|
||||
|
||||
/// Follow https://datatracker.ietf.org/doc/html/rfc7239 to decode the Forwarded header value.
|
||||
/// Roughly, proxies can add new sets of values by appending a comma to the existing list
|
||||
/// (so we have something like "values1, values2, values3" from proxy1, proxy2 and proxy3 for
|
||||
/// instance) and then the valeus themselves are ';' separated name=value pairs. The value in each
|
||||
/// pair may or may not be surrounded in double quotes.
|
||||
///
|
||||
/// Examples from the RFC:
|
||||
///
|
||||
/// Forwarded: for="_gazonk"
|
||||
/// Forwarded: For="[2001:db8:cafe::17]:4711"
|
||||
/// Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43
|
||||
/// Forwarded: for=192.0.2.43, for=198.51.100.17
|
||||
fn get_first_addr_from_forwarded_header(value: &str) -> Option<&str> {
|
||||
let first_values = value.split(',').next()?;
|
||||
|
||||
for pair in first_values.split(';') {
|
||||
let mut parts = pair.trim().splitn(2, '=');
|
||||
let key = parts.next()?;
|
||||
let value = parts.next()?;
|
||||
|
||||
if key.to_lowercase() == "for" {
|
||||
// trim double quotes if they surround the value:
|
||||
let value = if value.starts_with('"') && value.ends_with('"') {
|
||||
&value[1..value.len() - 1]
|
||||
} else {
|
||||
value
|
||||
};
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_first_addr_from_x_forwarded_for_header(value: &str) -> Option<&str> {
|
||||
value.split(",").map(|val| val.trim()).next()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_addr_from_forwarded_rfc_examples() {
|
||||
let examples = vec![
|
||||
(r#"for="_gazonk""#, "_gazonk"),
|
||||
(r#"For="[2001:db8:cafe::17]:4711""#, "[2001:db8:cafe::17]:4711"),
|
||||
(r#"for=192.0.2.60;proto=http;by=203.0.113.43"#, "192.0.2.60"),
|
||||
(r#"for=192.0.2.43, for=198.51.100.17"#, "192.0.2.43"),
|
||||
];
|
||||
|
||||
for (value, expected) in examples {
|
||||
assert_eq!(
|
||||
get_first_addr_from_forwarded_header(value),
|
||||
Some(expected),
|
||||
"Header value: {}", value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user