6

I would like to read a pcap file generated by tcpdump that contains large UDP packets that have undergone IPV4 fragmentation. The original packets are of a size of around 22000 bytes.

In C++, I would use libtins with its IPV4Reassembler. Is there a way that I can do something similar in Rust?

Currently in Rust here is what I have written so far: a highly incomplete first-pass attempt (using crate pnet):

use pnet::packet::{
    ethernet::{EtherTypes, EthernetPacket},
    ip::IpNextHeaderProtocols,
    ipv4::Ipv4Packet,
    udp::UdpPacket,
    Packet,
};

struct Ipv4Reassembler {
    cap: pcap::Capture<pcap::Offline>,
}

impl Iterator for Ipv4Reassembler {
    type Item = Vec<u8>;

    fn next(&mut self) -> Option<Self::Item> {
        let mut payload = Vec::<u8>::new();
        while let Some(packet) = self.cap.next().ok() {
            // todo: handle packets other than Ethernet packets
            let ethernet = EthernetPacket::new(packet.data).unwrap();
            match ethernet.get_ethertype() {
                EtherTypes::Ipv4 => {
                    let ipv4_packet = Ipv4Packet::new(ethernet.payload()).unwrap();
                    // dbg!(&ipv4_packet);
                    // todo: discard incomplete packets
                    // todo: construct header for reassembled packet
                    // todo: check id, etc
                    let off: usize = 8 * ipv4_packet.get_fragment_offset() as usize;
                    let end = off + ipv4_packet.payload().len();
                    if payload.len() < end {
                        payload.resize(end, 0);
                    }
                    payload[off..end].clone_from_slice(ipv4_packet.payload());
                    if ipv4_packet.get_flags() & 1 == 0 {
                        return Some(payload);
                    }
                }
                _ => {}
            }
        }
        None
    }
}

fn main() {
    let pcap_path = "os-992114000702.pcap";

    let reass = Ipv4Reassembler {
        cap: pcap::Capture::from_file(&pcap_path).unwrap(),
    };

    for payload in reass {
        let udp_packet = UdpPacket::new(&payload).unwrap();
        dbg!(&udp_packet);
        dbg!(&udp_packet.payload().len());
    }
}

In C++ here is the code I would use (using libtins):

#include <tins/ip_reassembler.h>
#include <tins/packet.h>
#include <tins/rawpdu.h>
#include <tins/sniffer.h>
#include <tins/tins.h>
#include <tins/udp.h>

#include <iostream>
#include <string>

void read_packets(const std::string &pcap_filename) {
    Tins::IPv4Reassembler reassembler;
    Tins::FileSniffer sniffer(pcap_filename);

    while (Tins::Packet packet = sniffer.next_packet()) {
        auto &pdu = *packet.pdu();
        const Tins::Timestamp &timestamp = packet.timestamp();
        if (reassembler.process(pdu) != Tins::IPv4Reassembler::FRAGMENTED) {
            const Tins::UDP *udp = pdu.find_pdu<Tins::UDP>();
            if (!udp) {
                continue;
            }
            const Tins::RawPDU *raw = pdu.find_pdu<Tins::RawPDU>();
            if (!raw) {
                continue;
            }
            const Tins::RawPDU::payload_type &payload = raw->payload();
            std::cout << "Packet: " << payload.size() << std::endl;
            // do something with the reassembled packet here
        }
    }
}

int main() {
    const std::string pcap_path = "os-992114000702.pcap";
    read_packets(pcap_path);
}
g++ -O3 -o pcap pcap.cpp -ltins

It seems that one solution is to implement RFC815 but I am not sure how to do that in Rust. I have found:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Daniel
  • 510
  • 3
  • 15

0 Answers0