1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Bidirectional connection identifiers.
//!
//! Provides endpoint-specific (distinguishes originator and responder) and generic identifiers for bi-directional connections.
//! Retina defines a "connection" by five tuple (source/destination addresses, ports, and transport protocol).

use crate::conntrack::L4Context;

use crate::protocols::packet::tcp::TCP_PROTOCOL;
use crate::protocols::packet::udp::UDP_PROTOCOL;
use std::cmp;
use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddr::V4, SocketAddr::V6};

use serde::Serialize;

/// Connection 5-tuple.
///
/// The sender of the first observed packet in the connection becomes the originator `orig`, and the
/// recipient becomes the responder `resp`.
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize)]
pub struct FiveTuple {
    /// The originator connection endpoint.
    pub orig: SocketAddr,
    /// The responder connection endpoint.
    pub resp: SocketAddr,
    /// The layer-4 protocol.
    pub proto: usize,
}

impl FiveTuple {
    /// Creates a new 5-tuple from `ctxt`.
    pub fn from_ctxt(ctxt: L4Context) -> Self {
        FiveTuple {
            orig: ctxt.src,
            resp: ctxt.dst,
            proto: ctxt.proto,
        }
    }

    /// Converts a 5-tuple to a non-directional connection identifier.
    pub fn conn_id(&self) -> ConnId {
        ConnId::new(self.orig, self.resp, self.proto)
    }

    /// Utility for returning a string representation of the dst. subnet
    /// /24 for IPv4, /64 for IPv6; no mask for broadcast
    pub fn dst_subnet_str(&self) -> String {
        if let V4(_) = self.orig {
            if let V4(dst) = self.resp {
                if dst.ip().is_broadcast() || dst.ip().is_multicast() {
                    return dst.ip().to_string();
                } else {
                    let mask = !0u32 << (32 - 24); // Convert to a /24
                    return Ipv4Addr::from(dst.ip().to_bits() & mask).to_string();
                }
            }
        } else if let V6(_) = self.orig {
            if let V6(dst) = self.resp {
                let mask = !0u128 << (128 - 64); // Convert to a /64
                return Ipv6Addr::from(dst.ip().to_bits() & mask).to_string();
            }
        }
        String::new()
    }

    /// Utility for returning a string representation of the dst. IP
    pub fn dst_ip_str(&self) -> String {
        if let V4(_) = self.orig {
            if let V4(dst) = self.resp {
                return dst.ip().to_string();
            }
        } else if let V6(_) = self.orig {
            if let V6(dst) = self.resp {
                return dst.ip().to_string();
            }
        }
        String::new()
    }

    /// Utility for returning a string representation of the transport
    /// protocol and source/destination ports
    pub fn transp_proto_str(&self) -> String {
        let src_port = self.orig.port();
        let dst_port = self.resp.port();
        let proto = match self.proto {
            UDP_PROTOCOL => "udp",
            TCP_PROTOCOL => "tcp",
            _ => "none",
        };
        format!(
            "{{ \"proto\": \"{}\", \"src\": \"{}\", \"dst\": \"{}\" }}",
            proto, src_port, dst_port
        )
    }
}

impl fmt::Display for FiveTuple {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} -> ", self.orig)?;
        write!(f, "{}", self.resp)?;
        write!(f, " protocol {}", self.proto)?;
        Ok(())
    }
}

/// A generic connection identifier.
///
/// Identifies a connection independent of the source and destination socket address order. Does not
/// distinguish between the originator and responder of the connection.
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct ConnId(SocketAddr, SocketAddr, usize);

impl ConnId {
    /// Returns the connection ID of a packet with `src` and `dst` IP/port pairs.
    pub(super) fn new(src: SocketAddr, dst: SocketAddr, protocol: usize) -> Self {
        ConnId(cmp::max(src, dst), cmp::min(src, dst), protocol)
    }
}

impl fmt::Display for ConnId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} <> ", self.0)?;
        write!(f, "{}", self.1)?;
        write!(f, " protocol {}", self.2)?;
        Ok(())
    }
}