Skip to content

Commit 1d2fdef

Browse files
committed
add support for IGMP
1 parent fd56567 commit 1d2fdef

13 files changed

Lines changed: 153 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ All Sniffnet releases with the relative changes are documented in this file.
44

55
## [UNRELEASED]
66
- IPFIX collector capabilities: receive and analyze network traffic from remote devices ([#1270](https://github.com/GyulyVGC/sniffnet/pull/1270) — fixes [#303](https://github.com/GyulyVGC/sniffnet/issues/303))
7+
- Added support for IGMP connections and messages ([#1301](https://github.com/GyulyVGC/sniffnet/pull/1301) — fixes [#1269](https://github.com/GyulyVGC/sniffnet/issues/1269))
78
- Show output file path in Overview page when exporting a PCAP file ([`3f2c42c`](https://github.com/GyulyVGC/sniffnet/pull/1290/commits/3f2c42c70f06d5d29ddad80dde956281c6705511))
89
- Fix the app freezing and exhausting memory when importing a PCAP file containing a large time gap between consecutive packets ([`4605ec7`](https://github.com/GyulyVGC/sniffnet/pull/1290/commits/4605ec717bbaf8fa34268805814d5585f084201e))
910
- Restrict the PCAP export file to have a valid PCAP extension, so that files of other types can't be overwritten ([`5b4801b`](https://github.com/GyulyVGC/sniffnet/pull/1290/commits/5b4801b438ff1c7968cfac423a8e8585778309a8))

lib/sniffnet-packet-parser/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,9 @@
22

33
All `sniffnet-packet-parser` releases with the relative changes are documented in this file.
44

5+
## [UNRELEASED]
6+
### Added
7+
- IGMP support ([#1301](https://github.com/GyulyVGC/sniffnet/pull/1301))
8+
59
## [0.1.0] - 2026-09-02
610
- Initial release of `sniffnet-packet-parser`, the network packet parser for Sniffnet

lib/sniffnet-packet-parser/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Powered by [`etherparse`](https://github.com/JulianSchmid/etherparse).
1010
- `UDP`
1111
- `ICMPv4`
1212
- `ICMPv6`
13+
- `IGMP`
1314
- `ARP`
1415

1516
## Supported link types

lib/sniffnet-packet-parser/src/headers.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::igmp_type::IgmpType;
12
use crate::link_type::LinkType;
23
use crate::{ArpType, IcmpType, Protocol};
34
use etherparse::{EtherType, LaxPacketHeaders};
@@ -36,6 +37,8 @@ pub struct TransportInfo {
3637
pub protocol: Protocol,
3738
/// ICMP message type, if the packet is an ICMP packet.
3839
pub icmp_type: Option<IcmpType>,
40+
/// IGMP message type, if the packet is an IGMP packet.
41+
pub igmp_type: Option<IgmpType>,
3942
}
4043

4144
#[must_use]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use std::fmt::{Display, Formatter};
2+
3+
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
4+
/// The IGMP message type.
5+
pub enum IgmpType {
6+
MembershipQuery,
7+
MembershipReportV1,
8+
MembershipReportV2,
9+
MembershipReportV3,
10+
LeaveGroup,
11+
Dvmrp,
12+
Pim,
13+
CiscoTrace,
14+
MulticastTracerouteResponse,
15+
MulticastTraceroute,
16+
MulticastRouterAdvertisement,
17+
MulticastRouterSolicitation,
18+
MulticastRouterTermination,
19+
Unknown,
20+
}
21+
22+
impl IgmpType {
23+
#[must_use]
24+
pub(crate) fn from_etherparse(igmp_type: &etherparse::IgmpType) -> IgmpType {
25+
match igmp_type {
26+
etherparse::IgmpType::MembershipQuery(_)
27+
| etherparse::IgmpType::MembershipQueryWithSources(_) => IgmpType::MembershipQuery,
28+
etherparse::IgmpType::MembershipReportV1(_) => IgmpType::MembershipReportV1,
29+
etherparse::IgmpType::MembershipReportV2(_) => IgmpType::MembershipReportV2,
30+
etherparse::IgmpType::MembershipReportV3(_) => IgmpType::MembershipReportV3,
31+
etherparse::IgmpType::LeaveGroup(_) => IgmpType::LeaveGroup,
32+
etherparse::IgmpType::Unknown(h) => match h.igmp_type {
33+
0x13 => IgmpType::Dvmrp,
34+
0x14 => IgmpType::Pim,
35+
0x15 => IgmpType::CiscoTrace,
36+
0x1e => IgmpType::MulticastTracerouteResponse,
37+
0x1f => IgmpType::MulticastTraceroute,
38+
0x30 => IgmpType::MulticastRouterAdvertisement,
39+
0x31 => IgmpType::MulticastRouterSolicitation,
40+
0x32 => IgmpType::MulticastRouterTermination,
41+
_ => IgmpType::Unknown,
42+
},
43+
}
44+
}
45+
}
46+
47+
impl Display for IgmpType {
48+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49+
write!(
50+
f,
51+
"{}",
52+
match self {
53+
IgmpType::MembershipQuery => "Membership Query",
54+
IgmpType::MembershipReportV1 => "Membership Report v1",
55+
IgmpType::MembershipReportV2 => "Membership Report v2",
56+
IgmpType::MembershipReportV3 => "Membership Report v3",
57+
IgmpType::LeaveGroup => "Leave Group",
58+
IgmpType::Dvmrp => "DVMRP",
59+
IgmpType::Pim => "PIM v1",
60+
IgmpType::CiscoTrace => "Cisco Trace",
61+
IgmpType::MulticastTracerouteResponse => "Multicast Traceroute Response",
62+
IgmpType::MulticastTraceroute => "Multicast Traceroute",
63+
IgmpType::MulticastRouterAdvertisement => "Multicast Router Advertisement",
64+
IgmpType::MulticastRouterSolicitation => "Multicast Router Solicitation",
65+
IgmpType::MulticastRouterTermination => "Multicast Router Termination",
66+
IgmpType::Unknown => "?",
67+
}
68+
)
69+
}
70+
}

lib/sniffnet-packet-parser/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ mod headers;
77
#[cfg(feature = "full")]
88
mod icmp_type;
99
#[cfg(feature = "full")]
10+
mod igmp_type;
11+
#[cfg(feature = "full")]
1012
mod link_type;
1113
#[cfg(feature = "full")]
1214
mod packet;
@@ -19,6 +21,8 @@ pub use headers::{LinkInfo, NetInfo, TransportInfo};
1921
#[cfg(feature = "full")]
2022
pub use icmp_type::{IcmpType, IcmpTypeV4, IcmpTypeV6};
2123
#[cfg(feature = "full")]
24+
pub use igmp_type::IgmpType;
25+
#[cfg(feature = "full")]
2226
pub use link_type::LinkType;
2327
#[cfg(feature = "full")]
2428
pub use packet::ParsedPacket;

lib/sniffnet-packet-parser/src/packet.rs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use etherparse::{ArpHardwareId, EtherType, LinkHeader, NetHeaders, TransportHead
55
use crate::arp_type::ArpType;
66
use crate::headers::{LinkInfo, NetInfo, TransportInfo, get_sniffable_headers};
77
use crate::icmp_type::{IcmpTypeV4, IcmpTypeV6};
8+
use crate::igmp_type::IgmpType;
89
use crate::link_type::LinkType;
910
use crate::protocol::Protocol;
1011

@@ -37,6 +38,7 @@ impl ParsedPacket {
3738
dst_port: None,
3839
protocol: Protocol::Arp,
3940
icmp_type: None,
41+
igmp_type: None,
4042
})
4143
} else {
4244
analyze_transport_header(headers.transport)
@@ -78,11 +80,10 @@ fn analyze_link_header(link_header: Option<LinkHeader>) -> LinkInfo {
7880
} else {
7981
None
8082
};
81-
let dst_mac = None;
8283
let bytes = 16;
8384
LinkInfo {
8485
src_mac,
85-
dst_mac,
86+
dst_mac: None,
8687
bytes,
8788
}
8889
}
@@ -151,11 +152,11 @@ fn analyze_net_header(network_header: Option<NetHeaders>) -> Option<NetInfo> {
151152
_ => return None,
152153
};
153154
let bytes = arp_packet.packet_len();
154-
let arp_type = ArpType::from_etherparse(arp_packet.operation);
155+
let arp_type = Some(ArpType::from_etherparse(arp_packet.operation));
155156
Some(NetInfo {
156157
src_ip,
157158
dst_ip,
158-
arp_type: Some(arp_type),
159+
arp_type,
159160
bytes,
160161
})
161162
}
@@ -176,6 +177,7 @@ fn analyze_transport_header(transport_header: Option<TransportHeader>) -> Option
176177
dst_port,
177178
protocol,
178179
icmp_type: None,
180+
igmp_type: None,
179181
})
180182
}
181183
Some(TransportHeader::Tcp(tcp_header)) => {
@@ -187,36 +189,41 @@ fn analyze_transport_header(transport_header: Option<TransportHeader>) -> Option
187189
dst_port,
188190
protocol,
189191
icmp_type: None,
192+
igmp_type: None,
190193
})
191194
}
192195
Some(TransportHeader::Icmpv4(icmpv4_header)) => {
193-
let src_port = None;
194-
let dst_port = None;
195196
let protocol = Protocol::Icmpv4;
196-
let icmp_type = IcmpTypeV4::from_etherparse(&icmpv4_header.icmp_type);
197+
let icmp_type = Some(IcmpTypeV4::from_etherparse(&icmpv4_header.icmp_type));
197198
Some(TransportInfo {
198-
src_port,
199-
dst_port,
199+
src_port: None,
200+
dst_port: None,
200201
protocol,
201-
icmp_type: Some(icmp_type),
202+
icmp_type,
203+
igmp_type: None,
202204
})
203205
}
204206
Some(TransportHeader::Icmpv6(icmpv6_header)) => {
205-
let src_port = None;
206-
let dst_port = None;
207207
let protocol = Protocol::Icmpv6;
208-
let icmp_type = IcmpTypeV6::from_etherparse(&icmpv6_header.icmp_type);
208+
let icmp_type = Some(IcmpTypeV6::from_etherparse(&icmpv6_header.icmp_type));
209209
Some(TransportInfo {
210-
src_port,
211-
dst_port,
210+
src_port: None,
211+
dst_port: None,
212212
protocol,
213-
icmp_type: Some(icmp_type),
213+
icmp_type,
214+
igmp_type: None,
214215
})
215216
}
216-
Some(TransportHeader::Igmp(_)) => {
217-
#[allow(clippy::match_same_arms)]
218-
// TODO!
219-
None
217+
Some(TransportHeader::Igmp(igmp_header)) => {
218+
let protocol = Protocol::Igmp;
219+
let igmp_type = Some(IgmpType::from_etherparse(&igmp_header.igmp_type));
220+
Some(TransportInfo {
221+
src_port: None,
222+
dst_port: None,
223+
protocol,
224+
icmp_type: None,
225+
igmp_type,
226+
})
220227
}
221228
None => None,
222229
}

src/gui/pages/connection_details_page.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,6 @@ fn col_info<'a>(
176176
val.traffic_direction,
177177
) == TrafficType::Unicast;
178178
let measure_latency = sniffer.capture_source.supports_latency() && is_unicast;
179-
let is_icmp = key.protocol.is_icmp();
180-
let is_arp = key.protocol.eq(&Protocol::Arp);
181179

182180
let mut ret_val = Column::new()
183181
.spacing(10)
@@ -209,7 +207,7 @@ fn col_info<'a>(
209207
&key.protocol.to_string(),
210208
));
211209

212-
if !is_icmp && !is_arp {
210+
if !key.protocol.is_portless() {
213211
ret_val = ret_val.push(TextType::highlighted_subtitle_with_desc(
214212
service_translation(language),
215213
&val.service.to_string(),
@@ -252,12 +250,11 @@ fn col_info<'a>(
252250
));
253251
}
254252

255-
let messages = if is_icmp {
256-
pretty_print_message_types(&val.icmp_types)
257-
} else if is_arp {
258-
pretty_print_message_types(&val.arp_types)
259-
} else {
260-
String::new()
253+
let messages = match key.protocol {
254+
Protocol::Icmpv4 | Protocol::Icmpv6 => pretty_print_message_types(&val.icmp_types),
255+
Protocol::Arp => pretty_print_message_types(&val.arp_types),
256+
Protocol::Igmp => pretty_print_message_types(&val.igmp_types),
257+
_ => String::new(),
261258
};
262259

263260
if !messages.is_empty() {

src/networking/ipfix/collect.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ fn ingest_flow_record(
237237
mac_addresses,
238238
None,
239239
None,
240+
None,
240241
packets,
241242
bytes,
242243
ip_blacklist,

src/networking/manage_packets.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use crate::networking::types::service_query::ServiceQuery;
1818
use crate::networking::types::traffic_direction::TrafficDirection;
1919
use crate::networking::types::traffic_type::TrafficType;
2020
use crate::utils::types::timestamp::Timestamp;
21-
use sniffnet_packet_parser::ArpType;
2221
use sniffnet_packet_parser::IcmpType;
22+
use sniffnet_packet_parser::{ArpType, IgmpType};
2323
use std::time::Instant;
2424

2525
include!(concat!(env!("OUT_DIR"), "/services.rs"));
@@ -76,14 +76,15 @@ pub fn get_service(
7676
}
7777

7878
/// Function to insert the source and destination of a packet into the map containing the analyzed traffic
79-
#[allow(clippy::too_many_arguments)]
79+
#[allow(clippy::too_many_arguments, clippy::similar_names)]
8080
pub fn modify_or_insert_in_map(
8181
info_traffic_msg: &mut InfoTraffic,
8282
key: &AddressPortPair,
8383
my_interface_addresses: &[Address],
8484
mac_addresses: (Option<[u8; 6]>, Option<[u8; 6]>),
8585
icmp_type: Option<IcmpType>,
8686
arp_type: Option<ArpType>,
87+
igmp_type: Option<IgmpType>,
8788
packets: u128,
8889
bytes: u128,
8990
ip_blacklist: &IpBlacklist,
@@ -147,6 +148,14 @@ pub fn modify_or_insert_in_map(
147148
.and_modify(|n| *n += 1)
148149
.or_insert(1);
149150
}
151+
if key.protocol.eq(&Protocol::Igmp)
152+
&& let Some(igmp_type) = igmp_type
153+
{
154+
info.igmp_types
155+
.entry(igmp_type)
156+
.and_modify(|n| *n += 1)
157+
.or_insert(1);
158+
}
150159
})
151160
.or_insert_with(|| InfoAddressPortPair {
152161
mac_address1: mac_addresses.0,
@@ -172,6 +181,13 @@ pub fn modify_or_insert_in_map(
172181
} else {
173182
HashMap::new()
174183
},
184+
igmp_types: if key.protocol.eq(&Protocol::Igmp)
185+
&& let Some(igmp_type) = igmp_type
186+
{
187+
HashMap::from([(igmp_type, 1)])
188+
} else {
189+
HashMap::new()
190+
},
175191
is_blacklisted,
176192
program: Program::NotApplicable,
177193
});

0 commit comments

Comments
 (0)