rustioctl

How to use ioctl with SIOCGIWESSID


I want to read the SSID that I'm connect to.
I'm using ioctl to do so. I'm trying to read it using nix::ioctl_read. I'm new to this so I'm unsure if what I'm doing is correct. Here's what I've tried.

const SIOCGIWESSID: u8 = 0x8B;
ioctl_read!(read_essid, SIOCGIWESSID, 0x8B1B, Iwreq);

#[derive(Debug)]
pub struct Essid {
    length: u8,
    pointer: [u8; 33],
}

#[derive(Debug)]
pub struct WreqData {
    essid: Essid,
}

#[derive(Debug)]
#[repr(C)]
pub struct Iwreq {
    frn_name: [u8; 33],
    u: WreqData,
}

fn get_essid() -> String {
    const WESSID_LEN: usize = 33;
    let fd = socket(
        AddressFamily::Inet,
        SockType::Datagram,
        SockFlag::empty(),
        None,
    )
    .unwrap();
    let pointer: [u8; WESSID_LEN] = [0; WESSID_LEN];
    let mut ifr_tmp = [0u8; WESSID_LEN];
    let interface_name = "wlp2s0\0";
    ifr_tmp[..interface_name.len()].copy_from_slice(interface_name.as_bytes());
    let mut data = Iwreq {
        frn_name: ifr_tmp,
        u: WreqData {
            essid: Essid {
                length: WESSID_LEN as u8,
                pointer,
            },
        },
    };
    println!("{}", unsafe { read_essid(fd, &mut data).unwrap() });
    println!("{:?}", data);
    unimplemented!()
}

I get the error "ENOTTY" from the ioctl call. I think I'm passing incorrect arguments to ioctl_read.
What can I pass to ioctl_read! to make it work? Or is there another solution I should be looking into?


Solution

  • Looks like you have two main problems:

    Here is a modified version that appears to work on my machine:

    #[repr(C)]
    pub struct Essid {
        pointer: *const c_char,
        length: u16,
        flags: u16,
    }
    
    impl Essid {
        const fn new(pointer: *const c_char) -> Self {
            Self {
                pointer,
                length: 33,
                flags: 0,
            }
        }
    }
    
    #[repr(C)]
    pub struct WreqData {
        essid: Essid,
    }
    
    #[repr(C)]
    pub struct Iwreq {
        frn_name: [u8; 16],
        u: WreqData,
    }
    
    impl Iwreq {
        fn new(name: &str, buf: &[c_char; 33]) -> Self {
            let mut frn_name = [0; 16];
            frn_name[..name.len()].copy_from_slice(name.as_bytes());
    
            Self {
                frn_name,
                u: WreqData {
                    essid: Essid::new(buf.as_ptr()),
                },
            }
        }
    }
    
    nix::ioctl_read_bad!(read_essid, 0x8B1B, Iwreq);
    
    fn get_essid(name: &str) -> anyhow::Result<String> {
        let sock = socket::socket(
            AddressFamily::Inet,
            SockType::Datagram,
            SockFlag::empty(),
            None,
        )?;
        let buf = [0; 33];
        let mut data = Iwreq::new(name, &buf);
        unsafe { read_essid(sock.as_raw_fd(), &mut data) }?;
        let ptr = buf.as_ptr();
        Ok(unsafe { CStr::from_ptr(ptr) }.to_str()?.to_owned())
    }
    

    Alternatively, the same information could be obtained using netlink sockets. For example:

    async fn get_essid(name: &str) -> anyhow::Result<String> {
        let (connection, handle, _) = wl_nl80211::new_connection()?;
        tokio::spawn(connection);
    
        let mut ifaces = handle.interface().get().execute().await;
        while let Ok(Some(iface)) = ifaces.try_next().await {
            if !iface
                .payload
                .nlas
                .iter()
                .any(|nla| matches!(nla, Nl80211Attr::IfName(n) if n == name))
            {
                continue;
            }
    
            for nla in iface.payload.nlas {
                if let Nl80211Attr::Ssid(ssid) = nla {
                    return Ok(ssid);
                }
            }
        }
    
        bail!("SSID not found")
    }
    

    I've uploaded this example/test code to GitLab if you want to look at it in full: https://gitlab.com/harryausten/ssid-query. Hope this helps!