1use crate::llmnr::LlmnrPacket;
9use crate::mdns::MdnsPacket;
10use crate::modbus::ModbusTcpPacket;
11use crate::mqtt::MqttPacket;
12use crate::netbios::NetbiosNsPacket;
13use crate::radius::RadiusPacket;
14use crate::rdp::TpktHeader;
15use crate::smb2::Smb2Header;
16use crate::ssh::SshPacket;
17use crate::tls::TlsClientHello;
18
19mod ports {
21 pub const TLS: u16 = 443;
22 pub const SSH: u16 = 22;
23 pub const MQTT: u16 = 1883;
24 pub const MODBUS: u16 = 502;
25 pub const SMB2: u16 = 445;
26 pub const RDP: u16 = 3389;
27 pub const RADIUS_AUTH: u16 = 1812;
28 pub const RADIUS_ACCT: u16 = 1813;
29 pub const MDNS: u16 = 5353;
30 pub const LLMNR: u16 = 5355;
31 pub const NETBIOS_NS: u16 = 137;
32}
33
34#[non_exhaustive]
36#[derive(Debug, Clone)]
37pub enum AppPacket<'a> {
38 Tls(TlsClientHello<'a>),
39 Ssh(SshPacket<'a>),
40 Mqtt(MqttPacket<'a>),
41 Modbus(ModbusTcpPacket<'a>),
42 Radius(RadiusPacket<'a>),
43 Mdns(MdnsPacket<'a>),
44 Llmnr(LlmnrPacket<'a>),
45 Netbios(NetbiosNsPacket<'a>),
46 Smb2(Smb2Header<'a>),
47 Rdp(TpktHeader<'a>),
48}
49
50impl<'a> AppPacket<'a> {
51 pub fn protocol_name(&self) -> &'static str {
53 match self {
54 AppPacket::Tls(_) => "TLS",
55 AppPacket::Ssh(_) => "SSH",
56 AppPacket::Mqtt(_) => "MQTT",
57 AppPacket::Modbus(_) => "ModbusTCP",
58 AppPacket::Radius(_) => "RADIUS",
59 AppPacket::Mdns(_) => "mDNS",
60 AppPacket::Llmnr(_) => "LLMNR",
61 AppPacket::Netbios(_) => "NetBIOS-NS",
62 AppPacket::Smb2(_) => "SMB2",
63 AppPacket::Rdp(_) => "RDP",
64 }
65 }
66}
67
68macro_rules! from_impl {
69 ($variant:ident, $type:ty) => {
70 impl<'a> From<$type> for AppPacket<'a> {
71 fn from(p: $type) -> Self {
72 AppPacket::$variant(p)
73 }
74 }
75 };
76}
77
78from_impl!(Tls, TlsClientHello<'a>);
79from_impl!(Ssh, SshPacket<'a>);
80from_impl!(Mqtt, MqttPacket<'a>);
81from_impl!(Modbus, ModbusTcpPacket<'a>);
82from_impl!(Radius, RadiusPacket<'a>);
83from_impl!(Mdns, MdnsPacket<'a>);
84from_impl!(Llmnr, LlmnrPacket<'a>);
85from_impl!(Netbios, NetbiosNsPacket<'a>);
86from_impl!(Smb2, Smb2Header<'a>);
87from_impl!(Rdp, TpktHeader<'a>);
88
89#[inline]
90fn port_matches(src: u16, dst: u16, port: u16) -> bool {
91 src == port || dst == port
92}
93
94pub fn parse_tcp_application(buf: &[u8], src_port: u16, dst_port: u16) -> Option<AppPacket<'_>> {
109 if port_matches(src_port, dst_port, ports::TLS) {
110 return TlsClientHello::parse(buf).map(AppPacket::Tls);
111 }
112 if port_matches(src_port, dst_port, ports::SSH) {
113 return SshPacket::parse(buf).map(AppPacket::Ssh);
114 }
115 if port_matches(src_port, dst_port, ports::MQTT) {
116 return MqttPacket::parse(buf).map(AppPacket::Mqtt);
117 }
118 if port_matches(src_port, dst_port, ports::MODBUS) {
119 return ModbusTcpPacket::new(buf).map(AppPacket::Modbus);
120 }
121 if port_matches(src_port, dst_port, ports::SMB2) {
122 return Smb2Header::parse(buf).map(AppPacket::Smb2);
123 }
124 if port_matches(src_port, dst_port, ports::RDP) {
125 return TpktHeader::parse(buf).map(AppPacket::Rdp);
126 }
127 None
128}
129
130pub fn parse_udp_application(buf: &[u8], src_port: u16, dst_port: u16) -> Option<AppPacket<'_>> {
134 if port_matches(src_port, dst_port, ports::RADIUS_AUTH)
135 || port_matches(src_port, dst_port, ports::RADIUS_ACCT)
136 {
137 return RadiusPacket::new(buf).map(AppPacket::Radius);
138 }
139 if port_matches(src_port, dst_port, ports::MDNS) {
140 return MdnsPacket::new(buf).map(AppPacket::Mdns);
141 }
142 if port_matches(src_port, dst_port, ports::LLMNR) {
143 return LlmnrPacket::new(buf).map(AppPacket::Llmnr);
144 }
145 if port_matches(src_port, dst_port, ports::NETBIOS_NS) {
146 return NetbiosNsPacket::new(buf).map(AppPacket::Netbios);
147 }
148 None
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::dns::builder::DnsPacketBuilder;
155 use crate::dns::types::{DnsClass, DnsType};
156 use crate::mqtt::MqttPacketBuilder;
157
158 #[test]
159 fn dispatch_tls_on_443() {
160 let data = &[
161 0x16, 0x03, 0x01, 0x00, 0x44, 0x01, 0x00, 0x00, 0x40, 0x03, 0x03, 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
166 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
167 0x00, 0x00, 0x02, 0x13, 0x01, 0x01, 0x00, 0x00, 0x07, 0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04, ];
173 let pkt = parse_tcp_application(data, 54321, 443).unwrap();
174 assert!(matches!(pkt, AppPacket::Tls(_)));
175 assert_eq!(pkt.protocol_name(), "TLS");
176 }
177
178 #[test]
179 fn dispatch_ssh_on_22() {
180 let data = b"SSH-2.0-OpenSSH_8.9\r\n";
181 let pkt = parse_tcp_application(data, 22, 54321).unwrap();
182 assert!(matches!(pkt, AppPacket::Ssh(SshPacket::Banner(_))));
183 assert_eq!(pkt.protocol_name(), "SSH");
184 }
185
186 #[test]
187 fn dispatch_mqtt_on_1883() {
188 let data = MqttPacketBuilder::connect("sensor1");
189 let pkt = parse_tcp_application(&data, 1883, 12345).unwrap();
190 assert!(matches!(pkt, AppPacket::Mqtt(_)));
191 }
192
193 #[test]
194 fn dispatch_modbus_on_502() {
195 let data = &[0x00u8, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0A];
196 let pkt = parse_tcp_application(data, 502, 12345).unwrap();
197 assert!(matches!(pkt, AppPacket::Modbus(_)));
198 }
199
200 #[test]
201 fn dispatch_smb2_on_445() {
202 let mut hdr = [0u8; 64];
203 hdr[4..8].copy_from_slice(b"\xfeSMB");
204 let pkt = parse_tcp_application(&hdr, 445, 12345).unwrap();
205 assert!(matches!(pkt, AppPacket::Smb2(_)));
206 }
207
208 #[test]
209 fn dispatch_rdp_on_3389() {
210 let data = [0x03u8, 0x00, 0x00, 0x2C, 0xE0];
211 let pkt = parse_tcp_application(&data, 3389, 12345).unwrap();
212 assert!(matches!(pkt, AppPacket::Rdp(_)));
213 }
214
215 #[test]
216 fn dispatch_radius_on_1812() {
217 let pkt = vec![1u8, 0, 0, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
218 let parsed = parse_udp_application(&pkt, 1812, 12345).unwrap();
219 assert!(matches!(parsed, AppPacket::Radius(_)));
220 }
221
222 #[test]
223 fn dispatch_mdns_on_5353() {
224 let q = DnsPacketBuilder::query()
225 .add_question("_http._tcp.local", DnsType::PTR, DnsClass::IN)
226 .build();
227 let pkt = parse_udp_application(&q, 5353, 12345).unwrap();
228 assert!(matches!(pkt, AppPacket::Mdns(_)));
229 }
230
231 #[test]
232 fn dispatch_llmnr_on_5355() {
233 let q = DnsPacketBuilder::query()
234 .add_question("wpad", DnsType::A, DnsClass::IN)
235 .build();
236 let pkt = parse_udp_application(&q, 12345, 5355).unwrap();
237 assert!(matches!(pkt, AppPacket::Llmnr(_)));
238 }
239
240 #[test]
241 fn dispatch_netbios_on_137() {
242 let mut data = vec![0x00, 0x01, 0x01, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
243 let mut enc = [0u8; 32];
244 let name = b"HOST1 ";
245 for (i, b) in name.iter().enumerate() {
246 enc[i * 2] = (b >> 4) + 0x41;
247 enc[i * 2 + 1] = (b & 0x0F) + 0x41;
248 }
249 data.extend_from_slice(&enc);
250 data.extend_from_slice(&[0x00, 0x20, 0x00, 0x01]);
251 let pkt = parse_udp_application(&data, 137, 12345).unwrap();
252 assert!(matches!(pkt, AppPacket::Netbios(_)));
253 }
254
255 #[test]
256 fn unknown_tcp_port_returns_none() {
257 assert!(parse_tcp_application(b"foo", 12345, 54321).is_none());
258 }
259
260 #[test]
261 fn unknown_udp_port_returns_none() {
262 assert!(parse_udp_application(b"foo", 12345, 54321).is_none());
263 }
264
265 #[test]
266 fn known_port_bad_data_returns_none() {
267 assert!(parse_tcp_application(b"not tls", 443, 12345).is_none());
268 assert!(parse_udp_application(&[0u8; 10], 1812, 12345).is_none());
269 }
270}