@@ -6,6 +6,8 @@ use smoltcp::iface::{Config, Interface, SocketSet};
66#[ cfg( feature = "net-trace" ) ]
77use smoltcp:: phy:: Tracer ;
88use smoltcp:: phy:: { Device , Medium } ;
9+ #[ cfg( feature = "write-pcap-file" ) ]
10+ use smoltcp:: phy:: { PcapMode , PcapWriter } ;
911#[ cfg( feature = "dhcpv4" ) ]
1012use smoltcp:: socket:: dhcpv4;
1113#[ cfg( all( feature = "dns" , not( feature = "dhcpv4" ) ) ) ]
@@ -16,6 +18,8 @@ use smoltcp::wire::{IpCidr, Ipv4Address, Ipv4Cidr};
1618
1719use super :: network:: { NetworkInterface , NetworkState } ;
1820use crate :: arch;
21+ #[ cfg( feature = "write-pcap-file" ) ]
22+ use crate :: drivers:: Driver ;
1923use crate :: drivers:: net:: NetworkDriver ;
2024
2125cfg_select ! {
@@ -42,19 +46,30 @@ impl<'a> NetworkInterface<'a> {
4246 feature = "rtl8139" ,
4347 feature = "virtio-net" ,
4448 ) => {
45- #[ cfg_attr( feature = "net-trace" , expect( unused_mut) ) ]
49+ #[ cfg_attr( any ( feature = "net-trace" , feature = "write-pcap-file" ) , expect( unused_mut) ) ]
4650 let Some ( mut device) = NETWORK_DEVICE . lock( ) . take( ) else {
4751 return NetworkState :: InitializationFailed ;
4852 } ;
4953 }
5054 _ => {
51- #[ cfg_attr( feature = "net-trace" , expect( unused_mut) ) ]
55+ #[ cfg_attr( any ( feature = "net-trace" , feature = "write-pcap-file" ) , expect( unused_mut) ) ]
5256 let mut device = LoopbackDriver :: new( ) ;
5357 }
5458 }
5559
5660 let mac = device. get_mac_address ( ) ;
5761
62+ #[ cfg_attr( feature = "net-trace" , expect( unused_mut) ) ]
63+ #[ cfg( feature = "write-pcap-file" ) ]
64+ let mut device = {
65+ let default_name = device. get_name ( ) ;
66+ PcapWriter :: new (
67+ device,
68+ pcap_writer:: FileSink :: new ( default_name) ,
69+ PcapMode :: Both ,
70+ )
71+ } ;
72+
5873 #[ cfg( feature = "net-trace" ) ]
5974 let mut device = Tracer :: new ( device, |timestamp, printer| trace ! ( "{timestamp} {printer}" ) ) ;
6075
@@ -132,3 +147,102 @@ impl<'a> NetworkInterface<'a> {
132147 } ) )
133148 }
134149}
150+
151+ #[ cfg( feature = "write-pcap-file" ) ]
152+ pub ( in crate :: executor) mod pcap_writer {
153+ use core:: fmt:: Write as _;
154+
155+ use embedded_io:: Write as _;
156+ use smoltcp:: phy:: PcapSink ;
157+
158+ use crate :: errno:: Errno ;
159+ use crate :: fs:: File ;
160+
161+ /// Sink for packet captures. If the file Option is None, the writes are ignored.
162+ /// This is useful when we fail to create the sink file at runtime.
163+ pub struct FileSink ( Option < File > ) ;
164+
165+ impl FileSink {
166+ pub ( super ) fn new ( device_name : & str ) -> Self {
167+ let ( parent, file_prefix, extension) = parse_path ( device_name) ;
168+ let mut path = format ! ( "{parent}/{file_prefix}" ) ;
169+ let base_len = path. len ( ) ;
170+ for i in 1 .. {
171+ if let Some ( extension) = extension {
172+ path. push ( '.' ) ;
173+ path. push_str ( extension) ;
174+ }
175+ match File :: create_new ( path. as_str ( ) ) {
176+ Ok ( file) => {
177+ info ! ( "The packet capture will be written to '{path}'." ) ;
178+ return Self ( Some ( file) ) ;
179+ }
180+ Err ( Errno :: Exist ) => {
181+ path. truncate ( base_len) ;
182+ write ! ( & mut path, " ({i})" ) . unwrap ( ) ;
183+ }
184+ Err ( e) => {
185+ if e == Errno :: Noent {
186+ error ! ( "'{parent}/' does not exist. Is it mounted?" ) ;
187+ }
188+ error ! (
189+ "Error {e:?} encountered while creating the pcap file. No pcap file will be written."
190+ ) ;
191+ break ;
192+ }
193+ }
194+ }
195+ Self ( None )
196+ }
197+ }
198+
199+ fn parse_path ( device_name : & str ) -> ( & str , & str , Option < & str > ) {
200+ let mut parent = "/root" ;
201+ let mut file_prefix = device_name;
202+ let mut extension = Some ( "pcap" ) ;
203+
204+ if let Some ( path) = crate :: env:: var ( "HERMIT_PCAP_PATH" ) . filter ( |var| !var. is_empty ( ) ) {
205+ let file_name = if let Some ( ( l, r) ) = path. rsplit_once ( '/' ) {
206+ parent = l;
207+ if r. is_empty ( ) { None } else { Some ( r) }
208+ } else {
209+ Some ( path. as_str ( ) )
210+ } ;
211+
212+ if let Some ( file_name) = file_name {
213+ ( file_prefix, extension) = if let Some ( ( l, r) ) = file_name. rsplit_once ( '.' )
214+ && !l. is_empty ( )
215+ {
216+ ( l, Some ( r) )
217+ } else {
218+ ( file_name, None )
219+ }
220+ } ;
221+ }
222+ ( parent, file_prefix, extension)
223+ }
224+
225+ impl PcapSink for FileSink {
226+ fn write ( & mut self , data : & [ u8 ] ) {
227+ let Some ( file) = self . 0 . as_mut ( ) else {
228+ trace ! ( "No file to write packet capture." ) ;
229+ return ;
230+ } ;
231+
232+ if let Err ( err) = file. write ( data) {
233+ error ! ( "Error while writing to the pcap file: {err}" ) ;
234+ }
235+ }
236+
237+ fn flush ( & mut self ) {
238+ let Some ( file) = self . 0 . as_mut ( ) else {
239+ trace ! ( "No file to write packet capture." ) ;
240+ return ;
241+ } ;
242+
243+ if let Err ( err) = file. flush ( ) {
244+ error ! ( "Error while flushing the pcap file: {err}" ) ;
245+ }
246+ }
247+ }
248+ }
0 commit comments