ekko 0.1.1

Ekko is a simple utility for sending echo requests, giving you (mostly) everything you need.
Documentation
use std::{

    collections::{HashMap}, 

    net::{
        Ipv6Addr, 
        Ipv4Addr, 
        IpAddr, 
    }, 

    net::{
        SocketAddrV6,
        SocketAddrV4, 
        SocketAddr, 
    }, 

    time::{
        Duration, 
        Instant,
    },

};

use socket2::{
    Protocol, 
    Domain, 
    Socket, 
    Type,
};

use trust_dns_resolver::{

    Resolver, 

    config::{
        ResolverConfig, 
        ResolverOpts,
    },

};

use super::{

    error::{EkkoError},

    packets::{
        EchoResponse,
        EchoRequest, 
    },

    responses::{
        EkkoResponse,
        EkkoData,
        Unreachable,
        UnreachableCodeV6,
        UnreachableCodeV4,
    },

};

pub struct Ekko {
    resolver_cache: HashMap<IpAddr, Option<String>>,
    resolver: Resolver,
    source_socket_address: SocketAddr,
    target_socket_address: SocketAddr,
    socket: Socket,
    buffer: Vec<u8>,
    sequence_number: u16,
}

impl Ekko {
    fn resolve_domain(&mut self, target: IpAddr) -> Result<Option<String>, EkkoError> {
        let domain = if self.resolver_cache.contains_key(&(target)) {
            self.resolver_cache.get(&target).ok_or({
                EkkoError::ResolverDomainCacheLookup(target.to_string())
            })?.clone()
        } 
        
        else {
            if let Ok(entries) = self.resolver.reverse_lookup(target) {
                if let Some(result) = entries.iter().next() {
                    self.resolver_cache.insert(target, Some(result.to_string()));
                    Some(result.to_string())
                } else {
                    self.resolver_cache.insert(target, None);
                    None
                }
            } else {
                self.resolver_cache.insert(target, None);
                None
            }
        };

        Ok(domain)
    }

    pub fn send(&mut self, hops: u32) -> Result<EkkoResponse, EkkoError> {
        self.send_with_timeout(hops, Some(Duration::from_millis(256)))
    }

    pub fn send_with_timeout(&mut self, hops: u32, timeout: Option<Duration>) -> Result<EkkoResponse, EkkoError> {
        self.socket.set_recv_buffer_size(512)
            .map_err(|_| EkkoError::SocketSetReceiveBufferSize)?;
        self.socket.set_read_timeout(timeout)
            .map_err(|_| EkkoError::SocketSetReadTimeout)?;
        self.socket.set_ttl(hops)
            .map_err(|_| EkkoError::SocketSetMaxHops)?;

        let timepoint = Instant::now();
        match (self.source_socket_address, self.target_socket_address) {
            (SocketAddr::V6(source), SocketAddr::V6(target)) => {
                self.buffer.resize(512, 0);
                let request = EchoRequest::new_ipv6(self.buffer.as_mut_slice(), rand::random(), self.sequence_number, &source.ip().segments(), &target.ip().segments())?;
                self.socket.send_to(&(request.as_slice()[..64]), &(target.into()))
                .map_err(|e| EkkoError::SocketSend(e.to_string()))?;
            },
            (SocketAddr::V4(_), SocketAddr::V4(target)) => {
                self.buffer.resize(512, 0);
                let request = EchoRequest::new_ipv4(self.buffer.as_mut_slice(), rand::random(), self.sequence_number)?;
                self.socket.send_to(&(request.as_slice()[..64]), &(target.into()))
                    .map_err(|e| EkkoError::SocketSend(e.to_string()))?;
            },
            (SocketAddr::V4(source), SocketAddr::V6(target)) => {
                return Err(EkkoError::SocketIpMismatch { 
                    src: source.to_string(), 
                    tgt: target.to_string() 
                })
            },
            (SocketAddr::V6(source), SocketAddr::V4(target)) => {
                return Err(EkkoError::SocketIpMismatch { 
                    src: source.to_string(), 
                    tgt: target.to_string() 
                })
            },
        };

        self.buffer.resize(512, 0);
        if let Ok((_, responder)) = self.socket.recv_from(self.buffer.as_mut_slice())  {
            let responding_address = match self.source_socket_address {
                SocketAddr::V6(_) => IpAddr::V6(responder.as_inet6()
                    .ok_or(EkkoError::SocketReceiveNoIpv6)?.ip().clone()),
                SocketAddr::V4(_) => IpAddr::V4(responder.as_inet()
                    .ok_or(EkkoError::SocketReceiveNoIpv6)?.ip().clone()),
            };

            self.sequence_number += 1;

            let elapsed = timepoint.elapsed();
            let response = match responding_address {
                IpAddr::V4(_) => EchoResponse::from_slice(&self.buffer[20..]),
                IpAddr::V6(_) => EchoResponse::from_slice(&self.buffer[..]),
            };

            match responding_address {
                IpAddr::V6(_) => match response.get_type()? {
                    1 => {
                        let unreachable_code = Unreachable::V6(match response.get_code()? {
                            0 => UnreachableCodeV6::NoRouteToDestination,
                            1 => UnreachableCodeV6::CommunicationWithDestinationAdministrativelyProhibited,
                            2 => UnreachableCodeV6::BeyondScopeOfSourceAddress,
                            3 => UnreachableCodeV6::AddressUnreachable,
                            4 => UnreachableCodeV6::PortUnreachable,
                            5 => UnreachableCodeV6::SourceAddressFailedIngressEgressPolicy,
                            6 => UnreachableCodeV6::RejectRouteToDestination,
                            7 => UnreachableCodeV6::ErrorInSourceRoutingHeader,
                            _ => UnreachableCodeV6::Unexpected(response.get_code()?),
                        });

                        Ok(EkkoResponse::UnreachableResponse((EkkoData { 
                            address: Some(responding_address), 
                            domain: self.resolve_domain(responding_address.clone())?, 
                            hops: hops,
                            timepoint: timepoint, 
                            elapsed: elapsed,
                        }, unreachable_code)))
                    },
                    3 => {
                        Ok(EkkoResponse::ExceededResponse(EkkoData { 
                            address: Some(responding_address), 
                            domain: self.resolve_domain(responding_address.clone())?, 
                            hops: hops,
                            timepoint: timepoint, 
                            elapsed: elapsed,
                        }))
                    },
                    129 => {
                        Ok(EkkoResponse::DestinationResponse(EkkoData { 
                            address: Some(responding_address), 
                            domain: self.resolve_domain(responding_address.clone())?, 
                            hops: hops,
                            timepoint: timepoint, 
                            elapsed: elapsed,
                        }))
                    },
                    _ => {
                        let unexpected = (response.get_type()?, response.get_code()?);

                        Ok(EkkoResponse::UnexpectedResponse((EkkoData { 
                            address: Some(responding_address), 
                            domain: self.resolve_domain(responding_address.clone())?, 
                            hops: hops,
                            timepoint: timepoint, 
                            elapsed: elapsed,
                        }, unexpected)))
                    },
                },
                IpAddr::V4(_) => match response.get_type()? {
                    3 => {
                        let unreachable_code = Unreachable::V4(match response.get_code()? {
                            0  => UnreachableCodeV4::DestinationNetworkUnreachable,
                            1  => UnreachableCodeV4::DestinationHostUnreachable,
                            2  => UnreachableCodeV4::DestinationProtocolUnreachable,
                            3  => UnreachableCodeV4::DestinationPortUnreachable,
                            4  => UnreachableCodeV4::FragmentationRequired,
                            5  => UnreachableCodeV4::SourceRouteFailed,
                            6  => UnreachableCodeV4::DestinationNetworkUnknown,
                            7  => UnreachableCodeV4::DestinationHostUnknown,
                            8  => UnreachableCodeV4::SourceHostIsolated,
                            9  => UnreachableCodeV4::NetworkAdministrativelyProhibited,
                            10 => UnreachableCodeV4::HostAdministrativelyProhibited,
                            11 => UnreachableCodeV4::NetworkUnreachable,
                            12 => UnreachableCodeV4::HostUnreachable,
                            13 => UnreachableCodeV4::CommunicationAdministrativelyProhibited,
                            14 => UnreachableCodeV4::HostPrecedenceViolation,
                            15 => UnreachableCodeV4::PrecedenceCutoff,
                            _ => UnreachableCodeV4::Unexpected(response.get_code()?),
                        });

                        Ok(EkkoResponse::UnreachableResponse((EkkoData { 
                            address: Some(responding_address), 
                            domain: self.resolve_domain(responding_address.clone())?, 
                            hops: hops,
                            timepoint: timepoint, 
                            elapsed: elapsed,
                        }, unreachable_code)))
                    },
                    11 => {
                        Ok(EkkoResponse::ExceededResponse(EkkoData { 
                            address: Some(responding_address), 
                            domain: self.resolve_domain(responding_address.clone())?, 
                            hops: hops,
                            timepoint: timepoint, 
                            elapsed: elapsed,
                        }))
                    },
                    0 => {
                        Ok(EkkoResponse::DestinationResponse(EkkoData { 
                            address: Some(responding_address), 
                            domain: self.resolve_domain(responding_address.clone())?, 
                            hops: hops,
                            timepoint: timepoint, 
                            elapsed: elapsed,
                        }))
                    },
                    _ => {
                        let unexpected = (response.get_type()?, response.get_code()?);

                        Ok(EkkoResponse::UnexpectedResponse((EkkoData { 
                            address: Some(responding_address), 
                            domain: self.resolve_domain(responding_address.clone())?, 
                            hops: hops,
                            timepoint: timepoint, 
                            elapsed: elapsed,
                        }, unexpected)))
                    },
                },
            }
        } else {
            Ok(EkkoResponse::LackingResponse(EkkoData { 
                address: None, 
                domain: None, 
                hops: hops,
                timepoint: timepoint, 
                elapsed: timepoint.elapsed(),
            }))
        }
    }

    pub fn with_target(target: &str) -> Result<Ekko, EkkoError>  {
        let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).map_err(|e| {
            EkkoError::ResolverCreate(e.to_string())
        })?;

        let target_address = {
            if let Ok(target_address) = target.parse() {
                target_address
            } else {
                resolver.lookup_ip(target).map_err(|_| {
                    EkkoError::ResolverIpLookup(target.to_string())
                })?.iter().last().ok_or({
                    EkkoError::ResolverIpLookup(target.to_string())
                })?
            }
        };

        match target_address {
            IpAddr::V4(target_address) => {
                let source_address = SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0);
                let socket = Socket::new(Domain::ipv4(), Type::raw(), Some(Protocol::icmpv4())).map_err(|e| {
                    EkkoError::SocketCreateIcmpv4(e.to_string())
                })?;

                socket.bind(&(source_address.into())).map_err(|_| {
                    EkkoError::SocketBindIpv4(source_address.to_string())
                })?;

                
                Ok(Ekko {
                    resolver_cache: HashMap::new(),
                    resolver: resolver,
                    source_socket_address: SocketAddr::V4(source_address),
                    target_socket_address: SocketAddr::V4(SocketAddrV4::new(target_address, 0)),
                    socket: socket,
                    buffer: Vec::with_capacity(512),
                    sequence_number: 0,
                })
            },
            IpAddr::V6(target_address) => {
                let source_address = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 0, 0, 0);
                let socket = Socket::new(Domain::ipv6(), Type::raw(), Some(Protocol::icmpv6())).map_err(|e| {
                    EkkoError::SocketCreateIcmpv6(e.to_string())
                })?;

                socket.bind(&(source_address.into())).map_err(|_| {
                    EkkoError::SocketBindIpv6(source_address.to_string())
                })?;

                
                Ok(Ekko {
                    resolver_cache: HashMap::new(),
                    resolver: resolver,
                    source_socket_address: SocketAddr::V6(source_address),
                    target_socket_address: SocketAddr::V6(SocketAddrV6::new(target_address, 0, 0, 0)),
                    socket: socket,
                    buffer: Vec::with_capacity(512),
                    sequence_number: 0,
                })
            },
        }
    }
}