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
20const IP_TOS_CE: u8 = 0x01;
22const IP_TOS_ECT: u8 = 0x02;
24const IP4_MBZ: u8 = 0b0100;
26
27pub 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(); 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; 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 mss = Some(mss_value);
240 }
241
242 }
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 }
258 SACK_PERMITTED => {
259 olayout.push(TcpOption::Sok);
260
261 }
265 SACK => {
266 olayout.push(TcpOption::Sack);
267
268 }
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 }
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}