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