huginn_net_tcp/
tcp_process.rs

1use crate::error::HuginnNetTcpError;
2use crate::ip_options::IpOptions;
3use crate::observable::{ObservableMtu, ObservableTcp, ObservableUptime};
4use crate::tcp::{IpVersion, PayloadSize, Quirk, TcpOption, Ttl, WindowSize};
5use crate::uptime::check_ts_tcp;
6use crate::uptime::{Connection, SynData};
7use crate::window_size::detect_win_multiplicator;
8use crate::{mtu, ttl};
9use pnet::packet::ip::IpNextHeaderProtocols;
10use pnet::packet::{
11    ipv4::{Ipv4Flags, Ipv4Packet},
12    ipv6::Ipv6Packet,
13    tcp::{TcpFlags, TcpOptionNumbers::*, TcpOptionPacket, TcpPacket},
14    Packet, PacketSize,
15};
16use std::convert::TryInto;
17use std::net::IpAddr;
18use ttl_cache::TtlCache;
19
20/// Congestion encountered
21const IP_TOS_CE: u8 = 0x01;
22/// ECN supported
23const IP_TOS_ECT: u8 = 0x02;
24/// Must be zero
25const IP4_MBZ: u8 = 0b0100;
26
27// Internal representation of a TCP package
28pub struct ObservableTCPPackage {
29    pub tcp_request: Option<ObservableTcp>,
30    pub tcp_response: Option<ObservableTcp>,
31    pub mtu: Option<ObservableMtu>,
32    pub uptime: Option<ObservableUptime>,
33}
34
35fn from_client(tcp_flags: u8) -> bool {
36    use TcpFlags::*;
37    tcp_flags & SYN != 0 && tcp_flags & ACK == 0
38}
39
40fn from_server(tcp_flags: u8) -> bool {
41    use TcpFlags::*;
42    tcp_flags & SYN != 0 && tcp_flags & ACK != 0
43}
44
45fn is_valid(tcp_flags: u8, tcp_type: u8) -> bool {
46    use TcpFlags::*;
47
48    !(((tcp_flags & SYN) == SYN && (tcp_flags & (FIN | RST)) != 0)
49        || (tcp_flags & (FIN | RST)) == (FIN | RST)
50        || tcp_type == 0)
51}
52
53pub fn process_tcp_ipv4(
54    packet: &Ipv4Packet,
55    connection_tracker: &mut TtlCache<Connection, SynData>,
56) -> Result<ObservableTCPPackage, HuginnNetTcpError> {
57    if packet.get_next_level_protocol() != IpNextHeaderProtocols::Tcp {
58        return Err(HuginnNetTcpError::UnsupportedProtocol("IPv4".to_string()));
59    }
60
61    if packet.get_fragment_offset() > 0
62        || (packet.get_flags() & Ipv4Flags::MoreFragments) == Ipv4Flags::MoreFragments
63    {
64        return Err(HuginnNetTcpError::UnexpectedPackage("IPv4".to_string()));
65    }
66
67    let version = IpVersion::V4;
68    let ttl_observed: u8 = packet.get_ttl();
69    let ttl: Ttl = ttl::calculate_ttl(ttl_observed);
70    let olen: u8 = IpOptions::calculate_ipv4_length(packet);
71    let mut quirks = vec![];
72
73    if (packet.get_ecn() & (IP_TOS_CE | IP_TOS_ECT)) != 0 {
74        quirks.push(Quirk::Ecn);
75    }
76
77    if (packet.get_flags() & IP4_MBZ) != 0 {
78        quirks.push(Quirk::MustBeZero);
79    }
80
81    if (packet.get_flags() & Ipv4Flags::DontFragment) != 0 {
82        quirks.push(Quirk::Df);
83
84        if packet.get_identification() != 0 {
85            quirks.push(Quirk::NonZeroID);
86        }
87    } else if packet.get_identification() == 0 {
88        quirks.push(Quirk::ZeroID);
89    }
90
91    let source_ip: IpAddr = IpAddr::V4(packet.get_source());
92    let destination_ip = IpAddr::V4(packet.get_destination());
93
94    let tcp_payload = packet.payload(); // Get a reference to the payload without moving `packet`
95
96    let ip_package_header_length: u8 = packet.get_header_length();
97
98    TcpPacket::new(tcp_payload)
99        .ok_or_else(|| HuginnNetTcpError::UnexpectedPackage("TCP packet too short".to_string()))
100        .and_then(|tcp_packet| {
101            visit_tcp(
102                connection_tracker,
103                &tcp_packet,
104                version,
105                ttl,
106                ip_package_header_length,
107                olen,
108                quirks,
109                source_ip,
110                destination_ip,
111            )
112        })
113}
114
115pub fn process_tcp_ipv6(
116    packet: &Ipv6Packet,
117    connection_tracker: &mut TtlCache<Connection, SynData>,
118) -> Result<ObservableTCPPackage, HuginnNetTcpError> {
119    if packet.get_next_header() != IpNextHeaderProtocols::Tcp {
120        return Err(HuginnNetTcpError::UnsupportedProtocol("IPv6".to_string()));
121    }
122    let version = IpVersion::V6;
123    let ttl_observed: u8 = packet.get_hop_limit();
124    let ttl: Ttl = ttl::calculate_ttl(ttl_observed);
125    let olen: u8 = IpOptions::calculate_ipv6_length(packet);
126    let mut quirks = vec![];
127
128    if packet.get_flow_label() != 0 {
129        quirks.push(Quirk::FlowID);
130    }
131    if (packet.get_traffic_class() & (IP_TOS_CE | IP_TOS_ECT)) != 0 {
132        quirks.push(Quirk::Ecn);
133    }
134
135    let source_ip: IpAddr = IpAddr::V6(packet.get_source());
136    let destination_ip = IpAddr::V6(packet.get_destination());
137
138    let ip_package_header_length: u8 = 40; //IPv6 header is always 40 bytes
139
140    TcpPacket::new(packet.payload())
141        .ok_or_else(|| HuginnNetTcpError::UnexpectedPackage("TCP packet too short".to_string()))
142        .and_then(|tcp_packet| {
143            visit_tcp(
144                connection_tracker,
145                &tcp_packet,
146                version,
147                ttl,
148                ip_package_header_length,
149                olen,
150                quirks,
151                source_ip,
152                destination_ip,
153            )
154        })
155}
156
157#[allow(clippy::too_many_arguments)]
158fn visit_tcp(
159    connection_tracker: &mut TtlCache<Connection, SynData>,
160    tcp: &TcpPacket,
161    version: IpVersion,
162    ittl: Ttl,
163    ip_package_header_length: u8,
164    olen: u8,
165    mut quirks: Vec<Quirk>,
166    source_ip: IpAddr,
167    destination_ip: IpAddr,
168) -> Result<ObservableTCPPackage, HuginnNetTcpError> {
169    use TcpFlags::*;
170
171    let flags: u8 = tcp.get_flags();
172    let from_client: bool = from_client(flags);
173    let from_server: bool = from_server(flags);
174
175    if !from_client && !from_server {
176        return Ok(ObservableTCPPackage {
177            tcp_request: None,
178            tcp_response: None,
179            mtu: None,
180            uptime: None,
181        });
182    }
183    let tcp_type: u8 = flags & (SYN | ACK | FIN | RST);
184    if !is_valid(flags, tcp_type) {
185        return Err(HuginnNetTcpError::InvalidTcpFlags(flags));
186    }
187
188    if (flags & (ECE | CWR)) != 0 {
189        quirks.push(Quirk::Ecn);
190    }
191    if tcp.get_sequence() == 0 {
192        quirks.push(Quirk::SeqNumZero);
193    }
194    if flags & ACK == ACK {
195        if tcp.get_acknowledgement() == 0 {
196            quirks.push(Quirk::AckNumZero);
197        }
198    } else if tcp.get_acknowledgement() != 0 && flags & RST == 0 {
199        quirks.push(Quirk::AckNumNonZero);
200    }
201
202    if flags & URG == URG {
203        quirks.push(Quirk::Urg);
204    } else if tcp.get_urgent_ptr() != 0 {
205        quirks.push(Quirk::NonZeroURG);
206    }
207
208    if flags & PSH == PSH {
209        quirks.push(Quirk::Push);
210    }
211
212    let mut buf = tcp.get_options_raw();
213    let mut mss = None;
214    let mut wscale = None;
215    let mut olayout = vec![];
216    let mut uptime: Option<ObservableUptime> = None;
217
218    while let Some(opt) = TcpOptionPacket::new(buf) {
219        buf = &buf[opt.packet_size().min(buf.len())..];
220
221        let data: &[u8] = opt.payload();
222
223        match opt.get_number() {
224            EOL => {
225                olayout.push(TcpOption::Eol(buf.len() as u8));
226
227                if buf.iter().any(|&b| b != 0) {
228                    quirks.push(Quirk::TrailinigNonZero);
229                }
230            }
231            NOP => {
232                olayout.push(TcpOption::Nop);
233            }
234            MSS => {
235                olayout.push(TcpOption::Mss);
236                if data.len() >= 2 {
237                    let mss_value: u16 = u16::from_be_bytes([data[0], data[1]]);
238                    //quirks.push(Quirk::mss);
239                    mss = Some(mss_value);
240                }
241
242                /*if data.len() != 4 {
243                    quirks.push(Quirk::OptBad);
244                }*/
245            }
246            WSCALE => {
247                olayout.push(TcpOption::Ws);
248
249                wscale = Some(data[0]);
250
251                if data[0] > 14 {
252                    quirks.push(Quirk::ExcessiveWindowScaling);
253                }
254                /*if data.len() != 3 {
255                    quirks.push(Quirk::OptBad);
256                }*/
257            }
258            SACK_PERMITTED => {
259                olayout.push(TcpOption::Sok);
260
261                /*if data.len() != 2 {
262                    quirks.push(Quirk::OptBad);
263                }*/
264            }
265            SACK => {
266                olayout.push(TcpOption::Sack);
267
268                /*match data.len() {
269                    10 | 18 | 26 | 34 => {}
270                    _ => quirks.push(Quirk::OptBad),
271                }*/
272            }
273            TIMESTAMPS => {
274                olayout.push(TcpOption::TS);
275
276                if data.len() >= 4 {
277                    let ts_val_bytes: [u8; 4] = data[..4].try_into().map_err(|_| {
278                        HuginnNetTcpError::Parse(
279                            "Failed to convert slice to array for timestamp value".to_string(),
280                        )
281                    })?;
282                    if u32::from_ne_bytes(ts_val_bytes) == 0 {
283                        quirks.push(Quirk::OwnTimestampZero);
284                    }
285                }
286
287                if data.len() >= 8 && tcp_type == SYN {
288                    let ts_peer_bytes: [u8; 4] = data[4..8].try_into().map_err(|_| {
289                        HuginnNetTcpError::Parse(
290                            "Failed to convert slice to array for peer timestamp value".to_string(),
291                        )
292                    })?;
293                    if u32::from_ne_bytes(ts_peer_bytes) != 0 {
294                        quirks.push(Quirk::PeerTimestampNonZero);
295                    }
296                }
297
298                if data.len() >= 8 {
299                    let ts_val_bytes: [u8; 4] = data[..4].try_into().map_err(|_| {
300                        HuginnNetTcpError::Parse(
301                            "Failed to convert slice to array for timestamp value".to_string(),
302                        )
303                    })?;
304                    let ts_val: u32 = u32::from_ne_bytes(ts_val_bytes);
305                    let connection: Connection = Connection {
306                        src_ip: source_ip,
307                        src_port: tcp.get_source(),
308                        dst_ip: destination_ip,
309                        dst_port: tcp.get_destination(),
310                    };
311                    uptime = check_ts_tcp(connection_tracker, &connection, from_client, ts_val);
312                }
313
314                /*if data.len() != 10 {
315                    quirks.push(Quirk::OptBad);
316                }*/
317            }
318            _ => {
319                olayout.push(TcpOption::Unknown(opt.get_number().0));
320            }
321        }
322    }
323
324    let mtu: Option<ObservableMtu> = match (mss, &version) {
325        (Some(mss_value), IpVersion::V4) => {
326            mtu::extract_from_ipv4(tcp, ip_package_header_length, mss_value)
327        }
328        (Some(mss_value), IpVersion::V6) => {
329            mtu::extract_from_ipv6(tcp, ip_package_header_length, mss_value)
330        }
331        _ => None,
332    };
333
334    let wsize: WindowSize = detect_win_multiplicator(
335        tcp.get_window(),
336        mss.unwrap_or(0),
337        ip_package_header_length as u16,
338        olayout.contains(&TcpOption::TS),
339        &version,
340    );
341
342    let tcp_signature: ObservableTcp = ObservableTcp {
343        matching: huginn_net_db::observable_signals::TcpObservation {
344            version,
345            ittl,
346            olen,
347            mss,
348            wsize,
349            wscale,
350            olayout,
351            quirks,
352            pclass: if tcp.payload().is_empty() {
353                PayloadSize::Zero
354            } else {
355                PayloadSize::NonZero
356            },
357        },
358    };
359
360    Ok(ObservableTCPPackage {
361        tcp_request: if from_client {
362            Some(tcp_signature.clone())
363        } else {
364            None
365        },
366        tcp_response: if !from_client {
367            Some(tcp_signature)
368        } else {
369            None
370        },
371        mtu: if from_client { mtu } else { None },
372        uptime: if !from_client { uptime } else { None },
373    })
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn test_from_client() {
382        assert!(from_client(TcpFlags::SYN));
383        assert!(!from_client(TcpFlags::SYN | TcpFlags::ACK));
384        assert!(!from_client(TcpFlags::ACK));
385    }
386
387    #[test]
388    fn test_from_server() {
389        assert!(from_server(TcpFlags::SYN | TcpFlags::ACK));
390        assert!(!from_server(TcpFlags::SYN));
391        assert!(!from_server(TcpFlags::ACK));
392        assert!(!from_server(TcpFlags::RST));
393    }
394
395    #[test]
396    fn test_is_valid() {
397        assert!(is_valid(TcpFlags::SYN, TcpFlags::SYN));
398        assert!(!is_valid(TcpFlags::SYN | TcpFlags::FIN, TcpFlags::SYN));
399        assert!(!is_valid(TcpFlags::SYN | TcpFlags::RST, TcpFlags::SYN));
400        assert!(!is_valid(
401            TcpFlags::FIN | TcpFlags::RST,
402            TcpFlags::FIN | TcpFlags::RST
403        ));
404        assert!(!is_valid(TcpFlags::SYN, 0));
405    }
406}