Skip to main content

fosr_lib/
utils.rs

1use crate::structs::{FlowId, L4Proto, PacketDirection};
2use indicatif::ProgressBar;
3use indicatif::ProgressStyle;
4use pcap_file::pcap;
5use pnet_packet::Packet;
6use pnet_packet::ip::IpNextHeaderProtocols;
7use pnet_packet::{ethernet, ipv4, tcp, udp};
8use rand::Rng;
9use std::collections::HashMap;
10use std::fs::File;
11use std::fs::OpenOptions;
12use std::io::BufReader;
13use std::io::BufWriter;
14use std::io::Write;
15use std::net::Ipv4Addr;
16use std::path::Path;
17use std::time::Duration;
18
19const DURATION_THRESHOLD: Duration = Duration::from_secs(600);
20
21// timestamp,duration,protocol,src_ip,dst_ip,dst_port,fwd_packets,bwd_packets,fwd_bytes,bwd_bytes,time_sequence,payloads
22
23#[derive(Debug)]
24/// Flow statistics
25pub struct FlowStats {
26    pub timestamp: Duration,
27    pub duration: Duration,
28    pub protocol: L4Proto,
29    pub src_ip: Ipv4Addr,
30    pub dst_ip: Ipv4Addr,
31    pub src_port: u16,
32    pub dst_port: u16,
33    pub ttl_client: u8,
34    pub ttl_server: u8,
35    pub fwd_packets_count: usize,
36    pub bwd_packets_count: usize,
37    pub fwd_bytes: usize,
38    pub bwd_bytes: usize,
39    pub payloads: Vec<Vec<u8>>,
40    pub directions: Vec<PacketDirection>,
41    pub flags: Vec<u8>, // empty when it is not TCP
42    pub iat: Vec<Duration>,
43}
44
45#[allow(clippy::upper_case_acronyms)]
46enum PacketInfo {
47    TCP(TCPPacketInfo),
48    UDP(UDPPacketInfo),
49    ICMP(ICMPPacketInfo),
50}
51
52trait PacketInfoTrait {
53    fn ts(&self) -> Duration;
54    fn ttl(&self) -> u8;
55    fn src_ip(&self) -> Ipv4Addr;
56    fn payload(self) -> Vec<u8>;
57    fn payload_size(&self) -> usize;
58}
59
60#[derive(Debug)]
61struct TCPPacketInfo {
62    payload: Vec<u8>,
63    ts: Duration,
64    flags: u8,
65    src_ip: Ipv4Addr,
66    ttl: u8,
67}
68
69impl PacketInfoTrait for TCPPacketInfo {
70    fn ts(&self) -> Duration {
71        self.ts
72    }
73    fn ttl(&self) -> u8 {
74        self.ttl
75    }
76    fn src_ip(&self) -> Ipv4Addr {
77        self.src_ip
78    }
79    fn payload(self) -> Vec<u8> {
80        self.payload
81    }
82    fn payload_size(&self) -> usize {
83        self.payload.len()
84    }
85}
86
87#[derive(Debug)]
88struct ICMPPacketInfo {
89    // we assume no payload
90    // we may need to add more fields to correctly generate them
91    ts: Duration,
92    src_ip: Ipv4Addr,
93    ttl: u8,
94}
95
96impl PacketInfoTrait for ICMPPacketInfo {
97    fn ts(&self) -> Duration {
98        self.ts
99    }
100    fn ttl(&self) -> u8 {
101        self.ttl
102    }
103    fn src_ip(&self) -> Ipv4Addr {
104        self.src_ip
105    }
106    fn payload(self) -> Vec<u8> {
107        vec![]
108    }
109    fn payload_size(&self) -> usize {
110        0
111    }
112}
113
114#[derive(Debug)]
115struct UDPPacketInfo {
116    payload: Vec<u8>,
117    ts: Duration,
118    src_ip: Ipv4Addr,
119    ttl: u8,
120}
121
122impl PacketInfoTrait for UDPPacketInfo {
123    fn ts(&self) -> Duration {
124        self.ts
125    }
126    fn ttl(&self) -> u8 {
127        self.ttl
128    }
129    fn src_ip(&self) -> Ipv4Addr {
130        self.src_ip
131    }
132    fn payload(self) -> Vec<u8> {
133        self.payload
134    }
135    fn payload_size(&self) -> usize {
136        self.payload.len()
137    }
138}
139
140impl From<pcap::PcapPacket<'_>> for PacketInfo {
141    fn from(p: pcap::PcapPacket<'_>) -> PacketInfo {
142        let eth_packet = ethernet::EthernetPacket::new(&p.data).unwrap();
143        let ip_packet = ipv4::Ipv4Packet::new(eth_packet.payload()).unwrap();
144        let ttl = ip_packet.get_ttl();
145
146        match ip_packet.get_next_level_protocol() {
147            IpNextHeaderProtocols::Tcp => {
148                let tcp_packet = tcp::TcpPacket::new(ip_packet.payload()).unwrap();
149                PacketInfo::TCP(TCPPacketInfo {
150                    payload: tcp_packet.payload().to_vec(),
151                    ts: p.timestamp,
152                    flags: tcp_packet.get_flags(),
153                    src_ip: ip_packet.get_source(),
154                    ttl,
155                })
156            }
157            IpNextHeaderProtocols::Udp => {
158                let udp_packet = udp::UdpPacket::new(ip_packet.payload()).unwrap();
159                PacketInfo::UDP(UDPPacketInfo {
160                    payload: udp_packet.payload().to_vec(),
161                    ts: p.timestamp,
162                    src_ip: ip_packet.get_source(),
163                    ttl,
164                })
165            }
166            IpNextHeaderProtocols::Icmp => PacketInfo::ICMP(ICMPPacketInfo {
167                ts: p.timestamp,
168                src_ip: ip_packet.get_source(),
169                ttl,
170            }),
171
172            _ => {
173                // log::error!("Unsupported protocol: {proto}");
174                panic!("Unsupported protocol")
175            }
176        }
177    }
178}
179
180impl FlowStats {
181    /// Extract flow statistics from a flow
182    fn process_packets<T: PacketInfoTrait>(flow_id: FlowId, packets: Vec<T>) -> FlowStats {
183        let first_packet = packets.first().unwrap(); // we know there is a least one packet
184        let timestamp = first_packet.ts();
185        let duration = packets
186            .last()
187            .unwrap()
188            .ts()
189            .checked_sub(first_packet.ts())
190            .unwrap();
191
192        let iat: Vec<Duration> = packets
193            .windows(2)
194            .map(|packets| packets[1].ts().checked_sub(packets[0].ts()).unwrap())
195            .collect();
196
197        let fwd_bytes: usize = packets
198            .iter()
199            .filter_map(|p| {
200                if p.src_ip() == flow_id.src_ip {
201                    Some(p.payload_size())
202                } else {
203                    None
204                }
205            })
206            .sum();
207
208        let bwd_bytes: usize = packets
209            .iter()
210            .filter_map(|p| {
211                if p.src_ip() == flow_id.src_ip {
212                    None
213                } else {
214                    Some(p.payload_size())
215                }
216            })
217            .sum();
218
219        // use the first TTL we find. We assume the TTLs are constant
220        // we could use the median value instead
221        let ttl_client = packets
222            .iter()
223            .find(|p| p.src_ip() == flow_id.src_ip)
224            .map_or(0, PacketInfoTrait::ttl);
225
226        let ttl_server = packets
227            .iter()
228            .find(|p| p.src_ip() != flow_id.src_ip)
229            .map_or(0, PacketInfoTrait::ttl);
230
231        let directions: Vec<PacketDirection> = packets
232            .iter()
233            .map(|p| {
234                if p.src_ip() == flow_id.src_ip {
235                    PacketDirection::Forward
236                } else {
237                    PacketDirection::Backward
238                }
239            })
240            .collect();
241
242        let fwd_packets_count = directions
243            .iter()
244            .filter(|&d| *d == PacketDirection::Forward)
245            .count();
246
247        let bwd_packets_count = directions
248            .iter()
249            .filter(|&d| *d == PacketDirection::Backward)
250            .count();
251
252        let payloads: Vec<Vec<u8>> = packets.into_iter().map(PacketInfoTrait::payload).collect();
253
254        FlowStats {
255            timestamp,
256            duration,
257            protocol: flow_id.protocol,
258            src_ip: flow_id.src_ip,
259            dst_ip: flow_id.dst_ip,
260            src_port: flow_id.src_port,
261            dst_port: flow_id.dst_port,
262            ttl_client,
263            ttl_server,
264            fwd_packets_count,
265            bwd_packets_count,
266            fwd_bytes,
267            bwd_bytes,
268            payloads,
269            directions,
270            flags: vec![],
271            iat,
272        }
273    }
274
275    fn new_from_tcp(flow_id: FlowId, packets: Vec<TCPPacketInfo>) -> Self {
276        // get the flags before the packets are consumed
277        let flags = packets.iter().map(|p| p.flags).collect();
278        let mut stats = Self::process_packets(flow_id, packets);
279        stats.flags = flags;
280        stats
281    }
282
283    fn new_from_udp(flow_id: FlowId, packets: Vec<UDPPacketInfo>) -> Self {
284        Self::process_packets(flow_id, packets)
285    }
286
287    fn new_from_icmp(flow_id: FlowId, packets: Vec<ICMPPacketInfo>) -> Self {
288        Self::process_packets(flow_id, packets)
289    }
290}
291
292fn flow_id_from_packet(data: &[u8]) -> Option<FlowId> {
293    let eth_packet = ethernet::EthernetPacket::new(data).unwrap();
294    let ip_packet = ipv4::Ipv4Packet::new(eth_packet.payload()).unwrap();
295
296    let (protocol, src_port, dst_port) = match ip_packet.get_next_level_protocol() {
297        IpNextHeaderProtocols::Tcp => {
298            let tcp_packet = tcp::TcpPacket::new(ip_packet.payload())?;
299            (
300                L4Proto::TCP,
301                tcp_packet.get_source(),
302                tcp_packet.get_destination(),
303            )
304        }
305        IpNextHeaderProtocols::Udp => {
306            let udp_packet = udp::UdpPacket::new(ip_packet.payload()).unwrap();
307            (
308                L4Proto::UDP,
309                udp_packet.get_source(),
310                udp_packet.get_destination(),
311            )
312        }
313        IpNextHeaderProtocols::Icmp => (L4Proto::ICMP, 0, 0),
314
315        _ => {
316            // log::error!("Unsupported protocol: {proto}");
317            return None;
318        }
319    };
320
321    Some(FlowId {
322        protocol,
323        src_ip: ip_packet.get_source(),
324        dst_ip: ip_packet.get_destination(),
325        src_port,
326        dst_port,
327    })
328}
329
330/// Export flow statistics to a file
331pub fn export_stats(file: &str, stats: Vec<FlowStats>, include_payloads: bool) {
332    let file = File::create(file).expect("Cannot open file");
333    let mut output = BufWriter::new(file);
334    let header = if include_payloads {
335        "timestamp,duration,protocol,src_ip,dst_ip,src_port,dst_port,ttl_client,ttl_server,fwd_packets_count,bwd_packets_count,fwd_bytes,bwd_bytes,payloads,directions,flags,iat"
336    } else {
337        "timestamp,duration,protocol,src_ip,dst_ip,src_port,dst_port,ttl_client,ttl_server,fwd_packets_count,bwd_packets_count,fwd_bytes,bwd_bytes,directions,flags,iat"
338    };
339    writeln!(output, "{header}").expect("Error during CSV writing");
340    for f in stats {
341        let iat: Vec<u128> = f.iat.iter().map(Duration::as_millis).collect();
342        if include_payloads {
343            writeln!(
344                output,
345                "{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {:?}, {:?}, {:?}, {:?}",
346                f.timestamp.as_millis(),
347                f.duration.as_millis(),
348                f.protocol,
349                f.src_ip,
350                f.dst_ip,
351                f.src_port,
352                f.dst_port,
353                f.ttl_client,
354                f.ttl_server,
355                f.fwd_packets_count,
356                f.bwd_packets_count,
357                f.fwd_bytes,
358                f.bwd_bytes,
359                f.payloads,
360                f.directions,
361                f.flags,
362                iat
363            )
364            .expect("Error during CSV writing");
365        } else {
366            writeln!(
367                output,
368                "{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {:?}, {:?}, {:?}",
369                f.timestamp.as_millis(),
370                f.duration.as_millis(),
371                f.protocol,
372                f.src_ip,
373                f.dst_ip,
374                f.src_port,
375                f.dst_port,
376                f.ttl_client,
377                f.ttl_server,
378                f.fwd_packets_count,
379                f.bwd_packets_count,
380                f.fwd_bytes,
381                f.bwd_bytes,
382                f.directions,
383                f.flags,
384                iat
385            )
386            .expect("Error during CSV writing");
387        }
388    }
389}
390
391/// Extract flow statistics from a file
392pub fn process_file(file: &str) -> Vec<FlowStats> {
393    let file_in = BufReader::new(File::open(file).expect("Error opening file"));
394    let mut pcap_reader = pcap::PcapReader::new(file_in).unwrap();
395    let mut tcp_ongoing_flows: HashMap<FlowId, Vec<TCPPacketInfo>> = HashMap::new();
396    let mut udp_ongoing_flows: HashMap<FlowId, Vec<UDPPacketInfo>> = HashMap::new();
397    let mut icmp_ongoing_flows: HashMap<FlowId, Vec<ICMPPacketInfo>> = HashMap::new();
398    let mut finished_flows: Vec<FlowStats> = vec![];
399
400    while let Some(packet) = pcap_reader.next_packet() {
401        if let Ok(packet) = packet
402            && let Some(mut flow_id) = flow_id_from_packet(&packet.data)
403        {
404            flow_id.normalize();
405
406            let packet_info: PacketInfo = packet.into();
407
408            match packet_info {
409                PacketInfo::TCP(packet) => {
410                    let mut flow = tcp_ongoing_flows.entry(flow_id).or_default();
411                    if let Some(last_packet) = flow.last() {
412                        // check if the flow is already finished
413                        if last_packet.ts + DURATION_THRESHOLD < packet.ts {
414                            // TODO
415                            finished_flows.push(FlowStats::new_from_tcp(
416                                flow_id,
417                                tcp_ongoing_flows.remove(&flow_id).unwrap(),
418                            ));
419                            flow = tcp_ongoing_flows.entry(flow_id).or_default();
420                        }
421                    }
422                    flow.push(packet); // TODO réordonner si on voit "SYN"
423                }
424                PacketInfo::UDP(packet) => {
425                    let mut flow = udp_ongoing_flows.entry(flow_id).or_default();
426                    if let Some(last_packet) = flow.last() {
427                        // check if the flow is already finished
428                        if last_packet.ts + DURATION_THRESHOLD < packet.ts {
429                            finished_flows.push(FlowStats::new_from_udp(
430                                flow_id,
431                                udp_ongoing_flows.remove(&flow_id).unwrap(),
432                            ));
433                            flow = udp_ongoing_flows.entry(flow_id).or_default();
434                        }
435                    }
436                    flow.push(packet);
437                }
438                PacketInfo::ICMP(packet) => {
439                    let mut flow = icmp_ongoing_flows.entry(flow_id).or_default();
440                    if let Some(last_packet) = flow.last() {
441                        // check if the flow is already finished
442                        if last_packet.ts + DURATION_THRESHOLD < packet.ts {
443                            finished_flows.push(FlowStats::new_from_icmp(
444                                flow_id,
445                                icmp_ongoing_flows.remove(&flow_id).unwrap(),
446                            ));
447                            flow = icmp_ongoing_flows.entry(flow_id).or_default();
448                        }
449                    }
450                    flow.push(packet);
451                }
452            }
453        }
454    }
455    // unfinished flows
456    for (k, v) in tcp_ongoing_flows.drain() {
457        finished_flows.push(FlowStats::new_from_tcp(k, v));
458    }
459    for (k, v) in udp_ongoing_flows.drain() {
460        finished_flows.push(FlowStats::new_from_udp(k, v));
461    }
462    for (k, v) in icmp_ongoing_flows.drain() {
463        finished_flows.push(FlowStats::new_from_icmp(k, v));
464    }
465
466    finished_flows
467}
468
469/// Split a pcap file between original and Fos-R packets
470pub fn split_untaint(input: &str) {
471    let path = Path::new(input);
472    let mut s_original = String::from(path.file_stem().unwrap().to_str().unwrap());
473    let mut s_fosr = s_original.clone();
474    s_original.push_str("-original.pcap");
475    s_fosr.push_str("-fosr.pcap");
476
477    let file_out_original = OpenOptions::new()
478        .write(true)
479        .create(true)
480        .truncate(true)
481        .open(path.with_file_name(s_original))
482        .expect("Error opening or creating file");
483    let file_out_fosr = OpenOptions::new()
484        .write(true)
485        .create(true)
486        .truncate(true)
487        .open(path.with_file_name(s_fosr))
488        .expect("Error opening or creating file");
489
490    let mut pcap_writer_original =
491        pcap::PcapWriter::new(BufWriter::new(file_out_original)).expect("Error writing file");
492    let mut pcap_writer_fosr =
493        pcap::PcapWriter::new(BufWriter::new(file_out_fosr)).expect("Error writing file");
494
495    let mut count = 0;
496    {
497        // count the number of packet so we can put a progress bar
498        let file_in = BufReader::new(File::open(input).expect("Error opening file"));
499        let mut pcap_reader = pcap::PcapReader::new(file_in).unwrap();
500        while pcap_reader.next_packet().is_some() {
501            count += 1;
502        }
503    }
504    let file_in = BufReader::new(File::open(input).expect("Error opening file"));
505    let mut pcap_reader = pcap::PcapReader::new(file_in).unwrap();
506
507    // setup the progress bar
508    let pb = ProgressBar::new(count);
509    pb.set_style(
510        ProgressStyle::with_template("{spinner:.green} Untainting [{wide_bar}] ({eta})").unwrap(),
511    );
512
513    while let Some(packet) = pcap_reader.next_packet() {
514        let mut packet = packet.expect("Error during packet parsing");
515        // let packet = packet.into_owned();
516        let data = packet.data.to_mut();
517        // let mut eth_packet = ethernet::MutableEthernetPacket::new(&mut data).unwrap();
518        let ip_start = ethernet::MutableEthernetPacket::minimum_packet_size();
519        let mut ipv4_packet = ipv4::MutableIpv4Packet::new(&mut data[ip_start..]).unwrap();
520        let ip_flags = ipv4_packet.get_flags();
521        if ipv4_packet.get_flags() & 0b100 > 0 {
522            ipv4_packet.set_flags(ip_flags & 0b011);
523            ipv4_packet.set_checksum(ipv4::checksum(&ipv4_packet.to_immutable()));
524            pcap_writer_fosr.write_packet(&packet).unwrap();
525        } else {
526            pcap_writer_original.write_packet(&packet).unwrap();
527        }
528        pb.inc(1);
529    }
530    pb.finish();
531}
532
533/// Remove the Fos-R taint (i.e., the third IP flag bit) from a pcap file
534pub fn untaint_file(input: &str, output: &str) {
535    let file_out = OpenOptions::new()
536        .write(true)
537        .create(true)
538        .truncate(true)
539        .open(output)
540        .expect("Error opening or creating file");
541    let mut pcap_writer =
542        pcap::PcapWriter::new(BufWriter::new(file_out)).expect("Error writing file");
543
544    let mut count = 0;
545    {
546        // count the number of packet so we can put a progress bar
547        let file_in = BufReader::new(File::open(input).expect("Error opening file"));
548        let mut pcap_reader = pcap::PcapReader::new(file_in).unwrap();
549        while pcap_reader.next_packet().is_some() {
550            count += 1;
551        }
552    }
553    let file_in = BufReader::new(File::open(input).expect("Error opening file"));
554    let mut pcap_reader = pcap::PcapReader::new(file_in).unwrap();
555
556    // setup the progress bar
557    let pb = ProgressBar::new(count);
558    pb.set_style(
559        ProgressStyle::with_template("{spinner:.green} Untainting [{wide_bar}] ({eta})").unwrap(),
560    );
561
562    while let Some(packet) = pcap_reader.next_packet() {
563        let mut packet = packet.expect("Error during packet parsing");
564        // let packet = packet.into_owned();
565        let data = packet.data.to_mut();
566        // let mut eth_packet = ethernet::MutableEthernetPacket::new(&mut data).unwrap();
567        let ip_start = ethernet::MutableEthernetPacket::minimum_packet_size();
568        let mut ipv4_packet = ipv4::MutableIpv4Packet::new(&mut data[ip_start..]).unwrap();
569        let ip_flags = ipv4_packet.get_flags();
570        ipv4_packet.set_flags(ip_flags & 0b011);
571        ipv4_packet.set_checksum(ipv4::checksum(&ipv4_packet.to_immutable()));
572        pb.inc(1);
573        pcap_writer.write_packet(&packet).unwrap();
574    }
575    pb.finish();
576}
577
578/// Verify whether a IP address is global (i.e., public)
579pub fn is_global(addr: &Ipv4Addr) -> bool {
580    addr.octets()[0] != 0
581        && !addr.is_multicast()
582        && !addr.is_broadcast()
583        && !addr.is_documentation()
584        && !addr.is_link_local()
585        && !addr.is_loopback()
586        && !addr.is_private()
587}
588
589/// Sample a random global (i.e., public) IP address
590pub fn sample_random_global_ip(rng: &mut impl Rng) -> Ipv4Addr {
591    let mut addr = Ipv4Addr::from_bits(rng.next_u32());
592    // rejection sampling
593    while !is_global(&addr) {
594        // while !addr.is_global() { // TODO: use when not experimental anymore
595        addr = Ipv4Addr::from_bits(rng.next_u32());
596    }
597    addr
598}