rust-pcap / pcap

Rust language pcap library
Apache License 2.0
610 stars 138 forks source link

Handle fragmented packets #175

Closed dllu closed 3 years ago

dllu commented 3 years ago

I am trying to read a pcap file that contains IP fragmented packets.

Currently I can print out the correct size of the packets using C++ with libtins, like so

#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;
        }
    }
}

int main() {
    const std::string pcap_path = "os-992114000702.pcap";
    read_packets(pcap_path);
}
g++ -O3 -o pcap pcap.cpp -ltins #make sure you have libtins installed

Is there any way to do something similar in rust using pcap?

I am doing this currently:

fn main() {
    let pcap_path = "os-992114000702.pcap";
    let mut cap = pcap::Capture::from_file(&pcap_path).unwrap();
    loop {
        let packet = cap.next().unwrap();
        println!("{:?}", packet.header);
    }
}

But it prints out the wrong packet length.

c++ code above prints out:

Packet: 24896
Packet: 24896
Packet: 24896
Packet: 48
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 48
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 24896
Packet: 48
Packet: 24896

whereas the rust code prints out:

PacketHeader { ts: 1622849937.065466, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.065467, caplen: 1258, len: 1258 }
PacketHeader { ts: 1622849937.066903, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066904, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066904, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066905, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066905, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066906, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066906, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066907, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066907, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066907, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066908, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066908, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066909, caplen: 1514, len: 1514 }
PacketHeader { ts: 1622849937.066909, caplen: 1514, len: 1514 }
stappersg commented 3 years ago

FWIW in the 1514 I do see MTU, Maximum Transfer Unit, the max size of ethernet packets.

Wojtek242 commented 3 years ago

I don't think pcap is at the right level to do what you want to do . As @stappersg pointed out, you seem to be printing the length of individual Ethernet frames, because that's the granularity that libpcap works at. I don't know if libpcap has in-built functionality to assemble them into complete IP packets. If you can figure out the right libpcap calls (as pcap is just a Rust wrapper around it) then we can see if that can be done in pcap too.

dllu commented 3 years ago

Alright it seems that libpcap doesn't have packet reassembly (see: https://stackoverflow.com/questions/2172782/libpcap-ip-packet-reassembly).

I guess I have to write my own packet reassembler. Here's my janky first attempt using 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() {
            let ethernet = EthernetPacket::new(packet.data).unwrap();
            match ethernet.get_ethertype() {
                EtherTypes::Ipv4 => {
                    let ipv4_packet = Ipv4Packet::new(ethernet.payload()).unwrap();
                    // dbg!(&ipv4_packet);
                    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);
    }
}
stappersg commented 3 years ago

Link to https://github.com/libpnet/libpnet/issues/507