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