1use tracing::debug;
2
3#[derive(Clone, Debug, PartialEq)]
4pub struct Signature {
5 pub version: IpVersion,
6 pub ittl: Ttl,
8 pub olen: u8,
10 pub mss: Option<u16>,
12 pub wsize: WindowSize,
14 pub wscale: Option<u8>,
16 pub olayout: Vec<TcpOption>,
18 pub quirks: Vec<Quirk>,
20 pub pclass: PayloadSize,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum TcpMatchQuality {
26 High,
27 Medium,
28 Low,
29}
30
31impl TcpMatchQuality {
32 pub fn as_score(self) -> u32 {
33 match self {
34 TcpMatchQuality::High => 0,
35 TcpMatchQuality::Medium => 1,
36 TcpMatchQuality::Low => 2,
37 }
38 }
39}
40
41impl crate::db_matching_trait::MatchQuality for TcpMatchQuality {
42 const MAX_DISTANCE: u32 = 18;
44
45 fn distance_to_score(distance: u32) -> f32 {
46 match distance {
47 0 => 1.0,
48 1 => 0.95,
49 2 => 0.90,
50 3..=4 => 0.80,
51 5..=6 => 0.70,
52 7..=9 => 0.60,
53 10..=12 => 0.40,
54 13..=15 => 0.20,
55 d if d <= Self::MAX_DISTANCE => 0.10,
56 _ => 0.05,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum IpVersion {
63 V4,
64 V6,
65 Any,
66}
67impl IpVersion {
68 pub fn distance_ip_version(&self, other: &IpVersion) -> Option<u32> {
69 if other == &IpVersion::Any {
70 Some(TcpMatchQuality::High.as_score())
71 } else {
72 match (self, other) {
73 (IpVersion::V4, IpVersion::V4) | (IpVersion::V6, IpVersion::V6) => {
74 Some(TcpMatchQuality::High.as_score())
75 }
76 _ => None,
77 }
78 }
79 }
80}
81
82#[derive(Clone, Debug, PartialEq)]
84pub enum Ttl {
85 Value(u8),
88
89 Distance(u8, u8),
93
94 Guess(u8),
97
98 Bad(u8),
101}
102
103impl Ttl {
104 pub fn distance_ttl(&self, other: &Ttl) -> Option<u32> {
105 match (self, other) {
106 (Ttl::Value(a), Ttl::Value(b)) => {
107 if a == b {
108 Some(TcpMatchQuality::High.as_score())
109 } else {
110 Some(TcpMatchQuality::Low.as_score())
111 }
112 }
113 (Ttl::Distance(a1, a2), Ttl::Distance(b1, b2)) => {
114 if a1 == b1 && a2 == b2 {
115 Some(TcpMatchQuality::High.as_score())
116 } else {
117 Some(TcpMatchQuality::Low.as_score())
118 }
119 }
120 (Ttl::Distance(a1, a2), Ttl::Value(b1)) => {
121 if a1.saturating_add(*a2) == *b1 {
122 Some(TcpMatchQuality::High.as_score())
123 } else {
124 Some(TcpMatchQuality::Low.as_score())
125 }
126 }
127 (Ttl::Guess(a), Ttl::Guess(b)) => {
128 if a == b {
129 Some(TcpMatchQuality::High.as_score())
130 } else {
131 Some(TcpMatchQuality::Low.as_score())
132 }
133 }
134 (Ttl::Bad(a), Ttl::Bad(b)) => {
135 if a == b {
136 Some(TcpMatchQuality::High.as_score())
137 } else {
138 Some(TcpMatchQuality::Low.as_score())
139 }
140 }
141 (Ttl::Guess(a), Ttl::Value(b)) => {
142 if a == b {
143 Some(TcpMatchQuality::High.as_score())
144 } else {
145 Some(TcpMatchQuality::Low.as_score())
146 }
147 }
148 (Ttl::Value(a), Ttl::Distance(b1, b2)) => {
149 if *a == b1.saturating_add(*b2) {
150 Some(TcpMatchQuality::High.as_score())
151 } else {
152 Some(TcpMatchQuality::Low.as_score())
153 }
154 }
155 (Ttl::Value(a), Ttl::Guess(b)) => {
156 if a == b {
157 Some(TcpMatchQuality::High.as_score())
158 } else {
159 Some(TcpMatchQuality::Low.as_score())
160 }
161 }
162 _ => None,
163 }
164 }
165}
166
167#[derive(Clone, Debug, PartialEq)]
169pub enum WindowSize {
170 Mss(u8),
173
174 Mtu(u8),
177
178 Value(u16),
181
182 Mod(u16),
185
186 Any,
188}
189
190impl WindowSize {
191 pub fn distance_window_size(&self, other: &WindowSize, mss: Option<u16>) -> Option<u32> {
192 match (self, other) {
193 (WindowSize::Mss(a), WindowSize::Mss(b)) => {
194 if a == b {
195 Some(TcpMatchQuality::High.as_score())
196 } else {
197 Some(TcpMatchQuality::Low.as_score())
198 }
199 }
200 (WindowSize::Mtu(a), WindowSize::Mtu(b)) => {
201 if a == b {
202 Some(TcpMatchQuality::High.as_score())
203 } else {
204 Some(TcpMatchQuality::Low.as_score())
205 }
206 }
207 (WindowSize::Value(a), WindowSize::Mss(b)) => {
208 if let Some(mss_value) = mss {
209 if let Some(ratio_other) = a.checked_div(mss_value) {
210 if *b as u16 == ratio_other {
211 debug!(
212 "window size difference: a {}, b {} == ratio_other {}",
213 a, b, ratio_other
214 );
215 Some(TcpMatchQuality::High.as_score())
216 } else {
217 Some(TcpMatchQuality::Low.as_score())
218 }
219 } else {
220 Some(TcpMatchQuality::Low.as_score())
221 }
222 } else {
223 Some(TcpMatchQuality::Low.as_score())
224 }
225 }
226 (WindowSize::Mod(a), WindowSize::Mod(b)) => {
227 if a == b {
228 Some(TcpMatchQuality::High.as_score())
229 } else {
230 Some(TcpMatchQuality::Low.as_score())
231 }
232 }
233 (WindowSize::Value(a), WindowSize::Value(b)) => {
234 if a == b {
235 Some(TcpMatchQuality::High.as_score())
236 } else {
237 Some(TcpMatchQuality::Low.as_score())
238 }
239 }
240 (_, WindowSize::Any) => Some(TcpMatchQuality::High.as_score()),
241 _ => None,
242 }
243 }
244}
245
246#[derive(Clone, Debug, PartialEq)]
247pub enum TcpOption {
248 Eol(u8),
250 Nop,
252 Mss,
254 Ws,
256 Sok,
258 Sack,
260 TS,
262 Unknown(u8),
264}
265
266#[derive(Clone, Debug, PartialEq)]
267pub enum Quirk {
268 Df,
270 NonZeroID,
272 ZeroID,
274 Ecn,
276 MustBeZero,
278 FlowID,
280 SeqNumZero,
282 AckNumNonZero,
284 AckNumZero,
286 NonZeroURG,
288 Urg,
290 Push,
292 OwnTimestampZero,
294 PeerTimestampNonZero,
296 TrailinigNonZero,
298 ExcessiveWindowScaling,
300 OptBad,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
306pub enum PayloadSize {
307 Zero,
310
311 NonZero,
314
315 Any,
318}
319
320impl PayloadSize {
321 pub fn distance_payload_size(&self, other: &PayloadSize) -> Option<u32> {
322 if other == &PayloadSize::Any || self == other {
323 Some(TcpMatchQuality::High.as_score())
324 } else {
325 None
326 }
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn test_distance_ttl_matching_cases() {
336 assert_eq!(
337 Ttl::Value(64).distance_ttl(&Ttl::Value(64)),
338 Some(TcpMatchQuality::High.as_score())
339 );
340 assert_eq!(
341 Ttl::Distance(57, 7).distance_ttl(&Ttl::Distance(57, 7)),
342 Some(TcpMatchQuality::High.as_score())
343 );
344 assert_eq!(
345 Ttl::Distance(57, 7).distance_ttl(&Ttl::Value(64)),
346 Some(TcpMatchQuality::High.as_score())
347 );
348 assert_eq!(
349 Ttl::Guess(64).distance_ttl(&Ttl::Value(64)),
350 Some(TcpMatchQuality::High.as_score())
351 );
352 }
353
354 #[test]
355 fn test_distance_ttl_non_matching_cases() {
356 assert_eq!(
357 Ttl::Value(64).distance_ttl(&Ttl::Value(128)),
358 Some(TcpMatchQuality::Low.as_score())
359 );
360 assert_eq!(
361 Ttl::Distance(57, 7).distance_ttl(&Ttl::Value(128)),
362 Some(TcpMatchQuality::Low.as_score())
363 );
364 assert_eq!(
365 Ttl::Bad(0).distance_ttl(&Ttl::Bad(1)),
366 Some(TcpMatchQuality::Low.as_score())
367 );
368 }
369
370 #[test]
371 fn test_distance_ttl_additional_cases() {
372 assert_eq!(
373 Ttl::Value(64).distance_ttl(&Ttl::Distance(57, 7)),
374 Some(TcpMatchQuality::High.as_score())
375 );
376 assert_eq!(
377 Ttl::Value(64).distance_ttl(&Ttl::Guess(64)),
378 Some(TcpMatchQuality::High.as_score())
379 );
380 assert_eq!(
381 Ttl::Value(64).distance_ttl(&Ttl::Distance(60, 7)),
382 Some(TcpMatchQuality::Low.as_score())
383 );
384 }
385
386 #[test]
387 fn test_distance_ttl_incompatible_types() {
388 assert_eq!(Ttl::Bad(0).distance_ttl(&Ttl::Value(64)), None);
389 assert_eq!(Ttl::Distance(64, 7).distance_ttl(&Ttl::Bad(0)), None);
390 assert_eq!(Ttl::Guess(64).distance_ttl(&Ttl::Distance(64, 7)), None);
391 }
392}