Rust OSError 22,将有效数据写入套接字时参数无效

Jor*_*man 8 sockets ip icmp rust

我很难理解为什么我的程序中在网络上发送 ICMP 回显请求的部分会出现此错误。最重要的是,我可以通过让套接字处理 IP 标头来使其工作,但是当我设置 IP_HDRINCL 选项并为其提供有效的 IP 标头时,它会返回 EINVAL 错误:

initialize
using interface en0 with ip 192.168.1.126 and mac a4:83:e7:43:40:81.
Input start ip/scan range: 192.168.1.1
[45, 0, 0, 1c, 20, 1, 40, 0, 40, 1, 97, 10, c0, a8, 1, 7e, c0, a8, 1, 1, 8, 0, 22, 2a, 97, 3e, 3e, 97]
[69, 0, 0, 28, 32, 1, 64, 0, 64, 1, 151, 16, 192, 168, 1, 126, 192, 168, 1, 1, 8, 0, 34, 42, 151, 62, 62, 151]
Err(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })
Run Code Online (Sandbox Code Playgroud)

为了帮助直观地了解正在发生的情况,我在发送数据时将发送的数据转储放在那里。第一个转储是十六进制格式的数据,第二个转储是相同的,但是十进制格式。

这是打开套接字并创建/发送/接收数据包的代码:

(对可能大量的代码表示歉意,尽管我应该确保提供足够的信息:))

    let response = net_tools::interface::Interface::detect();
    if response.is_err() {
        panic! {response};
    }
    let active_interface = response.unwrap();
    println!(
        "using interface {} with ip {} and mac {}.",
        active_interface.get_name(),
        active_interface.get_ip_as_struct(),
        active_interface.get_mac()
    );
    let scan_targets = net_tools::TaregtAddresses::retrieve_from_user();
    let sock = net_tools::socket_tools::Socket::new_v4().expect("Failed to open socket");
    let mut i = 0;
    for ipv4addr in scan_targets {
        if i == 1 {
            break;
        }
        let mut d = packet_crafter::Packet::new(
            active_interface.clone(),
            vec![Protocols::IP, Protocols::ICMP],
            // vec![Protocols::ICMP],
        );
        d.ip_header.set_next_protocol(Protocols::ICMP);
        d.ip_header.set_dst_ip(Some(ipv4addr.octets()));
        // d.ip_header.set_tos(0b11000100);
        d.finalize_headers(); // calculates checksums etc
        let data = d.build();
        println!("{:x?}", data);
        println!("{:?}", data);
        let response = sock.sendto(data.as_slice(), SocketAddrV4::new(ipv4addr, 21));
        println!("{:?}", response);
        i += 1;
    }
    println!("All data sent, receiving response:");
    let mut a = [0u8; 56];
    let received_data = sock.recv(&mut a);
    println!("{:?}", received_data);
    let v = a.to_vec();
    println!("{:?}", v);
Run Code Online (Sandbox Code Playgroud)

这就是我打开套接字并在其上发送数据的方式,请注意,我在打开它时调用了setsockopt并将IP_HDRINCL设置为1:

    pub fn new_v4() -> crate::io::Result<Self> {
        unsafe {
            let fam = libc::AF_INET; // Set domain family to ipv4
            if cfg!(target_os = "linux") {
                match cvt(libc::socket(fam, libc::SOCK_RAW | SOCK_CLOEXEC, 1)) {
                    Ok(fd) => return Ok(Self(Fd::new(fd))),
                    Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {}
                    Err(e) => return Err(e),
                }
            }

            let _fd = cvt(libc::socket(fam, libc::SOCK_RAW, 1))?; // 1 = setting next protocol as ICMP
            let _fd = Fd::new(_fd);
            _fd.set_cloexec()?;
            let socket = Self(_fd);
            if cfg!(target_vendor = "apple") {
                let payload = &1u32 as *const u32 as *const libc::c_void;
                cvt(libc::setsockopt(
                    *socket.as_inner(),
                    libc::SOL_SOCKET,
                    SO_NOSIGPIPE,
                    payload,
                    size_of::<libc::c_int>() as libc::socklen_t,
                ))?;
            }
            let payload = &1u32 as *const u32 as *const libc::c_void;
            cvt(libc::setsockopt(
                *socket.as_inner(),
                libc::IPPROTO_IP,
                libc::IP_HDRINCL,
                payload,
                size_of::<libc::c_int>() as libc::socklen_t,
            ))?;
            Ok(socket)
        }
    }

    pub fn sendto(&self, buf: &[u8], addr: SocketAddrV4) -> crate::io::Result<usize> {
        unsafe {
            let (sa, l) = sock_addr_into_raw(addr);
            let n = cvt({
                libc::sendto(
                    *self.as_inner(),
                    buf.as_ptr() as *const libc::c_void,
                    cmp::min(buf.len(), super::max_len()),
                    MSG_NOSIGNAL,
                    &sa as *const _,
                    l,
                )
            })?;
            Ok(n as usize)
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是转换socketAddr类型的函数:

pub fn sock_addr_into_raw(s: SocketAddrV4) -> (libc::sockaddr, libc::socklen_t) {
    unsafe {
        let mut storage = mem::MaybeUninit::<libc::sockaddr>::uninit();
        let len = mem::size_of::<SocketAddrV4>();
        copy_nonoverlapping(
            &s as *const _ as *const libc::sockaddr as *const _ as *const u8,
            &mut storage as *mut _ as *mut u8,
            len,
        );
        (storage.assume_init(), len as _)
    }
}
Run Code Online (Sandbox Code Playgroud)

回到我的观点,正如您从第一段代码中看到的那样,我转储的数据包数据中的 IP 标头是一个有效的 IP 标头,后面跟着一个有效的 ICMP 标头,据我所知,我有一直在改变周围的事情以确保它是有效的,所以如果不是的话请纠正我。但无论如何,我知道传入的所有其他参数sendto都是有效的,因为当我将 IP_HDRINCL 套接字选项设置为 0 并仅发送 ICMP 数据时,它就可以工作了...

initialize
using interface en0 with ip 192.168.1.126 and mac a4:83:e7:43:40:81.
Input start ip/scan range: 192.168.1.1
[8, 0, 8a, 92, 3f, 2e, 2e, 3f]
[8, 0, 138, 146, 63, 46, 46, 63]
Ok(8)
All data sent, receiving response:
Ok(28)
[69, 0, 8, 0, 224, 108, 0, 0, 254, 1, 88, 164, 192, 168, 1, 1, 192, 168, 1, 126, 0, 0, 146, 146, 63, 46, 46, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Run Code Online (Sandbox Code Playgroud)

我什至尝试过诸如当 IP_HDRINCL 为 1 时在数据包中发送以太网 II 标头之类的操作,但这仍然给出相同的 OSError。有谁知道为什么我自己发送IP头会破坏它?

(顺便说一句,我在 Mac OS Mojave 10.14.6 上运行它)