libp2p-community-tor 0.4.1

Tor transport for libp2p.
Documentation
// Copyright 2022 Hannes Furmans
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

use arti_client::{DangerouslyIntoTorAddr, IntoTorAddr, TorAddr};
use libp2p::{core::multiaddr::Protocol, Multiaddr};
use std::net::SocketAddr;

/// "Dangerously" extract a Tor address from the provided [`Multiaddr`].
///
/// See [`DangerouslyIntoTorAddr`] for details around the safety / privacy considerations.
pub fn dangerous_extract(multiaddr: &Multiaddr) -> Option<TorAddr> {
    if let Some(tor_addr) = safe_extract(multiaddr) {
        return Some(tor_addr);
    }

    let mut protocols = multiaddr.into_iter();

    let tor_addr = try_to_socket_addr(&protocols.next()?, &protocols.next()?)?
        .into_tor_addr_dangerously()
        .ok()?;

    Some(tor_addr)
}

/// "Safely" extract a Tor address from the provided [`Multiaddr`].
///
/// See [`IntoTorAddr`] for details around the safety / privacy considerations.
pub fn safe_extract(multiaddr: &Multiaddr) -> Option<TorAddr> {
    let mut protocols = multiaddr.into_iter();

    let tor_addr = try_to_domain_and_port(&protocols.next()?, &protocols.next()?)?
        .into_tor_addr()
        .ok()?;

    Some(tor_addr)
}

fn try_to_domain_and_port<'a>(
    maybe_domain: &'a Protocol,
    maybe_port: &Protocol,
) -> Option<(&'a str, u16)> {
    match (maybe_domain, maybe_port) {
        (
            Protocol::Dns(domain) | Protocol::Dns4(domain) | Protocol::Dns6(domain),
            Protocol::Tcp(port),
        ) => Some((domain.as_ref(), *port)),
        _ => None,
    }
}

fn try_to_socket_addr(maybe_ip: &Protocol, maybe_port: &Protocol) -> Option<SocketAddr> {
    match (maybe_ip, maybe_port) {
        (Protocol::Ip4(ip), Protocol::Tcp(port)) => Some(SocketAddr::from((*ip, *port))),
        (Protocol::Ip6(ip), Protocol::Tcp(port)) => Some(SocketAddr::from((*ip, *port))),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use arti_client::TorAddr;
    use std::net::{Ipv4Addr, Ipv6Addr};

    #[test]
    fn extract_correct_address_from_dns() {
        let addresses = [
            "/dns/ip.tld/tcp/10".parse().unwrap(),
            "/dns4/dns.ip4.tld/tcp/11".parse().unwrap(),
            "/dns6/dns.ip6.tld/tcp/12".parse().unwrap(),
        ];

        let actual = addresses
            .iter()
            .filter_map(safe_extract)
            .collect::<Vec<_>>();

        assert_eq!(
            &[
                TorAddr::from(("ip.tld", 10)).unwrap(),
                TorAddr::from(("dns.ip4.tld", 11)).unwrap(),
                TorAddr::from(("dns.ip6.tld", 12)).unwrap(),
            ],
            actual.as_slice()
        );
    }

    #[test]
    fn extract_correct_address_from_ips() {
        let addresses = [
            "/ip4/127.0.0.1/tcp/10".parse().unwrap(),
            "/ip6/::1/tcp/10".parse().unwrap(),
        ];

        let actual = addresses
            .iter()
            .filter_map(dangerous_extract)
            .collect::<Vec<_>>();

        assert_eq!(
            &[
                TorAddr::dangerously_from((Ipv4Addr::LOCALHOST, 10)).unwrap(),
                TorAddr::dangerously_from((Ipv6Addr::LOCALHOST, 10)).unwrap(),
            ],
            actual.as_slice()
        );
    }

    #[test]
    fn dangerous_extract_works_on_domains_too() {
        let addresses = [
            "/dns/ip.tld/tcp/10".parse().unwrap(),
            "/ip4/127.0.0.1/tcp/10".parse().unwrap(),
            "/ip6/::1/tcp/10".parse().unwrap(),
        ];

        let actual = addresses
            .iter()
            .filter_map(dangerous_extract)
            .collect::<Vec<_>>();

        assert_eq!(
            &[
                TorAddr::from(("ip.tld", 10)).unwrap(),
                TorAddr::dangerously_from((Ipv4Addr::LOCALHOST, 10)).unwrap(),
                TorAddr::dangerously_from((Ipv6Addr::LOCALHOST, 10)).unwrap(),
            ],
            actual.as_slice()
        );
    }

    #[test]
    fn detect_incorrect_address() {
        let addresses = [
            "/tcp/10/udp/12".parse().unwrap(),
            "/dns/ip.tld/dns4/ip.tld/dns6/ip.tld".parse().unwrap(),
            "/tcp/10/ip4/1.1.1.1".parse().unwrap(),
        ];

        let all_correct = addresses.iter().map(safe_extract).all(|res| res.is_none());

        assert!(
            all_correct,
            "During the parsing of the faulty addresses, there was an incorrectness"
        );
    }
}