|
1 | 1 | use once_cell::sync::OnceCell;
|
2 | 2 |
|
| 3 | +pub fn get_libc_ifaddrs() -> Option<( |
| 4 | + unsafe extern "C" fn(*mut *mut libc::ifaddrs) -> libc::c_int, |
| 5 | + unsafe extern "C" fn(*mut libc::ifaddrs), |
| 6 | +)> { |
| 7 | + match (get_getifaddrs(), get_freeifaddrs()) { |
| 8 | + (Some(a), Some(b)) => Some((a, b)), |
| 9 | + _ => None, |
| 10 | + } |
| 11 | +} |
| 12 | + |
3 | 13 | fn load_symbol<T>(sym: &'static str) -> Option<T> {
|
4 | 14 | const LIB_NAME: &str = "libc.so";
|
5 | 15 |
|
@@ -33,32 +43,191 @@ fn get_freeifaddrs() -> Option<unsafe extern "C" fn(*mut libc::ifaddrs)> {
|
33 | 43 | *INSTANCE.get_or_init(|| load_symbol("freeifaddrs"))
|
34 | 44 | }
|
35 | 45 |
|
36 |
| -pub unsafe fn getifaddrs(ifap: *mut *mut libc::ifaddrs) -> libc::c_int { |
37 |
| - // Android is complicated |
| 46 | +mod netlink { |
| 47 | + //! Netlink based getifaddrs. |
| 48 | + //! |
| 49 | + //! Based on the logic found in https://git.musl-libc.org/cgit/musl/tree/src/network/getifaddrs.c |
38 | 50 |
|
39 |
| - // API 24+ contains the getifaddrs and freeifaddrs functions but the NDK doesn't |
40 |
| - // expose those functions in ifaddrs.h when the minimum supported SDK is lower than 24 |
41 |
| - // and therefore we need to load them manually. |
42 |
| - if let Some(dyn_getifaddrs) = get_getifaddrs() { |
43 |
| - return dyn_getifaddrs(ifap); |
44 |
| - } |
| 51 | + use netlink_packet_core::{ |
| 52 | + NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, |
| 53 | + }; |
| 54 | + use netlink_packet_route::{ |
| 55 | + rtnl::address::nlas::Nla as AddressNla, rtnl::link::nlas::Nla as LinkNla, AddressMessage, |
| 56 | + LinkMessage, RtnlMessage, |
| 57 | + }; |
| 58 | + use netlink_sys::{protocols::NETLINK_ROUTE, Socket}; |
| 59 | + use std::net::{Ipv4Addr, Ipv6Addr}; |
45 | 60 |
|
46 |
| - // If API < 24 (or we can't load libc for some other reason), we fallback to using netlink |
47 |
| - netlink_getifaddrs(ifap) |
48 |
| -} |
| 61 | + use crate::interface::{Interface, InterfaceType, Ipv4Net, Ipv6Net, MacAddr}; |
| 62 | + |
| 63 | + pub fn unix_interfaces() -> Vec<Interface> { |
| 64 | + let socket = Socket::new(NETLINK_ROUTE).unwrap(); |
49 | 65 |
|
50 |
| -pub unsafe fn freeifaddrs(ifa: *mut libc::ifaddrs) { |
51 |
| - if let Some(dyn_freeifaddrs) = get_freeifaddrs() { |
52 |
| - return dyn_freeifaddrs(ifa); |
| 66 | + let mut ifaces = Vec::new(); |
| 67 | + enumerate_netlink( |
| 68 | + &socket, |
| 69 | + RtnlMessage::GetLink(LinkMessage::default()), |
| 70 | + &mut ifaces, |
| 71 | + ); |
| 72 | + enumerate_netlink( |
| 73 | + &socket, |
| 74 | + RtnlMessage::GetAddress(AddressMessage::default()), |
| 75 | + &mut ifaces, |
| 76 | + ); |
| 77 | + |
| 78 | + ifaces |
53 | 79 | }
|
54 | 80 |
|
55 |
| - netlink_freeifaddrs(ifa) |
56 |
| -} |
| 81 | + fn enumerate_netlink(socket: &Socket, msg: RtnlMessage, ifaces: &mut Vec<Interface>) { |
| 82 | + let mut packet = NetlinkMessage::new(NetlinkHeader::default(), NetlinkPayload::from(msg)); |
| 83 | + packet.header.flags = NLM_F_DUMP | NLM_F_REQUEST; |
| 84 | + packet.header.sequence_number = 1; |
| 85 | + packet.finalize(); |
57 | 86 |
|
58 |
| -unsafe fn netlink_getifaddrs(ifap: *mut *mut libc::ifaddrs) -> libc::c_int { |
59 |
| - todo!() |
60 |
| -} |
| 87 | + let mut buf = vec![0; packet.header.length as usize]; |
| 88 | + |
| 89 | + // TODO: gracefully handle error |
| 90 | + assert!(buf.len() == packet.buffer_len()); |
| 91 | + packet.serialize(&mut buf[..]); |
| 92 | + |
| 93 | + socket.send(&buf[..], 0).unwrap(); |
61 | 94 |
|
62 |
| -unsafe fn netlink_freeifaddrs(ifa: *mut libc::ifaddrs) { |
63 |
| - todo!() |
| 95 | + let mut receive_buffer = vec![0; 4096]; |
| 96 | + let mut offset = 0; |
| 97 | + |
| 98 | + loop { |
| 99 | + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); |
| 100 | + |
| 101 | + loop { |
| 102 | + let bytes = &receive_buffer[offset..]; |
| 103 | + let rx_packet: NetlinkMessage<RtnlMessage> = |
| 104 | + NetlinkMessage::deserialize(bytes).unwrap(); |
| 105 | + |
| 106 | + match rx_packet.payload { |
| 107 | + NetlinkPayload::Done => { |
| 108 | + return; |
| 109 | + } |
| 110 | + NetlinkPayload::Error(err) => { |
| 111 | + eprintln!("Error: {:?}", err); |
| 112 | + return; |
| 113 | + } |
| 114 | + NetlinkPayload::InnerMessage(msg) => { |
| 115 | + match msg { |
| 116 | + RtnlMessage::NewLink(link_msg) => { |
| 117 | + let mut interface: Interface = Interface { |
| 118 | + index: link_msg.header.index, |
| 119 | + name: String::new(), |
| 120 | + friendly_name: None, |
| 121 | + description: None, |
| 122 | + if_type: InterfaceType::try_from( |
| 123 | + link_msg.header.link_layer_type as u32, |
| 124 | + ) |
| 125 | + .unwrap_or(InterfaceType::Unknown), |
| 126 | + mac_addr: None, |
| 127 | + ipv4: Vec::new(), |
| 128 | + ipv6: Vec::new(), |
| 129 | + flags: link_msg.header.flags, |
| 130 | + transmit_speed: None, |
| 131 | + receive_speed: None, |
| 132 | + gateway: None, |
| 133 | + }; |
| 134 | + |
| 135 | + for nla in link_msg.nlas { |
| 136 | + match nla { |
| 137 | + LinkNla::IfName(name) => { |
| 138 | + interface.name = name; |
| 139 | + } |
| 140 | + LinkNla::Address(addr) => { |
| 141 | + match addr.len() { |
| 142 | + 6 => { |
| 143 | + interface.mac_addr = Some(MacAddr::new( |
| 144 | + addr.try_into().unwrap(), |
| 145 | + )); |
| 146 | + } |
| 147 | + 4 => { |
| 148 | + let ip = Ipv4Addr::from( |
| 149 | + <[u8; 4]>::try_from(addr).unwrap(), |
| 150 | + ); |
| 151 | + interface.ipv4.push(Ipv4Net::new_with_netmask( |
| 152 | + ip, |
| 153 | + Ipv4Addr::UNSPECIFIED, |
| 154 | + )); |
| 155 | + } |
| 156 | + _ => { |
| 157 | + // unclear what these would be |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | + _ => {} |
| 162 | + } |
| 163 | + } |
| 164 | + ifaces.push(interface); |
| 165 | + } |
| 166 | + RtnlMessage::NewAddress(addr_msg) => { |
| 167 | + println!("NewAddress: {:?}", addr_msg); |
| 168 | + if let Some(interface) = |
| 169 | + ifaces.iter_mut().find(|i| i.index == addr_msg.header.index) |
| 170 | + { |
| 171 | + for nla in addr_msg.nlas { |
| 172 | + match nla { |
| 173 | + AddressNla::Address(addr) => match addr.len() { |
| 174 | + 4 => { |
| 175 | + let ip = Ipv4Addr::from( |
| 176 | + <[u8; 4]>::try_from(addr).unwrap(), |
| 177 | + ); |
| 178 | + interface.ipv4.push(Ipv4Net::new( |
| 179 | + ip, |
| 180 | + addr_msg.header.prefix_len, |
| 181 | + )); |
| 182 | + } |
| 183 | + 16 => { |
| 184 | + let ip = Ipv6Addr::from( |
| 185 | + <[u8; 16]>::try_from(addr).unwrap(), |
| 186 | + ); |
| 187 | + interface.ipv6.push(Ipv6Net::new( |
| 188 | + ip, |
| 189 | + addr_msg.header.prefix_len, |
| 190 | + )); |
| 191 | + } |
| 192 | + _ => { |
| 193 | + // what else? |
| 194 | + } |
| 195 | + }, |
| 196 | + _ => {} |
| 197 | + } |
| 198 | + } |
| 199 | + } else { |
| 200 | + eprintln!( |
| 201 | + "found unknown interface with index: {}", |
| 202 | + addr_msg.header.index |
| 203 | + ); |
| 204 | + } |
| 205 | + } |
| 206 | + _ => { |
| 207 | + // not expecting other messages |
| 208 | + } |
| 209 | + } |
| 210 | + } |
| 211 | + _ => {} |
| 212 | + } |
| 213 | + offset += rx_packet.header.length as usize; |
| 214 | + if offset == size || rx_packet.header.length == 0 { |
| 215 | + offset = 0; |
| 216 | + break; |
| 217 | + } |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + #[cfg(test)] |
| 223 | + mod tests { |
| 224 | + use super::*; |
| 225 | + |
| 226 | + #[test] |
| 227 | + fn test_netlink_ifaddrs() { |
| 228 | + let interfaces = unix_interfaces(); |
| 229 | + dbg!(&interfaces); |
| 230 | + assert!(!interfaces.is_empty()); |
| 231 | + } |
| 232 | + } |
64 | 233 | }
|
0 commit comments