From 34dff4e2d25be155c2a4288a802dd4e7933a1ac1 Mon Sep 17 00:00:00 2001 From: aggstam Date: Thu, 6 Oct 2022 20:44:45 +0300 Subject: [PATCH] p2pnet: validate external address of inbound connection --- src/net/channel.rs | 1 + src/net/hosts.rs | 73 +++++++++++++++++++++++++++- src/net/message.rs | 12 +++++ src/net/protocol/protocol_address.rs | 32 ++++++++++-- src/net/protocol/protocol_seed.rs | 8 +-- 5 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/net/channel.rs b/src/net/channel.rs index f974a7ddf..22774d184 100644 --- a/src/net/channel.rs +++ b/src/net/channel.rs @@ -279,6 +279,7 @@ impl Channel { message_subsystem.add_dispatch::().await; message_subsystem.add_dispatch::().await; message_subsystem.add_dispatch::().await; + message_subsystem.add_dispatch::().await; } /// Convenience function that returns the Message Subsystem. diff --git a/src/net/hosts.rs b/src/net/hosts.rs index 5ced68952..bb456bb31 100644 --- a/src/net/hosts.rs +++ b/src/net/hosts.rs @@ -48,7 +48,20 @@ impl Hosts { } } - // TODO: add single host store, which also checks that resolved ips are the same as the connection + /// Add a new hosts external adders to the host list, after filtering and verifying + /// the address url resolves to the provided connection address. + pub async fn store_ext(&self, connection_addr: Url, input_addrs: Vec) { + let addrs = if !self.localnet { + let filtered = filter_localnet(input_addrs); + let filtered = filter_invalid(&self.ipv4_range, &self.ipv6_range, filtered); + filter_non_resolving(connection_addr, filtered) + } else { + input_addrs + }; + for addr in addrs { + self.addrs.lock().await.insert(addr); + } + } /// Return the list of hosts. pub async fn load_all(&self) -> Vec { @@ -135,6 +148,64 @@ fn filter_invalid( filtered } +/// Auxiliary function to filter unresolvable hosts, based on provided connection addr (excluding onion). +fn filter_non_resolving(connection_addr: Url, input_addrs: Vec) -> Vec { + let connection_domain = connection_addr.domain().unwrap(); + // Validate connection onion domain + if connection_domain.ends_with(".onion") && !is_valid_onion(connection_domain) { + return vec![] + } + + // Retrieve connection IPs + let mut ipv4_range = vec![]; + let mut ipv6_range = vec![]; + for i in connection_addr.socket_addrs(|| None).unwrap() { + match i.ip() { + IpAddr::V4(a) => { + ipv4_range.push(a); + } + IpAddr::V6(a) => { + ipv6_range.push(a); + } + } + } + + // Filter input addresses + let mut filtered = vec![]; + for addr in input_addrs { + // Keep valid onion domains + let addr_domain = addr.domain().unwrap(); + if addr_domain.ends_with(".onion") && addr_domain == connection_domain { + filtered.push(addr.clone()); + continue + } + + // Checking IP validity + let mut valid = true; + let socket_addrs = addr.socket_addrs(|| None).unwrap(); + for i in socket_addrs { + match i.ip() { + IpAddr::V4(a) => { + if !ipv4_range.contains(&a) { + valid = false; + break + } + } + IpAddr::V6(a) => { + if !ipv6_range.contains(&a) { + valid = false; + break + } + } + } + } + if valid { + filtered.push(addr.clone()); + } + } + filtered +} + /// Auxiliary function to validate an onion. fn is_valid_onion(onion: &str) -> bool { let onion = match onion.strip_suffix(".onion") { diff --git a/src/net/message.rs b/src/net/message.rs index 5a98b6582..e7eb847d3 100644 --- a/src/net/message.rs +++ b/src/net/message.rs @@ -37,6 +37,12 @@ pub struct AddrsMessage { pub addrs: Vec, } +/// Sends external address information to inbound connection. +#[derive(SerialEncodable, SerialDecodable)] +pub struct ExtAddrsMessage { + pub ext_addrs: Vec, +} + /// Requests version information of outbound connection. #[derive(SerialEncodable, SerialDecodable)] pub struct VersionMessage { @@ -74,6 +80,12 @@ impl Message for AddrsMessage { } } +impl Message for ExtAddrsMessage { + fn name() -> &'static str { + "extaddr" + } +} + impl Message for VersionMessage { fn name() -> &'static str { "version" diff --git a/src/net/protocol/protocol_address.rs b/src/net/protocol/protocol_address.rs index eebb15d2f..89da5b630 100644 --- a/src/net/protocol/protocol_address.rs +++ b/src/net/protocol/protocol_address.rs @@ -20,6 +20,7 @@ const SEND_ADDR_SLEEP_SECONDS: u64 = 900; pub struct ProtocolAddress { channel: ChannelPtr, addrs_sub: MessageSubscription, + ext_addrs_sub: MessageSubscription, get_addrs_sub: MessageSubscription, hosts: HostsPtr, jobsman: ProtocolJobsManagerPtr, @@ -27,8 +28,8 @@ pub struct ProtocolAddress { } impl ProtocolAddress { - /// Create a new address protocol. Makes an address and get-address - /// subscription and adds them to the address protocol instance. + /// Create a new address protocol. Makes an address, an external address + /// and a get-address subscription and adds them to the address protocol instance. pub async fn init(channel: ChannelPtr, p2p: P2pPtr) -> ProtocolBasePtr { let settings = p2p.settings(); let hosts = p2p.hosts(); @@ -40,6 +41,13 @@ impl ProtocolAddress { .await .expect("Missing addrs dispatcher!"); + // Creates a subscription to external address message. + let ext_addrs_sub = channel + .clone() + .subscribe_msg::() + .await + .expect("Missing ext_addrs dispatcher!"); + // Creates a subscription to get-address message. let get_addrs_sub = channel .clone() @@ -50,6 +58,7 @@ impl ProtocolAddress { Arc::new(Self { channel: channel.clone(), addrs_sub, + ext_addrs_sub, get_addrs_sub, hosts, jobsman: ProtocolJobsManager::new("ProtocolAddress", channel), @@ -69,6 +78,18 @@ impl ProtocolAddress { } } + /// Handles receiving the external address message. Loops to continually recieve + /// external address messages on the address subsciption. Adds the recieved + /// external addresses to the list of hosts. + async fn handle_receive_ext_addrs(self: Arc) -> Result<()> { + debug!(target: "net", "ProtocolAddress::handle_receive_ext_addrs() [START]"); + loop { + let ext_addrs_msg = self.ext_addrs_sub.receive().await?; + debug!(target: "net", "ProtocolAddress::handle_receive_ext_addrs() received {} addrs", ext_addrs_msg.ext_addrs.len()); + self.hosts.store_ext(self.channel.address(), ext_addrs_msg.ext_addrs.clone()).await; + } + } + /// Handles receiving the get-address message. Continually recieves /// get-address messages on the get-address subsciption. Then replies /// with an address message. @@ -92,9 +113,9 @@ impl ProtocolAddress { async fn send_my_addrs(self: Arc) -> Result<()> { debug!(target: "net", "ProtocolAddress::send_addrs() [START]"); loop { - let addrs = self.settings.external_addr.clone(); - let addr_msg = message::AddrsMessage { addrs }; - self.channel.clone().send(addr_msg).await?; + let ext_addrs = self.settings.external_addr.clone(); + let ext_addr_msg = message::ExtAddrsMessage { ext_addrs }; + self.channel.clone().send(ext_addr_msg).await?; async_util::sleep(SEND_ADDR_SLEEP_SECONDS).await; } } @@ -118,6 +139,7 @@ impl ProtocolBase for ProtocolAddress { debug!(target: "net", "ProtocolAddress::start() [START]"); self.jobsman.clone().start(executor.clone()); self.jobsman.clone().spawn(self.clone().handle_receive_addrs(), executor.clone()).await; + self.jobsman.clone().spawn(self.clone().handle_receive_ext_addrs(), executor.clone()).await; self.jobsman.clone().spawn(self.clone().handle_receive_get_addrs(), executor).await; // Send get_address message. diff --git a/src/net/protocol/protocol_seed.rs b/src/net/protocol/protocol_seed.rs index d0d4bde5a..dd5219be4 100644 --- a/src/net/protocol/protocol_seed.rs +++ b/src/net/protocol/protocol_seed.rs @@ -46,10 +46,10 @@ impl ProtocolSeed { return Ok(()) } - let addrs = self.settings.external_addr.clone(); - debug!(target: "net", "ProtocolSeed::send_own_address() addrs={:?}", addrs); - let addrs = message::AddrsMessage { addrs }; - self.channel.clone().send(addrs).await + let ext_addrs = self.settings.external_addr.clone(); + debug!(target: "net", "ProtocolSeed::send_self_address() ext_addrs={:?}", ext_addrs); + let ext_addr_msg = message::ExtAddrsMessage { ext_addrs }; + self.channel.clone().send(ext_addr_msg).await } }