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