libpnet / libpnet

Cross-platform, low level networking using the Rust programming language.
Apache License 2.0
2.27k stars 306 forks source link

icmp_packet_iter crashes on Windows #453

Open ilyagrishkov opened 4 years ago

ilyagrishkov commented 4 years ago

I've been playing around with libpnet on different platforms, and I couldn't make this code work on Windows.

extern crate pnet;

use pnet::transport::TransportProtocol::Ipv4;
use pnet::transport::TransportChannelType::Layer4;
use pnet::packet::ip::IpNextHeaderProtocols;
use pnet::transport::{icmp_packet_iter, transport_channel};

fn main() {
    let protocol_icmp = Layer4(Ipv4(IpNextHeaderProtocols::Icmp));
    let (_, mut receiver) = transport_channel(1024, protocol_icmp).unwrap();

    let mut iter = icmp_packet_iter(&mut receiver);

    match iter.next() {
        Ok((_, ipv4)) => println!("Addr: {}", ipv4.to_string()),
        Err(e) => panic!("ICMP iter error: {}", e)
    }
}

I've tested it on MacOS (so I assume it should also work on Linux) and it waits for the next icmp packet that arrives and prints out the address (as expected), but on Windows (MSVC toolchain, WinPcap 4.1.3) it crashes with the following error:

thread 'main' panicked at 'ICMP iter error: An invalid argument was supplied. (os error 10022)', src\main.rs:16:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I observed very similar behavior with another crate: socket2. It also complains about "And invalid argument..." when rx.recv_from(......) is called.

Is it something about Windows that I'm overlooking and what might cause this error?

ma-chengyuan commented 4 years ago

Same problem here. It seems that the problem is not limited to icmp_packet_iter but other packet iterators as well. The error arises in the call to recvfrom. MSDN says that error 10022 is due the socket not being bound by bind(). I don't see where pnet calls bind(), so perhaps this is the cause?

ma-chengyuan commented 4 years ago

I find a quick and dirty fix that works on my computer (reference):

#[cfg(windows)]
pub fn fix_windows_error(tx: &TransportReceiver) {
    use winapi::shared::ws2def::{ADDRESS_FAMILY, SOCKADDR_IN, SOCKADDR, AF_INET, INADDR_ANY};
    use winapi::shared::mstcpip::{SIO_RCVALL, RCVALL_ON};
    use winapi::shared::minwindef::{DWORD, LPDWORD, LPVOID};
    use winapi::um::winsock2;
    use winapi::um::winsock2::{SOCKET, SOCKET_ERROR};
    use winapi::ctypes::{c_int, c_char};
    use std::mem;
    unsafe {
        let mut addr: SOCKADDR_IN = mem::zeroed();
        addr.sin_family = AF_INET as ADDRESS_FAMILY;
        addr.sin_port = winsock2::ntohs(0);
        // *addr.sin_addr.S_un.S_addr_mut() = INADDR_ANY;
        // ICMP raw socket only works when bound to a specific ip instead of 0.0.0.0.
        *addr.sin_addr.S_un.S_addr_mut() = winsock2::inet_addr("<LOCAL IP>".as_ptr() as *const c_char);
        let socket = tx.socket.fd as SOCKET;
        let error = winsock2::bind(socket,
                                   &addr as *const SOCKADDR_IN as *const SOCKADDR,
                                   mem::size_of::<SOCKADDR_IN>() as c_int);
        if error == SOCKET_ERROR {
            panic!("{}", std::io::Error::last_os_error());
        }
        // This step is only necessary for ICMP raw socket
        let in_opt = RCVALL_ON.to_le_bytes();
        let out_opt = 0u32.to_le_bytes();
        let returned = [0 as DWORD; 0];
        winsock2::WSAIoctl(socket, SIO_RCVALL,
                           &in_opt as *const u8 as LPVOID,
                           in_opt.len() as DWORD,
                           &out_opt as *const u8 as LPVOID,
                           out_opt.len() as DWORD,
                           &returned as *const DWORD as LPDWORD,
                           std::ptr::null_mut(), None);
        if error == SOCKET_ERROR {
            panic!("{}", std::io::Error::last_os_error());
        }
    }
}

#[cfg(not(windows))]
pub fn fix_windows_error(tx: &TransportSender) {}

Just add it right after creating a channel:

let (mut tx, mut rx) = transport_channel(4096, protocol).unwrap();
fix_windows_error(&rx);

The side effect of this fix is that the receiver now also receives outgoing packets, so some filtering must be done.

This fix should work (not tested) for other protocols and packet iterators as well. Just bind to 0.0.0.0 and remove the WSAIoctl bits.

JuxhinDB commented 3 years ago

Any additional details/references to this would be great. I'll try to find some time to troubleshoot this but at face value, I get the impression that it's part of a larger problem.

spmadden commented 2 years ago

Sending a packet before calling the iterator seems to bind the socket, preventing this error.