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
//! Lateral-movement / Active Directory protocol surfacing (issue #29).
//!
//! Cap-free wiring tests: build a `Monitor` with the SMB / Kerberos / LDAP /
//! RDP `Protocol` markers and typed `.on::<P>()` message handlers, and assert
//! the whole registration path — flowscope parser install via
//! `session_on_ports`, slot-handle creation, and dispatcher slot wiring —
//! succeeds. `.build()` does not open a capture, so no CAP_NET_RAW is needed.
//!
//! Actual on-wire parsing of these protocols is covered by flowscope's own
//! pcap test suite; here we prove that netring surfaces them correctly and
//! that the typed handler signatures bind to the right `Protocol::Message`.
#![cfg(all(
feature = "tokio",
feature = "flow",
feature = "smb",
feature = "kerberos",
feature = "ldap",
feature = "rdp"
))]
use flowscope::kerberos::KerberosMessage;
use flowscope::ldap::LdapMessage;
use flowscope::rdp::RdpMessage;
use flowscope::smb::SmbMessage;
use netring::monitor::Monitor;
use netring::prelude::{Ctx, Kerberos, Ldap, Rdp, Smb};
use netring::protocol::{Dispatch, Protocol};
#[test]
fn dispatch_ports_match_iana_assignments() {
// The kernel prefilter narrows to these ports; a regression here would
// silently capture the wrong traffic.
assert!(matches!(Smb::dispatch(), Dispatch::Tcp(ref p) if p == &[445]));
assert!(matches!(Kerberos::dispatch(), Dispatch::Tcp(ref p) if p == &[88]));
assert!(matches!(Ldap::dispatch(), Dispatch::Tcp(ref p) if p == &[389]));
assert!(matches!(Rdp::dispatch(), Dispatch::Tcp(ref p) if p == &[3389]));
}
#[test]
fn names_are_stable_lowercase_slugs() {
assert_eq!(Smb::NAME, "smb");
assert_eq!(Kerberos::NAME, "kerberos");
assert_eq!(Ldap::NAME, "ldap");
assert_eq!(Rdp::NAME, "rdp");
}
#[tokio::test(flavor = "current_thread")]
async fn monitor_registers_all_four_ad_protocols() {
// Each `.protocol::<P>()` installs the flowscope session parser; each
// `.on::<P>()` binds a typed message handler to the dispatcher. A failure
// anywhere in that chain surfaces as a `BuildError`.
let built = Monitor::builder()
.interface("lo")
.protocol::<Smb>()
.on::<Smb>(|m: &SmbMessage| {
// Exercise a lateral-movement field so the closure's payload type
// is pinned to flowscope's `SmbMessage` at compile time.
let _ = m.tree_connect_is_admin_share;
Ok(())
})
.protocol::<Kerberos>()
.on::<Kerberos>(|m: &KerberosMessage| {
let _ = m.kerberoast_suspect;
Ok(())
})
.protocol::<Ldap>()
.on::<Ldap>(|m: &LdapMessage| {
let _ = m.search_attributes_spn_query;
Ok(())
})
.protocol::<Rdp>()
.on_ctx::<Rdp>(|m: &RdpMessage, _ctx: &mut Ctx<'_>| {
// RDP's message is an enum; match one variant to pin the type.
if let RdpMessage::ConnectionRequest {
cookie_username, ..
} = m
{
let _ = cookie_username;
}
Ok(())
})
.build();
assert!(
built.is_ok(),
"AD-protocol registration should build cleanly: {:?}",
built.err()
);
}