mirror of
https://github.com/GyulyVGC/sniffnet.git
synced 2026-06-05 21:06:52 +08:00
made Ipfix part of CaptureContext (WIP)
This commit is contained in:
parent
55bb9f7fe5
commit
22a282bd30
@ -158,7 +158,7 @@ fn test_restore_default_configs() {
|
||||
expanded: true,
|
||||
},
|
||||
import_pcap_path: "whole_day.pcapng".to_string(),
|
||||
ipfix_collector: Default::default(),
|
||||
ipfix_socket: Default::default(),
|
||||
export_pcap: ExportPcap {
|
||||
enabled: true,
|
||||
file_name: "sniffnet.pcap".to_string(),
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
use crate::gui::types::filters::Filters;
|
||||
use crate::gui::types::message::Message;
|
||||
use crate::gui::types::settings::Settings;
|
||||
use crate::networking::ipfix::IpfixCollectorConf;
|
||||
use crate::networking::ipfix::MyIpfixSocket;
|
||||
use crate::networking::types::capture_context::{CaptureSource, CaptureSourcePicklist};
|
||||
use crate::networking::types::my_device::MyDevice;
|
||||
use crate::networking::types::my_link_type::MyLinkType;
|
||||
@ -163,7 +163,7 @@ fn get_col_data_source(sniffer: &Sniffer, language: Language) -> Column<'_, Mess
|
||||
CaptureSourcePicklist::Ipfix => {
|
||||
col = col.push(get_col_ipfix_collector(
|
||||
language,
|
||||
&sniffer.conf.ipfix_collector,
|
||||
&sniffer.conf.ipfix_socket,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -319,7 +319,7 @@ fn get_col_import_pcap<'a>(
|
||||
|
||||
fn get_col_ipfix_collector<'a>(
|
||||
language: Language,
|
||||
conf: &IpfixCollectorConf,
|
||||
ipfix_socket: &MyIpfixSocket,
|
||||
) -> Column<'a, Message, StyleType> {
|
||||
let addr_row = Row::new()
|
||||
.align_y(Alignment::Center)
|
||||
@ -329,8 +329,8 @@ fn get_col_ipfix_collector<'a>(
|
||||
bind_address_translation(language)
|
||||
)))
|
||||
.push(
|
||||
TextInput::new("0.0.0.0", &conf.bind_addr)
|
||||
.on_input(Message::SetIpfixBindAddr)
|
||||
TextInput::new("0.0.0.0", &ipfix_socket.addr())
|
||||
.on_input(Message::SetIpfixAddr)
|
||||
.padding([2, 5]),
|
||||
);
|
||||
let port_row = Row::new()
|
||||
@ -338,8 +338,8 @@ fn get_col_ipfix_collector<'a>(
|
||||
.spacing(5)
|
||||
.push(Text::new(format!("{}:", port_translation(language))))
|
||||
.push(
|
||||
TextInput::new("4739", &conf.bind_port.to_string())
|
||||
.on_input(Message::SetIpfixBindPort)
|
||||
TextInput::new("4739", &ipfix_socket.port())
|
||||
.on_input(Message::SetIpfixPort)
|
||||
.padding([2, 5]),
|
||||
);
|
||||
let content = Column::new()
|
||||
|
||||
@ -37,6 +37,12 @@ pub fn waiting_page(sniffer: &Sniffer) -> Option<Container<'_, Message, StyleTyp
|
||||
Icon::Error.to_text().size(60),
|
||||
format!("{}\n\n{error}", error_translation(language)),
|
||||
)
|
||||
} else if matches!(cs, CaptureSource::Ipfix(_)) {
|
||||
// TODO!
|
||||
(
|
||||
Icon::File.to_text().size(60),
|
||||
reading_from_pcap_translation(language).to_string(),
|
||||
)
|
||||
} else if !link_type.is_supported() {
|
||||
(
|
||||
Icon::Forbidden.to_text().size(60),
|
||||
|
||||
@ -29,7 +29,6 @@
|
||||
use crate::mmdb::asn::ASN_MMDB;
|
||||
use crate::mmdb::country::COUNTRY_MMDB;
|
||||
use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders};
|
||||
use crate::networking::ipfix::MyIpfixCollector;
|
||||
use crate::networking::ipfix::collect::collect_ipfix;
|
||||
use crate::networking::parse_packets::BackendTrafficMessage;
|
||||
use crate::networking::parse_packets::parse_packets;
|
||||
@ -355,8 +354,8 @@ pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||
Message::ScaleFactorShortcut(increase) => self.scale_factor_shortcut(increase),
|
||||
Message::SetNewerReleaseStatus(status) => self.set_newer_release_status(status),
|
||||
Message::SetPcapImport(path) => self.set_pcap_import(path),
|
||||
Message::SetIpfixBindAddr(addr) => self.set_ipfix_bind_addr(addr),
|
||||
Message::SetIpfixBindPort(port) => self.set_ipfix_bind_port(&port),
|
||||
Message::SetIpfixAddr(addr) => self.set_ipfix_addr(addr),
|
||||
Message::SetIpfixPort(port) => self.set_ipfix_port(port),
|
||||
Message::PendingHosts(cap_id, host_msgs) => self.pending_hosts(cap_id, host_msgs),
|
||||
Message::OfflineGap(cap_id, gap) => self.offline_gap(cap_id, gap),
|
||||
Message::Periodic => self.periodic(),
|
||||
@ -507,8 +506,7 @@ fn set_capture_source(&mut self, cs_pick: CaptureSourcePicklist) {
|
||||
self.set_pcap_import(self.conf.import_pcap_path.clone());
|
||||
}
|
||||
CaptureSourcePicklist::Ipfix => {
|
||||
self.capture_source =
|
||||
CaptureSource::Ipfix(MyIpfixCollector::from_conf(&self.conf.ipfix_collector));
|
||||
self.capture_source = CaptureSource::Ipfix(self.conf.ipfix_socket.clone());
|
||||
}
|
||||
CaptureSourcePicklist::Device => {
|
||||
self.device_selection(&self.conf.device.device_name.clone());
|
||||
@ -820,24 +818,14 @@ fn set_pcap_import(&mut self, path: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ipfix_bind_addr(&mut self, addr: String) {
|
||||
self.conf.ipfix_collector.bind_addr = addr;
|
||||
self.capture_source =
|
||||
CaptureSource::Ipfix(MyIpfixCollector::from_conf(&self.conf.ipfix_collector));
|
||||
fn set_ipfix_addr(&mut self, addr: String) {
|
||||
self.conf.ipfix_socket.set_addr(addr);
|
||||
self.capture_source = CaptureSource::Ipfix(self.conf.ipfix_socket.clone());
|
||||
}
|
||||
|
||||
fn set_ipfix_bind_port(&mut self, raw: &str) {
|
||||
// Accept anything that parses as u16; ignore other input so the user can
|
||||
// edit the field freely without losing focus.
|
||||
if let Ok(port) = raw.parse::<u16>() {
|
||||
self.conf.ipfix_collector.bind_port = port;
|
||||
self.capture_source =
|
||||
CaptureSource::Ipfix(MyIpfixCollector::from_conf(&self.conf.ipfix_collector));
|
||||
} else if raw.is_empty() {
|
||||
self.conf.ipfix_collector.bind_port = 0;
|
||||
self.capture_source =
|
||||
CaptureSource::Ipfix(MyIpfixCollector::from_conf(&self.conf.ipfix_collector));
|
||||
}
|
||||
fn set_ipfix_port(&mut self, port: String) {
|
||||
self.conf.ipfix_socket.set_port(port);
|
||||
self.capture_source = CaptureSource::Ipfix(self.conf.ipfix_socket.clone());
|
||||
}
|
||||
|
||||
fn pending_hosts(&mut self, cap_id: usize, host_msgs: Vec<HostMessage>) {
|
||||
@ -1000,9 +988,6 @@ fn start(&mut self) -> Task<Message> {
|
||||
let current_device_name = &self.capture_source.get_name();
|
||||
self.device_selection(current_device_name);
|
||||
}
|
||||
if matches!(&self.capture_source, CaptureSource::Ipfix(_)) {
|
||||
return self.start_ipfix();
|
||||
}
|
||||
let pcap_path = self.conf.export_pcap.full_path();
|
||||
let capture_context =
|
||||
CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.conf.filters);
|
||||
@ -1018,27 +1003,46 @@ fn start(&mut self) -> Task<Message> {
|
||||
.set_link_type(capture_context.my_link_type());
|
||||
self.capture_source.set_addresses();
|
||||
let capture_source = self.capture_source.clone();
|
||||
self.traffic_chart
|
||||
.change_capture_source(matches!(capture_source, CaptureSource::Device(_)));
|
||||
self.traffic_chart.change_capture_source(matches!(
|
||||
capture_source,
|
||||
CaptureSource::Device(_) | CaptureSource::Ipfix(_)
|
||||
));
|
||||
let (tx, rx) = async_channel::unbounded();
|
||||
let (freeze_tx, freeze_rx) = tokio::sync::broadcast::channel(1_048_575);
|
||||
let freeze_rx2 = freeze_tx.subscribe();
|
||||
let filters = self.conf.filters.clone();
|
||||
let _ = thread::Builder::new()
|
||||
.name("thread_parse_packets".to_string())
|
||||
.spawn(move || {
|
||||
parse_packets(
|
||||
curr_cap_id,
|
||||
capture_source,
|
||||
mmdb_readers,
|
||||
&ip_blacklist,
|
||||
capture_context,
|
||||
filters,
|
||||
&tx,
|
||||
(freeze_rx, freeze_rx2),
|
||||
);
|
||||
})
|
||||
.log_err(location!());
|
||||
if let CaptureSource::Ipfix(collector) = &self.capture_source {
|
||||
let collector = collector.clone();
|
||||
let _ = thread::Builder::new()
|
||||
.name("thread_collect_ipfix".to_string())
|
||||
.spawn(move || {
|
||||
collect_ipfix(
|
||||
curr_cap_id,
|
||||
capture_context,
|
||||
mmdb_readers,
|
||||
&ip_blacklist,
|
||||
&tx,
|
||||
(freeze_rx, freeze_rx2),
|
||||
);
|
||||
})
|
||||
.log_err(location!());
|
||||
} else {
|
||||
let _ = thread::Builder::new()
|
||||
.name("thread_parse_packets".to_string())
|
||||
.spawn(move || {
|
||||
parse_packets(
|
||||
curr_cap_id,
|
||||
capture_source,
|
||||
mmdb_readers,
|
||||
&ip_blacklist,
|
||||
capture_context,
|
||||
filters,
|
||||
&tx,
|
||||
(freeze_rx, freeze_rx2),
|
||||
);
|
||||
})
|
||||
.log_err(location!());
|
||||
}
|
||||
self.current_capture_rx.1 = Some(rx.clone());
|
||||
self.freeze_tx = Some(freeze_tx);
|
||||
|
||||
@ -1085,47 +1089,6 @@ fn start(&mut self) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn start_ipfix(&mut self) -> Task<Message> {
|
||||
let CaptureSource::Ipfix(collector) = self.capture_source.clone() else {
|
||||
return Task::none();
|
||||
};
|
||||
self.pcap_error = None;
|
||||
self.running_page = Some(self.conf.last_opened_page);
|
||||
|
||||
let curr_cap_id = self.current_capture_rx.0;
|
||||
let mmdb_readers = self.mmdb_readers.clone();
|
||||
let ip_blacklist = self.ip_blacklist.clone();
|
||||
self.traffic_chart.change_capture_source(true);
|
||||
let (tx, rx) = async_channel::unbounded();
|
||||
let (freeze_tx, freeze_rx) = tokio::sync::broadcast::channel(1_048_575);
|
||||
let freeze_rx2 = freeze_tx.subscribe();
|
||||
let _ = thread::Builder::new()
|
||||
.name("thread_collect_ipfix".to_string())
|
||||
.spawn(move || {
|
||||
collect_ipfix(
|
||||
curr_cap_id,
|
||||
&collector,
|
||||
mmdb_readers,
|
||||
&ip_blacklist,
|
||||
&tx,
|
||||
(freeze_rx, freeze_rx2),
|
||||
);
|
||||
})
|
||||
.log_err(location!());
|
||||
self.current_capture_rx.1 = Some(rx.clone());
|
||||
self.freeze_tx = Some(freeze_tx);
|
||||
|
||||
Task::run(rx, |backend_msg| match backend_msg {
|
||||
BackendTrafficMessage::TickRun(cap_id, msg, host_msg, no_more_packets) => {
|
||||
Message::TickRun(cap_id, msg, host_msg, no_more_packets)
|
||||
}
|
||||
BackendTrafficMessage::PendingHosts(cap_id, host_msg) => {
|
||||
Message::PendingHosts(cap_id, host_msg)
|
||||
}
|
||||
BackendTrafficMessage::OfflineGap(cap_id, gap) => Message::OfflineGap(cap_id, gap),
|
||||
})
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Task<Message> {
|
||||
// close capture channel to kill previous captures
|
||||
if let Some(rx) = &self.current_capture_rx.1 {
|
||||
@ -1480,8 +1443,8 @@ fn register_sigint_handler() -> Task<Message> {
|
||||
pub fn is_capture_source_consistent(&self) -> bool {
|
||||
match (self.conf.capture_source_picklist, &self.capture_source) {
|
||||
(CaptureSourcePicklist::Device, CaptureSource::Device(_))
|
||||
| (CaptureSourcePicklist::File, CaptureSource::File(_)) => true,
|
||||
(CaptureSourcePicklist::Ipfix, CaptureSource::Ipfix(c)) => c.is_valid(),
|
||||
| (CaptureSourcePicklist::File, CaptureSource::File(_))
|
||||
| (CaptureSourcePicklist::Ipfix, CaptureSource::Ipfix(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -2278,7 +2241,7 @@ fn test_conf() {
|
||||
directory: "/".to_string()
|
||||
},
|
||||
import_pcap_path: "/test.pcap".to_string(),
|
||||
ipfix_collector: Default::default(),
|
||||
ipfix_socket: Default::default(),
|
||||
data_repr: DataRepr::Bits,
|
||||
}
|
||||
);
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
use crate::gui::types::favorite::Favorites;
|
||||
use crate::gui::types::filters::Filters;
|
||||
use crate::gui::types::settings::Settings;
|
||||
use crate::networking::ipfix::IpfixCollectorConf;
|
||||
use crate::networking::ipfix::MyIpfixSocket;
|
||||
use crate::networking::types::capture_context::CaptureSourcePicklist;
|
||||
use crate::networking::types::config_device::ConfigDevice;
|
||||
use crate::networking::types::data_representation::DataRepr;
|
||||
@ -48,9 +48,6 @@ pub struct Conf {
|
||||
/// Import path for PCAP file
|
||||
#[serde(deserialize_with = "deserialize_or_default")]
|
||||
pub import_pcap_path: String,
|
||||
/// IPFIX collector configuration (bind address and port)
|
||||
#[serde(deserialize_with = "deserialize_or_default")]
|
||||
pub ipfix_collector: IpfixCollectorConf,
|
||||
/// Remembers the last opened setting page
|
||||
#[serde(deserialize_with = "deserialize_or_default")]
|
||||
pub last_opened_setting: SettingsPage,
|
||||
@ -94,6 +91,9 @@ pub struct Conf {
|
||||
/// Information about PCAP file export
|
||||
#[serde(deserialize_with = "deserialize_or_default")]
|
||||
pub export_pcap: ExportPcap,
|
||||
/// IPFIX collector configuration (bind address and port)
|
||||
#[serde(deserialize_with = "deserialize_or_default")]
|
||||
pub ipfix_socket: MyIpfixSocket,
|
||||
/// Parameters from settings pages
|
||||
#[serde(deserialize_with = "deserialize_or_default")]
|
||||
pub settings: Settings,
|
||||
|
||||
@ -146,9 +146,9 @@ pub enum Message {
|
||||
/// Set the pcap import path
|
||||
SetPcapImport(String),
|
||||
/// Set the IPFIX collector bind address
|
||||
SetIpfixBindAddr(String),
|
||||
SetIpfixAddr(String),
|
||||
/// Set the IPFIX collector bind port
|
||||
SetIpfixBindPort(String),
|
||||
SetIpfixPort(String),
|
||||
/// Sent by the backend parsing packets at the end of an offline capture; includes all the pending hosts
|
||||
PendingHosts(usize, Vec<HostMessage>),
|
||||
/// Sent by offline captures: ticks without packets
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
use crate::location;
|
||||
use crate::mmdb::types::mmdb_reader::MmdbReaders;
|
||||
use crate::networking::ipfix::MyIpfixCollector;
|
||||
use crate::networking::ipfix::MyIpfixSocket;
|
||||
use crate::networking::ipfix::templates::TemplateCache;
|
||||
use crate::networking::ipfix::wire::{
|
||||
self, FlowRecord, IPFIX_VERSION, Set, decode_data_record, format_mac, parse_message,
|
||||
@ -25,6 +25,7 @@
|
||||
use crate::networking::types::address_port_pair::AddressPortPair;
|
||||
use crate::networking::types::arp_type::ArpType;
|
||||
use crate::networking::types::bogon::is_bogon;
|
||||
use crate::networking::types::capture_context::{CaptureContext, CaptureType};
|
||||
use crate::networking::types::data_info::DataInfo;
|
||||
use crate::networking::types::data_info_host::DataInfoHost;
|
||||
use crate::networking::types::icmp_type::IcmpType;
|
||||
@ -47,7 +48,7 @@
|
||||
/// second with the accumulated `InfoTraffic`.
|
||||
pub fn collect_ipfix(
|
||||
cap_id: usize,
|
||||
collector: &MyIpfixCollector,
|
||||
capture_context: CaptureContext,
|
||||
mmdb_readers: MmdbReaders,
|
||||
ip_blacklist: &IpBlacklist,
|
||||
tx: &Sender<BackendTrafficMessage>,
|
||||
@ -55,19 +56,9 @@ pub fn collect_ipfix(
|
||||
) {
|
||||
let (mut freeze_rx, _freeze_rx_2) = freeze_rxs;
|
||||
|
||||
let Some(bind) = collector.socket_addr() else {
|
||||
let (Some(CaptureType::Ipfix(socket)), None) = capture_context.consume() else {
|
||||
return;
|
||||
};
|
||||
let Ok(socket) = UdpSocket::bind(bind).log_err(location!()) else {
|
||||
return;
|
||||
};
|
||||
if socket
|
||||
.set_read_timeout(Some(RECV_TIMEOUT))
|
||||
.log_err(location!())
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut info_traffic_msg = InfoTraffic::default();
|
||||
let mut templates = TemplateCache::new();
|
||||
@ -416,6 +407,10 @@ fn build_key(record: &FlowRecord) -> Option<AddressPortPair> {
|
||||
/// Build a `[Address]` slice carrying just the exporter's IP, so direction
|
||||
/// classification treats the exporter as the local anchor.
|
||||
fn exporter_as_addresses(peer: IpAddr) -> Vec<Address> {
|
||||
if peer.is_loopback() || peer.is_unspecified() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
vec![Address {
|
||||
addr: peer,
|
||||
netmask: None,
|
||||
|
||||
@ -9,8 +9,11 @@
|
||||
pub mod templates;
|
||||
pub mod wire;
|
||||
|
||||
use crate::location;
|
||||
use crate::utils::error_logger::ErrorLogger;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::time::Duration;
|
||||
|
||||
/// IANA-registered default IPFIX collector port.
|
||||
pub const DEFAULT_IPFIX_PORT: u16 = 4739;
|
||||
@ -18,52 +21,50 @@
|
||||
/// Persisted IPFIX collector configuration.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
#[serde(default)]
|
||||
pub struct IpfixCollectorConf {
|
||||
pub bind_addr: String,
|
||||
pub bind_port: u16,
|
||||
pub struct MyIpfixSocket {
|
||||
addr: String,
|
||||
port: String,
|
||||
}
|
||||
|
||||
impl Default for IpfixCollectorConf {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bind_addr: String::from("0.0.0.0"),
|
||||
bind_port: DEFAULT_IPFIX_PORT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime handle for an IPFIX collector source, embedded in `CaptureSource::Ipfix`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MyIpfixCollector {
|
||||
bind_addr: String,
|
||||
bind_port: u16,
|
||||
}
|
||||
|
||||
impl MyIpfixCollector {
|
||||
pub fn new(bind_addr: String, bind_port: u16) -> Self {
|
||||
Self {
|
||||
bind_addr,
|
||||
bind_port,
|
||||
}
|
||||
impl MyIpfixSocket {
|
||||
pub fn addr(&self) -> &str {
|
||||
&self.addr
|
||||
}
|
||||
|
||||
pub fn from_conf(conf: &IpfixCollectorConf) -> Self {
|
||||
Self::new(conf.bind_addr.clone(), conf.bind_port)
|
||||
pub fn port(&self) -> &str {
|
||||
&self.port
|
||||
}
|
||||
|
||||
/// Parse the configured bind address + port into a `SocketAddr`. Returns
|
||||
/// `None` if `bind_addr` is not a valid IP literal.
|
||||
pub fn socket_addr(&self) -> Option<SocketAddr> {
|
||||
let ip: IpAddr = self.bind_addr.parse().ok()?;
|
||||
Some(SocketAddr::new(ip, self.bind_port))
|
||||
pub fn set_addr(&mut self, addr: String) {
|
||||
self.addr = addr;
|
||||
}
|
||||
|
||||
/// Whether the current config can be used to start a capture.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.socket_addr().is_some()
|
||||
pub fn set_port(&mut self, port: String) {
|
||||
self.port = port;
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> String {
|
||||
format!("{}:{}", self.bind_addr, self.bind_port)
|
||||
format!("{}:{}", self.addr, self.port)
|
||||
}
|
||||
|
||||
pub fn socket_addr(&self) -> Result<SocketAddr, String> {
|
||||
let port = self
|
||||
.port
|
||||
.parse::<u16>()
|
||||
.map_err(|_| format!("Invalid port number: {}", self.port))?;
|
||||
let ip_addr = self
|
||||
.addr
|
||||
.parse::<IpAddr>()
|
||||
.map_err(|_| format!("Invalid IP address: {}", self.addr))?;
|
||||
Ok(SocketAddr::new(ip_addr, port))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MyIpfixSocket {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
addr: IpAddr::V4(Ipv4Addr::UNSPECIFIED).to_string(),
|
||||
port: DEFAULT_IPFIX_PORT.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,8 +309,6 @@ pub(super) fn get_sniffable_headers(
|
||||
MyLinkType::LinuxSll(_) => from_linux_sll(packet, true),
|
||||
MyLinkType::LinuxSll2(_) => from_linux_sll(packet, false),
|
||||
MyLinkType::Null(_) | MyLinkType::Loop(_) => from_null(packet),
|
||||
// IPFIX never flows through this pcap-based path.
|
||||
MyLinkType::Ipfix => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::gui::types::conf::Conf;
|
||||
use crate::gui::types::filters::Filters;
|
||||
use crate::location;
|
||||
use crate::networking::ipfix::MyIpfixCollector;
|
||||
use crate::networking::ipfix::MyIpfixSocket;
|
||||
use crate::networking::types::my_device::MyDevice;
|
||||
use crate::networking::types::my_link_type::MyLinkType;
|
||||
use crate::translations::translations::network_adapter_translation;
|
||||
@ -11,11 +11,14 @@
|
||||
use crate::utils::error_logger::{ErrorLogger, Location};
|
||||
use pcap::{Active, Address, Capture, Device, Error, Packet, Savefile, Stat};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::UdpSocket;
|
||||
use std::time::Duration;
|
||||
|
||||
pub enum CaptureContext {
|
||||
Live(Live),
|
||||
LiveWithSavefile(LiveWithSavefile),
|
||||
Offline(Offline),
|
||||
Ipfix(Ipfix),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
@ -23,7 +26,7 @@ impl CaptureContext {
|
||||
pub fn new(source: &CaptureSource, pcap_out_path: Option<&String>, filters: &Filters) -> Self {
|
||||
let mut cap_type = match CaptureType::from_source(source, pcap_out_path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Self::Error(e.to_string()),
|
||||
Err(e) => return Self::Error(e),
|
||||
};
|
||||
|
||||
// only apply BPF filter if it is active, and return an error if it fails to apply
|
||||
@ -36,6 +39,7 @@ pub fn new(source: &CaptureSource, pcap_out_path: Option<&String>, filters: &Fil
|
||||
let cap = match cap_type {
|
||||
CaptureType::Live(cap) => cap,
|
||||
CaptureType::Offline(cap) => return Self::new_offline(cap),
|
||||
CaptureType::Ipfix(socket) => return Self::Ipfix(Ipfix { socket }),
|
||||
};
|
||||
|
||||
if let Some(out_path) = pcap_out_path {
|
||||
@ -78,6 +82,7 @@ pub fn consume(self) -> (Option<CaptureType>, Option<Savefile>) {
|
||||
(Some(CaptureType::Live(onws.live.cap)), Some(onws.savefile))
|
||||
}
|
||||
Self::Offline(off) => (Some(CaptureType::Offline(off.cap)), None),
|
||||
Self::Ipfix(ipfix) => (Some(CaptureType::Ipfix(ipfix.socket)), None),
|
||||
Self::Error(_) => (None, None),
|
||||
}
|
||||
}
|
||||
@ -89,6 +94,7 @@ pub fn my_link_type(&self) -> MyLinkType {
|
||||
MyLinkType::from_pcap_link_type(onws.live.cap.get_datalink())
|
||||
}
|
||||
Self::Offline(off) => MyLinkType::from_pcap_link_type(off.cap.get_datalink()),
|
||||
Self::Ipfix(_) => MyLinkType::default(),
|
||||
Self::Error(_) => MyLinkType::default(),
|
||||
}
|
||||
}
|
||||
@ -107,9 +113,14 @@ pub struct Offline {
|
||||
cap: Capture<pcap::Offline>,
|
||||
}
|
||||
|
||||
pub struct Ipfix {
|
||||
socket: UdpSocket,
|
||||
}
|
||||
|
||||
pub enum CaptureType {
|
||||
Live(Capture<Active>),
|
||||
Offline(Capture<pcap::Offline>),
|
||||
Ipfix(UdpSocket),
|
||||
}
|
||||
|
||||
impl CaptureType {
|
||||
@ -117,6 +128,9 @@ pub fn next_packet(&mut self) -> Result<Packet<'_>, Error> {
|
||||
match self {
|
||||
Self::Live(on) => on.next_packet(),
|
||||
Self::Offline(off) => off.next_packet(),
|
||||
Self::Ipfix(_) => Err(Error::PcapError(
|
||||
"Cannot capture packets from IPFIX source".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,13 +138,17 @@ pub fn stats(&mut self) -> Result<Stat, Error> {
|
||||
match self {
|
||||
Self::Live(on) => on.stats(),
|
||||
Self::Offline(off) => off.stats(),
|
||||
Self::Ipfix(_) => Err(Error::PcapError(
|
||||
"Cannot get stats from IPFIX source".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_source(source: &CaptureSource, pcap_out_path: Option<&String>) -> Result<Self, Error> {
|
||||
fn from_source(source: &CaptureSource, pcap_out_path: Option<&String>) -> Result<Self, String> {
|
||||
match source {
|
||||
CaptureSource::Device(device) => {
|
||||
let inactive = Capture::from_device(device.to_pcap_device())?;
|
||||
let inactive =
|
||||
Capture::from_device(device.to_pcap_device()).map_err(|e| e.to_string())?;
|
||||
let cap = inactive
|
||||
.promisc(false)
|
||||
.buffer_size(2_000_000) // 2MB buffer -> 10k packets of 200 bytes
|
||||
@ -141,13 +159,21 @@ fn from_source(source: &CaptureSource, pcap_out_path: Option<&String>) -> Result
|
||||
})
|
||||
.immediate_mode(false)
|
||||
.timeout(150) // ensure UI is updated even if no packets are captured
|
||||
.open()?;
|
||||
.open()
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(Self::Live(cap))
|
||||
}
|
||||
CaptureSource::File(file) => Ok(Self::Offline(Capture::from_file(&file.path)?)),
|
||||
CaptureSource::Ipfix(_) => Err(Error::PcapError(String::from(
|
||||
"IPFIX collector does not use a pcap capture",
|
||||
))),
|
||||
CaptureSource::File(file) => Ok(Self::Offline(
|
||||
Capture::from_file(&file.path).map_err(|e| e.to_string())?,
|
||||
)),
|
||||
CaptureSource::Ipfix(ipfix_socket) => {
|
||||
let socket = UdpSocket::bind(ipfix_socket.socket_addr()?)
|
||||
.map_err(|e| format!("Failed to bind UDP socket: {e}"))?;
|
||||
socket
|
||||
.set_read_timeout(Some(Duration::from_millis(150)))
|
||||
.map_err(|e| format!("Failed to set socket read timeout: {e}"))?;
|
||||
Ok(Self::Ipfix(socket))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,6 +181,9 @@ fn set_bpf(&mut self, bpf: &str) -> Result<(), Error> {
|
||||
match self {
|
||||
Self::Live(cap) => cap.filter(bpf, true),
|
||||
Self::Offline(cap) => cap.filter(bpf, true),
|
||||
Self::Ipfix(_) => Err(Error::PcapError(
|
||||
"Cannot set BPF filter on IPFIX source".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +208,7 @@ pub fn resume(&mut self, filters: &Filters) {
|
||||
pub enum CaptureSource {
|
||||
Device(MyDevice),
|
||||
File(MyPcapImport),
|
||||
Ipfix(MyIpfixCollector),
|
||||
Ipfix(MyIpfixSocket),
|
||||
}
|
||||
|
||||
impl CaptureSource {
|
||||
@ -193,9 +222,7 @@ pub fn from_conf(conf: &Conf) -> Self {
|
||||
let path = conf.import_pcap_path.clone();
|
||||
Self::File(MyPcapImport::new(path))
|
||||
}
|
||||
CaptureSourcePicklist::Ipfix => {
|
||||
Self::Ipfix(MyIpfixCollector::from_conf(&conf.ipfix_collector))
|
||||
}
|
||||
CaptureSourcePicklist::Ipfix => Self::Ipfix(conf.ipfix_socket.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,7 +265,7 @@ pub fn get_link_type(&self) -> MyLinkType {
|
||||
match self {
|
||||
Self::Device(device) => device.get_link_type(),
|
||||
Self::File(file) => file.link_type,
|
||||
Self::Ipfix(_) => MyLinkType::Ipfix,
|
||||
Self::Ipfix(_) => MyLinkType::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,8 +15,6 @@ pub enum MyLinkType {
|
||||
LinuxSll(Linktype),
|
||||
LinuxSll2(Linktype),
|
||||
Unsupported(Linktype),
|
||||
/// IPFIX collector — flow records over UDP, no underlying link layer
|
||||
Ipfix,
|
||||
#[default]
|
||||
NotYetAssigned,
|
||||
}
|
||||
@ -62,9 +60,6 @@ pub fn full_print_on_one_line(self, language: Language) -> String {
|
||||
}
|
||||
)
|
||||
}
|
||||
Self::Ipfix => {
|
||||
format!("{}: IPFIX", link_type_translation(language))
|
||||
}
|
||||
Self::NotYetAssigned => {
|
||||
format!("{}: -", link_type_translation(language))
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user