Rust OSError 22, Invalid argument when writing valid data to socket
Asked Answered
P

1

8

I'm struggling to understand why I'm getting this error from a part of my program which sends ICMP echo requests on the network. The starnge thing about this is that I can get it to work by letting the socket handle the IP header, but when I set the IP_HDRINCL option and give it a valid IP header, it returns EINVAL error:

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" })

To help visualise what's going on, I've put dumps of the data being sent in there when it sends it. the first dump is the data in hex format and the second is just the same but in decimal format.

Here is the code which opens the socket and creates/sends/receives packets:

( Apologies for the probably overwhelming amount of code, just though I should make sure I provide enough info:) )

    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);

This is how I'm opening a socket, and sending data on it, notice that I call setsockopt and set IP_HDRINCL to 1 when opening it:

    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)
        }
    }

This is the function to convert the type of the 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 _)
    }
}

So back to my point, as you can see from the first segment of code, the IP header in the packet data that I dump is a valid IP header, followed by a valid ICMP header, as far as I'm aware, I have been changing things around to ensure it's valid so correct me if its not. But anyway, I know that all the other parameters being passed into sendto are valid because when I set the IP_HDRINCL socket option to 0 and only send ICMP data then it works...

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]

I've even tried things such as sending an ethernet II header in the packet when IP_HDRINCL is 1, but that still just gives the same OSError. Does anyone know why sending the IP header myself breaks it??

(btw I'm running this on Mac OS Mojave 10.14.6)

Pepys answered 20/12, 2019 at 11:42 Comment(1)
Any fix on this? Also, I'm curious, what operating system was the peer using?Oval
S
0

For anyone coming from search engines. You can have this error when you don't have the rights to write to a file.

This code doesn't work

use std::fs::OpenOptions;
use std::io::Error;
use std::io::Write;

fn main() -> Result<(), Error> {

    // open the file
    let mut file = OpenOptions::new()
        .read(true)
//        .write(true)
        .create(true)
        .open("./output")?;
   
    // write some byte
    let bytes: [u8; 4] = [1, 2, 3, 4];
    file.write_all(&bytes)?;

    Ok(())
}

Compile and run:

$ rustc test.rs && ./test
Error: Os { code: 22, kind: InvalidInput, message: "Invalid argument" }

With write():

use std::fs::OpenOptions;
use std::io::Error;
use std::io::Write;

fn main() -> Result<(), Error> {

    // open the file
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("./output")?;
   
    // write some byte
    let bytes: [u8; 4] = [1, 2, 3, 4];
    file.write_all(&bytes)?;

    Ok(())
}

Compile and run:

$ rustc test.rs && ./test

$ xxd ./output
00000000: 0102 0304                                ....
Sailor answered 11/6, 2024 at 13:49 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.