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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Per-connection state management.
//!
//! Tracks a TCP or UDP connection, performs stream reassembly, and (via ConnInfo)
//! manages protocol parser state throughout the duration of the connection.

pub mod conn_info;
pub mod tcp_conn;
pub mod udp_conn;

use self::conn_info::ConnInfo;
use self::tcp_conn::TcpConn;
use self::udp_conn::UdpConn;
use crate::conntrack::conn_id::FiveTuple;
use crate::conntrack::pdu::{L4Context, L4Pdu};
use crate::lcore::CoreId;
use crate::protocols::packet::tcp::{ACK, RST, SYN};
use crate::protocols::stream::ParserRegistry;
use crate::subscription::{Subscription, Trackable};

use anyhow::{bail, Result};
use std::time::Instant;

/// Tracks either a TCP or a UDP connection.
///
/// Performs light-weight stream reassembly for TCP connections and tracks UDP connections.
pub(crate) enum L4Conn {
    Tcp(TcpConn),
    Udp(UdpConn),
}

/// Connection state.
pub(crate) struct Conn<T>
where
    T: Trackable,
{
    /// Timestamp of the last observed packet in the connection.
    pub(crate) last_seen_ts: Instant,
    /// Amount of time (in milliseconds) before the connection should be expired for inactivity.
    pub(crate) inactivity_window: usize,
    /// Layer-4 connection tracking.
    pub(crate) l4conn: L4Conn,
    /// Connection tracking for filtering and parsing.
    pub(crate) info: ConnInfo<T>,
}

impl<T> Conn<T>
where
    T: Trackable,
{
    /// Creates a new TCP connection from `ctxt` with an initial inactivity window of
    /// `initial_timeout` and a maximum out-or-order tolerance of `max_ooo`. This means that there
    /// can be at most `max_ooo` packets buffered out of sequence before Retina chooses to discard
    /// the connection.
    pub(super) fn new_tcp(
        initial_timeout: usize,
        max_ooo: usize,
        pdu: &L4Pdu,
        core_id: CoreId,
    ) -> Result<Self> {
        let tcp_conn = if pdu.ctxt.flags & SYN != 0
            && pdu.ctxt.flags & ACK == 0
            && pdu.ctxt.flags & RST == 0
        {
            TcpConn::new_on_syn(pdu.ctxt, max_ooo)
        } else {
            bail!("Not SYN")
        };
        Ok(Conn {
            last_seen_ts: Instant::now(),
            inactivity_window: initial_timeout,
            l4conn: L4Conn::Tcp(tcp_conn),
            info: ConnInfo::new(pdu, core_id),
        })
    }

    /// Creates a new UDP connection from `ctxt` with an initial inactivity window of
    /// `initial_timeout`.
    #[allow(clippy::unnecessary_wraps)]
    pub(super) fn new_udp(initial_timeout: usize, pdu: &L4Pdu, core_id: CoreId) -> Result<Self> {
        let udp_conn = UdpConn;
        Ok(Conn {
            last_seen_ts: Instant::now(),
            inactivity_window: initial_timeout,
            l4conn: L4Conn::Udp(udp_conn),
            info: ConnInfo::new(pdu, core_id),
        })
    }

    /// Updates a connection on the arrival of a new packet.
    pub(super) fn update(
        &mut self,
        pdu: L4Pdu,
        subscription: &Subscription<T::Subscribed>,
        registry: &ParserRegistry,
    ) {
        match &mut self.l4conn {
            L4Conn::Tcp(tcp_conn) => {
                tcp_conn.reassemble(pdu, &mut self.info, subscription, registry);
            }
            L4Conn::Udp(_udp_conn) => self.info.consume_pdu(pdu, subscription, registry),
        }
    }

    /// Updates flags
    #[inline]
    pub(super) fn update_tcp_flags(&mut self, flags: u8, dir: bool) {
        if let L4Conn::Tcp(tcp_conn) = &mut self.l4conn {
            tcp_conn.update_flags(flags, dir);
        }
    }

    /// Returns the connection 5-tuple.
    pub(super) fn five_tuple(&self) -> FiveTuple {
        self.info.cdata.five_tuple
    }

    /// Returns `true` if the connection should be removed from the conn. table.
    /// Note UDP connections are kept for a buffer period. UDP packets
    /// that pass the packet filter stage are assumed to represent an
    /// existing or new connection and are inserted into the connection
    /// table. Keeping UDP connections in "drop" state for a buffer
    /// period prevents dropped connections from being re-inserted.
    pub(super) fn remove_from_table(&self) -> bool {
        match &self.l4conn {
            L4Conn::Udp(_) => false,
            _ => self.info.actions.drop(),
        }
    }

    /// Returns `true` if PDUs for this connection should be dropped.
    pub(super) fn drop_pdu(&self) -> bool {
        self.info.actions.drop()
    }

    /// Returns `true` if the connection has been naturally terminated.
    pub(super) fn terminated(&self) -> bool {
        match &self.l4conn {
            L4Conn::Tcp(tcp_conn) => tcp_conn.is_terminated(),
            L4Conn::Udp(_udp_conn) => false,
        }
    }

    /// Returns the `true` if the packet represented by `ctxt` is in the direction of originator ->
    /// responder.
    pub(super) fn packet_dir(&self, ctxt: &L4Context) -> bool {
        self.five_tuple().orig == ctxt.src
    }

    /// Invokes connection termination tasks that are triggered when any of the following conditions
    /// occur:
    /// - the connection naturally terminates (e.g., FIN/RST)
    /// - the connection expires due to inactivity
    /// - the connection is drained at the end of the run
    pub(crate) fn terminate(&mut self, subscription: &Subscription<T::Subscribed>) {
        self.info.handle_terminate(subscription);
    }
}