huginn_net/
tcp_process.rs

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