1 // Copyright 2023 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 //! Packet Capture handlers and singleton for HTTP and gRPC server.
16 //!
17 //! This module implements a handler for GET, PATCH, LIST capture
18 //!
19 //! /v1/captures --> handle_capture_list
20 //!
21 //! /v1/captures/{id} --> handle_capture_patch, handle_capture_get
22 //!
23 //! handle_capture_cxx calls handle_capture, which calls handle_capture_* based on uri.
24 //! handle_packet_request and handle_packet_response is invoked by packet_hub
25 //! to write packets to files if capture state is on.
26 
27 // TODO(b/274506882): Implement gRPC status proto on error responses. Also write better
28 // and more descriptive error messages with proper error codes.
29 
30 use bytes::Bytes;
31 use http::{Request, Version};
32 use log::warn;
33 use netsim_common::util::time_display::TimeDisplay;
34 use netsim_proto::common::ChipKind;
35 use netsim_proto::frontend::ListCaptureResponse;
36 use protobuf_json_mapping::{print_to_string_with_options, PrintOptions};
37 use std::fs::File;
38 use std::io::{Read, Result};
39 use std::pin::Pin;
40 use std::time::{SystemTime, UNIX_EPOCH};
41 
42 use crate::devices::chip::ChipIdentifier;
43 use crate::ffi::ffi_response_writable::CxxServerResponseWriter;
44 use crate::ffi::CxxServerResponseWriterWrapper;
45 use crate::http_server::server_response::ResponseWritable;
46 use crate::resource::clone_captures;
47 use crate::wifi::radiotap;
48 
49 use anyhow::anyhow;
50 
51 use super::pcap_util::{append_record, append_record_pcapng, wrap_bt_packet, PacketDirection};
52 use super::PCAP_MIME_TYPE;
53 
54 const CHUNK_LEN: usize = 1024;
55 const JSON_PRINT_OPTION: PrintOptions = PrintOptions {
56     enum_values_int: false,
57     proto_field_name: false,
58     always_output_default_values: true,
59     _future_options: (),
60 };
61 
62 /// Helper function for getting file name from the given fields.
get_file(id: ChipIdentifier, device_name: String, chip_kind: ChipKind) -> Result<File>63 fn get_file(id: ChipIdentifier, device_name: String, chip_kind: ChipKind) -> Result<File> {
64     let mut filename = netsim_common::system::netsimd_temp_dir();
65     let extension = match chip_kind {
66         ChipKind::UWB => "pcapng",
67         _ => "pcap",
68     };
69     filename.push("pcaps");
70     filename.push(format!("netsim-{:?}-{:}-{:?}.{}", id, device_name, chip_kind, extension));
71     File::open(filename)
72 }
73 
74 // TODO: GetCapture should return the information of the capture. Need to reconsider
75 // uri hierarchy.
76 // GET /captures/id/{id} --> Get Capture information
77 // GET /captures/contents/{id} --> Download Pcap file
78 /// Performs GetCapture to download pcap file and write to writer.
handle_capture_get(writer: ResponseWritable, id: ChipIdentifier) -> anyhow::Result<()>79 fn handle_capture_get(writer: ResponseWritable, id: ChipIdentifier) -> anyhow::Result<()> {
80     let captures_arc = clone_captures();
81     let mut captures = captures_arc.write().unwrap();
82     let capture = captures
83         .get(id)
84         .ok_or(anyhow!("Cannot access Capture Resource"))?
85         .lock()
86         .map_err(|_| anyhow!("Failed to lock Capture"))?;
87 
88     if capture.size == 0 {
89         return Err(anyhow!(
90             "Capture file not found for {:?}-{}-{:?}",
91             id,
92             capture.device_name,
93             capture.chip_kind
94         ));
95     }
96     let mut file = get_file(id, capture.device_name.clone(), capture.chip_kind)?;
97     let mut buffer = [0u8; CHUNK_LEN];
98     let time_display = TimeDisplay::new(capture.seconds, capture.nanos as u32);
99     let header_value = format!(
100         "attachment; filename=\"netsim-{:?}-{:}-{:?}-{}.{}\"",
101         id,
102         capture.device_name.clone(),
103         capture.chip_kind,
104         time_display.utc_display(),
105         capture.extension
106     );
107     writer.put_ok_with_length(
108         PCAP_MIME_TYPE,
109         capture.size,
110         vec![("Content-Disposition".to_string(), header_value)],
111     );
112     loop {
113         let length = file.read(&mut buffer)?;
114         if length == 0 {
115             break;
116         }
117         writer.put_chunk(&buffer[..length]);
118     }
119     Ok(())
120 }
121 
122 /// Performs ListCapture to get the list of CaptureInfos and write to writer.
handle_capture_list(writer: ResponseWritable) -> anyhow::Result<()>123 fn handle_capture_list(writer: ResponseWritable) -> anyhow::Result<()> {
124     let captures_arc = clone_captures();
125     let captures = captures_arc.write().unwrap();
126     // Instantiate ListCaptureResponse and add Captures
127     let mut response = ListCaptureResponse::new();
128     for capture in captures.values() {
129         response.captures.push(
130             capture.lock().expect("Failed to acquire lock on CaptureInfo").get_capture_proto(),
131         );
132     }
133 
134     // Perform protobuf-json-mapping with the given protobuf
135     let json_response = print_to_string_with_options(&response, &JSON_PRINT_OPTION)
136         .map_err(|e| anyhow!("proto to JSON mapping failure: {}", e))?;
137     writer.put_ok("text/json", &json_response, vec![]);
138     Ok(())
139 }
140 
141 /// Performs PatchCapture to patch a CaptureInfo with id.
142 /// Writes the result of PatchCapture to writer.
handle_capture_patch( writer: ResponseWritable, id: ChipIdentifier, state: bool, ) -> anyhow::Result<()>143 fn handle_capture_patch(
144     writer: ResponseWritable,
145     id: ChipIdentifier,
146     state: bool,
147 ) -> anyhow::Result<()> {
148     let captures_arc = clone_captures();
149     let mut captures = captures_arc.write().unwrap();
150     if let Some(mut capture) = captures
151         .get(id)
152         .map(|arc_capture| arc_capture.lock().expect("Failed to acquire lock on CaptureInfo"))
153     {
154         if state {
155             capture.start_capture()?;
156         } else {
157             capture.stop_capture();
158         }
159         // Perform protobuf-json-mapping with the given protobuf
160         let json_response =
161             print_to_string_with_options(&capture.get_capture_proto(), &JSON_PRINT_OPTION)
162                 .map_err(|e| anyhow!("proto to JSON mapping failure: {}", e))?;
163         writer.put_ok("text/json", &json_response, vec![]);
164     }
165     Ok(())
166 }
167 
168 /// The Rust capture handler used directly by Http frontend or handle_capture_cxx for LIST, GET, and PATCH
handle_capture(request: &Request<Vec<u8>>, param: &str, writer: ResponseWritable)169 pub fn handle_capture(request: &Request<Vec<u8>>, param: &str, writer: ResponseWritable) {
170     if let Err(e) = handle_capture_internal(request, param, writer) {
171         writer.put_error(404, &e.to_string());
172     }
173 }
174 
get_id(param: &str) -> anyhow::Result<ChipIdentifier>175 fn get_id(param: &str) -> anyhow::Result<ChipIdentifier> {
176     param
177         .parse::<u32>()
178         .map_err(|_| anyhow!("Capture ID must be u32, found {}", param))
179         .map(ChipIdentifier)
180 }
181 
handle_capture_internal( request: &Request<Vec<u8>>, param: &str, writer: ResponseWritable, ) -> anyhow::Result<()>182 fn handle_capture_internal(
183     request: &Request<Vec<u8>>,
184     param: &str,
185     writer: ResponseWritable,
186 ) -> anyhow::Result<()> {
187     if request.uri() == "/v1/captures" {
188         match request.method().as_str() {
189             "GET" => handle_capture_list(writer),
190             _ => Err(anyhow!("Not found.")),
191         }
192     } else {
193         match request.method().as_str() {
194             "GET" => handle_capture_get(writer, get_id(param)?),
195             "PATCH" => {
196                 let id = get_id(param)?;
197                 let body = request.body();
198                 let state = String::from_utf8(body.to_vec()).unwrap();
199                 match state.as_str() {
200                     "0" => handle_capture_patch(writer, id, false),
201                     "1" => handle_capture_patch(writer, id, true),
202                     _ => Err(anyhow!("Incorrect state for PatchCapture")),
203                 }
204             }
205             _ => Err(anyhow!("Not found.")),
206         }
207     }
208 }
209 
210 /// Capture handler cxx for grpc server to call
handle_capture_cxx( responder: Pin<&mut CxxServerResponseWriter>, method: String, param: String, body: String, )211 pub fn handle_capture_cxx(
212     responder: Pin<&mut CxxServerResponseWriter>,
213     method: String,
214     param: String,
215     body: String,
216 ) {
217     let mut builder = Request::builder().method(method.as_str());
218     if param.is_empty() {
219         builder = builder.uri("/v1/captures");
220     } else {
221         builder = builder.uri(format!("/v1/captures/{}", param));
222     }
223     builder = builder.version(Version::HTTP_11);
224     let request = match builder.body(body.as_bytes().to_vec()) {
225         Ok(request) => request,
226         Err(err) => {
227             warn!("{err:?}");
228             return;
229         }
230     };
231     handle_capture(
232         &request,
233         param.as_str(),
234         &mut CxxServerResponseWriterWrapper { writer: responder },
235     );
236 }
237 
238 /// A common code for handle_request and handle_response methods.
handle_packet( chip_id: ChipIdentifier, packet: &[u8], packet_type: u32, direction: PacketDirection, )239 pub(super) fn handle_packet(
240     chip_id: ChipIdentifier,
241     packet: &[u8],
242     packet_type: u32,
243     direction: PacketDirection,
244 ) {
245     let captures_arc = clone_captures();
246     let captures = captures_arc.write().unwrap();
247     if let Some(mut capture) = captures
248         .chip_id_to_capture
249         .get(&chip_id)
250         .map(|arc_capture| arc_capture.lock().expect("Failed to acquire lock on CaptureInfo"))
251     {
252         let chip_kind = capture.chip_kind;
253         if let Some(ref mut file) = capture.file {
254             let timestamp =
255                 SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
256             let packet_buf = match chip_kind {
257                 ChipKind::BLUETOOTH => wrap_bt_packet(direction, packet_type, packet),
258                 ChipKind::WIFI => match radiotap::into_pcap(packet) {
259                     Some(buffer) => buffer,
260                     None => return,
261                 },
262                 ChipKind::UWB => {
263                     match append_record_pcapng(timestamp, file, packet) {
264                         Ok(size) => {
265                             capture.size += size;
266                             capture.records += 1;
267                         }
268                         Err(err) => warn!("{err:?}"),
269                     }
270                     return;
271                 }
272                 _ => {
273                     warn!("Unknown capture type");
274                     return;
275                 }
276             };
277             match append_record(timestamp, file, packet_buf.as_slice()) {
278                 Ok(size) => {
279                     capture.size += size;
280                     capture.records += 1;
281                 }
282                 Err(err) => {
283                     warn!("{err:?}");
284                 }
285             }
286         }
287     };
288 }
289 
290 /// Method for dispatcher to invoke (Host to Controller Packet Flow)
host_to_controller(chip_id: ChipIdentifier, packet: &Bytes, packet_type: u32)291 pub fn host_to_controller(chip_id: ChipIdentifier, packet: &Bytes, packet_type: u32) {
292     clone_captures().read().unwrap().send(
293         chip_id,
294         packet,
295         packet_type,
296         PacketDirection::HostToController,
297     );
298 }
299 
300 /// Method for dispatcher to invoke (Controller to Host Packet Flow)
controller_to_host(chip_id: ChipIdentifier, packet: &Bytes, packet_type: u32)301 pub fn controller_to_host(chip_id: ChipIdentifier, packet: &Bytes, packet_type: u32) {
302     clone_captures().read().unwrap().send(
303         chip_id,
304         packet,
305         packet_type,
306         PacketDirection::ControllerToHost,
307     );
308 }
309 
310 /// Method for clearing pcap files in temp directory
clear_pcap_files() -> bool311 pub fn clear_pcap_files() -> bool {
312     let mut path = netsim_common::system::netsimd_temp_dir();
313     path.push("pcaps");
314 
315     // Check if the directory exists.
316     if std::fs::metadata(&path).is_err() {
317         return false;
318     }
319 
320     // Delete the directory.
321     std::fs::remove_dir_all(&path).is_ok()
322 }
323