1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Controller interface for the `hostapd` C library.
16 //!
17 //! This module allows interaction with `hostapd` to manage WiFi access point and perform various wireless networking tasks directly from Rust code.
18 //!
19 //! The main `hostapd` process is managed by a separate thread while responses from the `hostapd` process are handled
20 //! by another thread, ensuring efficient and non-blocking communication.
21 //!
22 //! `hostapd` configuration consists of key-value pairs. The default configuration file is generated in the discovery directory.
23 //!
24 //! ## Features
25 //!
26 //! * **Asynchronous operation:** The module utilizes `tokio` for asynchronous communication with the `hostapd` process,
27 //! allowing for efficient and non-blocking operations.
28 //! * **Platform support:** Supports Linux, macOS, and Windows.
29 //! * **Configuration management:** Provides functionality to generate and manage `hostapd` configuration files.
30 //! * **Easy integration:** Offers a high-level API to simplify interaction with `hostapd`, abstracting away
31 //! low-level details.
32 //!
33 //! ## Usage
34 //!
35 //! Here's a basic example of how to create a `Hostapd` instance and start the `hostapd` process:
36 //!
37 //! ```
38 //! use hostapd_rs::hostapd::Hostapd;
39 //! use std::path::PathBuf;
40 //! use std::sync::mpsc;
41 //!
42 //! fn main() {
43 //! // Create a channel for receiving data from hostapd
44 //! let (tx, _) = mpsc::channel();
45 //!
46 //! // Create a new Hostapd instance
47 //! let mut hostapd = Hostapd::new(
48 //! tx, // Sender for receiving data
49 //! true, // Verbose mode (optional)
50 //! PathBuf::from("/tmp/hostapd.conf"), // Path to the configuration file
51 //! );
52 //!
53 //! // Start the hostapd process
54 //! hostapd.run();
55 //! }
56 //! ```
57 //!
58 //! This starts `hostapd` in a separate thread, allowing interaction with it using the `Hostapd` struct's methods.
59
60 use anyhow::bail;
61 use bytes::Bytes;
62 use log::{info, warn};
63 use netsim_packets::ieee80211::{Ieee80211, MacAddress};
64 use std::collections::HashMap;
65 use std::ffi::{c_char, c_int, CStr, CString};
66 use std::fs::File;
67 use std::io::{BufWriter, Write};
68 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
69 #[cfg(unix)]
70 use std::os::fd::IntoRawFd;
71 #[cfg(windows)]
72 use std::os::windows::io::IntoRawSocket;
73 use std::path::PathBuf;
74 use std::sync::{mpsc, Arc, RwLock};
75 use std::thread::{self, sleep};
76 use std::time::Duration;
77 use tokio::io::{AsyncReadExt, AsyncWriteExt};
78 use tokio::net::{
79 tcp::{OwnedReadHalf, OwnedWriteHalf},
80 TcpListener, TcpStream,
81 };
82 use tokio::runtime::Runtime;
83 use tokio::sync::Mutex;
84
85 use crate::hostapd_sys::{
86 run_hostapd_main, set_virtio_ctrl_sock, set_virtio_sock, VIRTIO_WIFI_CTRL_CMD_RELOAD_CONFIG,
87 VIRTIO_WIFI_CTRL_CMD_TERMINATE,
88 };
89
90 /// Alias for RawFd on Unix or RawSocket on Windows (converted to i32)
91 type RawDescriptor = i32;
92
93 /// Hostapd process interface.
94 ///
95 /// This struct provides methods for interacting with the `hostapd` process,
96 /// such as starting and stopping the process, configuring the access point,
97 /// and sending and receiving data.
98 pub struct Hostapd {
99 // TODO: update to tokio based RwLock when usages are async
100 handle: RwLock<Option<thread::JoinHandle<()>>>,
101 verbose: bool,
102 config: HashMap<String, String>,
103 config_path: PathBuf,
104 data_writer: Option<Mutex<OwnedWriteHalf>>,
105 ctrl_writer: Option<Mutex<OwnedWriteHalf>>,
106 tx_bytes: mpsc::Sender<Bytes>,
107 runtime: Arc<Runtime>,
108 // MAC address of the access point.
109 bssid: MacAddress,
110 }
111
112 impl Hostapd {
113 /// Creates a new `Hostapd` instance.
114 ///
115 /// # Arguments
116 ///
117 /// * `tx_bytes`: Sender for transmitting data received from `hostapd`.
118 /// * `verbose`: Whether to run `hostapd` in verbose mode.
119 /// * `config_path`: Path to the `hostapd` configuration file.
120
new(tx_bytes: mpsc::Sender<Bytes>, verbose: bool, config_path: PathBuf) -> Self121 pub fn new(tx_bytes: mpsc::Sender<Bytes>, verbose: bool, config_path: PathBuf) -> Self {
122 // Default Hostapd conf entries
123 let config_data = [
124 ("ssid", "AndroidWifi"),
125 ("interface", "wlan1"),
126 ("driver", "virtio_wifi"),
127 ("bssid", "00:13:10:95:fe:0b"),
128 ("country_code", "US"),
129 ("hw_mode", "g"),
130 ("channel", "8"),
131 ("beacon_int", "1000"),
132 ("dtim_period", "2"),
133 ("max_num_sta", "255"),
134 ("rts_threshold", "2347"),
135 ("fragm_threshold", "2346"),
136 ("macaddr_acl", "0"),
137 ("auth_algs", "3"),
138 ("ignore_broadcast_ssid", "0"),
139 ("wmm_enabled", "0"),
140 ("ieee80211n", "1"),
141 ("eapol_key_index_workaround", "0"),
142 ];
143 let mut config: HashMap<String, String> = HashMap::new();
144 config.extend(config_data.iter().map(|(k, v)| (k.to_string(), v.to_string())));
145
146 // TODO(b/381154253): Allow configuring BSSID in hostapd.conf.
147 // Currently, the BSSID is hardcoded in external/wpa_supplicant_8/src/drivers/driver_virtio_wifi.c. This should be configured by hostapd.conf and allow to be set by `Hostapd`.
148 let bssid_bytes: [u8; 6] = [0x00, 0x13, 0x10, 0x85, 0xfe, 0x01];
149 let bssid = MacAddress::from(&bssid_bytes);
150 Hostapd {
151 handle: RwLock::new(None),
152 verbose,
153 config,
154 config_path,
155 data_writer: None,
156 ctrl_writer: None,
157 tx_bytes,
158 runtime: Arc::new(Runtime::new().unwrap()),
159 bssid,
160 }
161 }
162
163 /// Starts the `hostapd` main process and response thread.
164 ///
165 /// The "hostapd" thread manages the C `hostapd` process by running `run_hostapd_main`.
166 /// The "hostapd_response" thread manages traffic between `hostapd` and netsim.
167 ///
168 /// TODO:
169 /// * update as async fn.
run(&mut self) -> bool170 pub fn run(&mut self) -> bool {
171 // Check if already running
172 assert!(!self.is_running(), "hostapd is already running!");
173 // Setup config file
174 self.gen_config_file().unwrap_or_else(|_| {
175 panic!("Failed to generate config file: {:?}.", self.config_path.display())
176 });
177
178 // Setup Sockets
179 let (ctrl_listener, _ctrl_reader, ctrl_writer) =
180 self.create_pipe().expect("Failed to create ctrl pipe");
181 self.ctrl_writer = Some(Mutex::new(ctrl_writer));
182 let (data_listener, data_reader, data_writer) =
183 self.create_pipe().expect("Failed to create data pipe");
184 self.data_writer = Some(Mutex::new(data_writer));
185
186 // Start hostapd thread
187 let verbose = self.verbose;
188 let config_path = self.config_path.to_string_lossy().into_owned();
189 *self.handle.write().unwrap() = Some(
190 thread::Builder::new()
191 .name("hostapd".to_string())
192 .spawn(move || Self::hostapd_thread(verbose, config_path))
193 .expect("Failed to spawn Hostapd thread"),
194 );
195
196 // Start hostapd response thread
197 let tx_bytes = self.tx_bytes.clone();
198 let runtime = Arc::clone(&self.runtime);
199 let _ = thread::Builder::new()
200 .name("hostapd_response".to_string())
201 .spawn(move || {
202 Self::hostapd_response_thread(
203 data_listener,
204 ctrl_listener,
205 data_reader,
206 tx_bytes,
207 runtime,
208 );
209 })
210 .expect("Failed to spawn hostapd_response thread");
211
212 true
213 }
214
215 /// Reconfigures `Hostapd` with the specified SSID (and password).
216 ///
217 /// TODO:
218 /// * implement password & encryption support
219 /// * update as async fn.
set_ssid( &mut self, ssid: impl Into<String>, password: impl Into<String>, ) -> anyhow::Result<()>220 pub fn set_ssid(
221 &mut self,
222 ssid: impl Into<String>,
223 password: impl Into<String>,
224 ) -> anyhow::Result<()> {
225 let ssid = ssid.into();
226 let password = password.into();
227 if ssid.is_empty() {
228 bail!("set_ssid must have a non-empty SSID");
229 }
230
231 if !password.is_empty() {
232 bail!("set_ssid with password is not yet supported.");
233 }
234
235 if ssid == self.get_ssid() && password == self.get_config_val("password") {
236 info!("SSID and password matches current configuration.");
237 return Ok(());
238 }
239
240 // Update the config
241 self.config.insert("ssid".to_string(), ssid);
242 if !password.is_empty() {
243 let password_config = [
244 ("wpa", "2"),
245 ("wpa_key_mgmt", "WPA-PSK"),
246 ("rsn_pairwise", "CCMP"),
247 ("wpa_passphrase", &password),
248 ];
249 self.config.extend(password_config.iter().map(|(k, v)| (k.to_string(), v.to_string())));
250 }
251
252 // Update the config file.
253 self.gen_config_file()?;
254
255 // Send command for Hostapd to reload config file
256 if let Err(e) = self.runtime.block_on(Self::async_write(
257 self.ctrl_writer.as_ref().unwrap(),
258 c_string_to_bytes(VIRTIO_WIFI_CTRL_CMD_RELOAD_CONFIG),
259 )) {
260 bail!("Failed to send VIRTIO_WIFI_CTRL_CMD_RELOAD_CONFIG to hostapd to reload config: {:?}", e);
261 }
262
263 Ok(())
264 }
265
266 /// Retrieves the current SSID in the `Hostapd` configuration.
get_ssid(&self) -> String267 pub fn get_ssid(&self) -> String {
268 self.get_config_val("ssid")
269 }
270
271 /// Retrieves the `Hostapd`'s BSSID.
get_bssid(&self) -> MacAddress272 pub fn get_bssid(&self) -> MacAddress {
273 self.bssid
274 }
275
276 /// Attempt to encrypt the given IEEE 802.11 frame.
try_encrypt(&self, _ieee80211: &Ieee80211) -> Option<Ieee80211>277 pub fn try_encrypt(&self, _ieee80211: &Ieee80211) -> Option<Ieee80211> {
278 // TODO
279 None
280 }
281
282 /// Attempt to decrypt the given IEEE 802.11 frame.
try_decrypt(&self, _ieee80211: &Ieee80211) -> Option<Ieee80211>283 pub fn try_decrypt(&self, _ieee80211: &Ieee80211) -> Option<Ieee80211> {
284 // TODO
285 None
286 }
287
288 /// Inputs data packet bytes from netsim to `hostapd`.
289 ///
290 /// TODO:
291 /// * update as async fn.
input(&self, bytes: Bytes) -> anyhow::Result<()>292 pub fn input(&self, bytes: Bytes) -> anyhow::Result<()> {
293 // Make sure hostapd is already running
294 assert!(self.is_running(), "Failed to send input. Hostapd is not running.");
295 self.runtime.block_on(Self::async_write(self.data_writer.as_ref().unwrap(), &bytes))
296 }
297
298 /// Checks whether the `hostapd` thread is running.
is_running(&self) -> bool299 pub fn is_running(&self) -> bool {
300 let handle_lock = self.handle.read().unwrap();
301 handle_lock.is_some() && !handle_lock.as_ref().unwrap().is_finished()
302 }
303
304 /// Terminates the `Hostapd` process thread by sending a control command.
terminate(&self)305 pub fn terminate(&self) {
306 if !self.is_running() {
307 warn!("hostapd terminate() called when hostapd thread is not running");
308 return;
309 }
310
311 // Send terminate command to hostapd
312 if let Err(e) = self.runtime.block_on(Self::async_write(
313 self.ctrl_writer.as_ref().unwrap(),
314 c_string_to_bytes(VIRTIO_WIFI_CTRL_CMD_TERMINATE),
315 )) {
316 warn!("Failed to send VIRTIO_WIFI_CTRL_CMD_TERMINATE to hostapd to terminate: {:?}", e);
317 }
318 }
319
320 /// Generates the `hostapd.conf` file in the discovery directory.
gen_config_file(&self) -> anyhow::Result<()>321 fn gen_config_file(&self) -> anyhow::Result<()> {
322 let conf_file = File::create(self.config_path.clone())?; // Create or overwrite the file
323 let mut writer = BufWriter::new(conf_file);
324
325 for (key, value) in &self.config {
326 writeln!(&mut writer, "{}={}", key, value)?;
327 }
328
329 Ok(writer.flush()?) // Ensure all data is written to the file
330 }
331
332 /// Gets the value of the given key in the config.
333 ///
334 /// Returns an empty String if the key is not found.
get_config_val(&self, key: &str) -> String335 fn get_config_val(&self, key: &str) -> String {
336 self.config.get(key).cloned().unwrap_or_default()
337 }
338
339 /// Creates a pipe of two connected `TcpStream` objects.
340 ///
341 /// Extracts the first stream's raw descriptor and splits the second stream
342 /// into `OwnedReadHalf` and `OwnedWriteHalf`.
343 ///
344 /// # Returns
345 ///
346 /// * `Ok((listener, read_half, write_half))` if the pipe creation is successful.
347 /// * `Err(std::io::Error)` if an error occurs during pipe creation.
create_pipe( &self, ) -> anyhow::Result<(RawDescriptor, OwnedReadHalf, OwnedWriteHalf), std::io::Error>348 fn create_pipe(
349 &self,
350 ) -> anyhow::Result<(RawDescriptor, OwnedReadHalf, OwnedWriteHalf), std::io::Error> {
351 let (listener, stream) = self.runtime.block_on(Self::async_create_pipe())?;
352 let listener = into_raw_descriptor(listener);
353 let (read_half, write_half) = stream.into_split();
354 Ok((listener, read_half, write_half))
355 }
356
357 /// Creates a pipe asynchronously.
async_create_pipe() -> anyhow::Result<(TcpStream, TcpStream), std::io::Error>358 async fn async_create_pipe() -> anyhow::Result<(TcpStream, TcpStream), std::io::Error> {
359 let listener = match TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, 0))).await {
360 Ok(listener) => listener,
361 Err(e) => {
362 // Support hosts that only have IPv6
363 info!("Failed to bind to 127.0.0.1:0. Try to bind to [::1]:0 next. Err: {:?}", e);
364 TcpListener::bind(SocketAddr::from((Ipv6Addr::LOCALHOST, 0))).await?
365 }
366 };
367 let addr = listener.local_addr()?;
368 let stream = TcpStream::connect(addr).await?;
369 let (listener, _) = listener.accept().await?;
370 Ok((listener, stream))
371 }
372
373 /// Writes data to a writer asynchronously.
async_write(writer: &Mutex<OwnedWriteHalf>, data: &[u8]) -> anyhow::Result<()>374 async fn async_write(writer: &Mutex<OwnedWriteHalf>, data: &[u8]) -> anyhow::Result<()> {
375 let mut writer_guard = writer.lock().await;
376 writer_guard.write_all(data).await?;
377 writer_guard.flush().await?;
378 Ok(())
379 }
380
381 /// Runs the C `hostapd` process with `run_hostapd_main`.
382 ///
383 /// This function is meant to be spawned in a separate thread.
hostapd_thread(verbose: bool, config_path: String)384 fn hostapd_thread(verbose: bool, config_path: String) {
385 let mut args = vec![CString::new("hostapd").unwrap()];
386 if verbose {
387 args.push(CString::new("-dddd").unwrap());
388 }
389 args.push(
390 CString::new(config_path.clone()).unwrap_or_else(|_| {
391 panic!("CString::new error on config file path: {}", config_path)
392 }),
393 );
394 let argv: Vec<*const c_char> = args.iter().map(|arg| arg.as_ptr()).collect();
395 let argc = argv.len() as c_int;
396 // Safety: we ensure that argc is length of argv and argv.as_ptr() is a valid pointer of hostapd args
397 unsafe { run_hostapd_main(argc, argv.as_ptr()) };
398 }
399
400 /// Sets the virtio (driver) data and control sockets.
set_virtio_driver_socket( data_descriptor: RawDescriptor, ctrl_descriptor: RawDescriptor, ) -> bool401 fn set_virtio_driver_socket(
402 data_descriptor: RawDescriptor,
403 ctrl_descriptor: RawDescriptor,
404 ) -> bool {
405 // Safety: we ensure that data_descriptor and ctrl_descriptor are valid i32 raw file descriptor or socket
406 unsafe {
407 set_virtio_sock(data_descriptor) == 0 && set_virtio_ctrl_sock(ctrl_descriptor) == 0
408 }
409 }
410
411 /// Manages reading `hostapd` responses and sending them via `tx_bytes`.
412 ///
413 /// The thread first attempts to set virtio driver sockets with retries until success.
414 /// Next, the thread reads `hostapd` responses and writes them to netsim.
hostapd_response_thread( data_listener: RawDescriptor, ctrl_listener: RawDescriptor, mut data_reader: OwnedReadHalf, tx_bytes: mpsc::Sender<Bytes>, runtime: Arc<Runtime>, )415 fn hostapd_response_thread(
416 data_listener: RawDescriptor,
417 ctrl_listener: RawDescriptor,
418 mut data_reader: OwnedReadHalf,
419 tx_bytes: mpsc::Sender<Bytes>,
420 runtime: Arc<Runtime>,
421 ) {
422 let mut buf: [u8; 1500] = [0u8; 1500];
423 loop {
424 if !Self::set_virtio_driver_socket(data_listener, ctrl_listener) {
425 warn!("Unable to set virtio driver socket. Retrying...");
426 sleep(Duration::from_millis(250));
427 continue;
428 };
429 break;
430 }
431 loop {
432 let size = match runtime.block_on(async { data_reader.read(&mut buf[..]).await }) {
433 Ok(size) => size,
434 Err(e) => {
435 warn!("Failed to read hostapd response: {:?}", e);
436 break;
437 }
438 };
439
440 if let Err(e) = tx_bytes.send(Bytes::copy_from_slice(&buf[..size])) {
441 warn!("Failed to send hostapd packet response: {:?}", e);
442 break;
443 };
444 }
445 }
446 }
447
448 impl Drop for Hostapd {
449 /// Terminates the `hostapd` process when the `Hostapd` instance is dropped.
drop(&mut self)450 fn drop(&mut self) {
451 self.terminate();
452 }
453 }
454
455 /// Converts a `TcpStream` to a `RawDescriptor` (i32).
into_raw_descriptor(stream: TcpStream) -> RawDescriptor456 fn into_raw_descriptor(stream: TcpStream) -> RawDescriptor {
457 let std_stream = stream.into_std().expect("into_raw_descriptor's into_std() failed");
458 // hostapd fd expects blocking, but rust set non-blocking for async
459 std_stream.set_nonblocking(false).expect("non-blocking");
460
461 // Use into_raw_fd for Unix to pass raw file descriptor to C
462 #[cfg(unix)]
463 return std_stream.into_raw_fd();
464
465 // Use into_raw_socket for Windows to pass raw socket to C
466 #[cfg(windows)]
467 std_stream.into_raw_socket().try_into().expect("Failed to convert Raw Socket value into i32")
468 }
469
470 /// Converts a null-terminated c-string slice into `&[u8]` bytes without the null terminator.
c_string_to_bytes(c_string: &[u8]) -> &[u8]471 fn c_string_to_bytes(c_string: &[u8]) -> &[u8] {
472 CStr::from_bytes_with_nul(c_string).unwrap().to_bytes()
473 }
474