@@ -15,7 +15,8 @@ use axum::{
1515use axum_extra:: response:: ErasedJson ;
1616use axum_server:: Handle ;
1717use hyper_util:: rt:: TokioTimer ;
18- use tower:: limit:: ConcurrencyLimitLayer ;
18+ use tower:: { limit:: ConcurrencyLimitLayer , ServiceBuilder } ;
19+
1920use tower_http:: {
2021 cors:: { AllowHeaders , AllowMethods , AllowOrigin , CorsLayer } ,
2122 trace:: { DefaultMakeSpan , DefaultOnFailure , DefaultOnResponse , TraceLayer } ,
@@ -27,6 +28,9 @@ use tracker::{
2728 info:: { ConnectionTrack , Track , TrackInfo } ,
2829} ;
2930
31+ #[ cfg( target_os = "linux" ) ]
32+ use tracker:: capture:: TcpCaptureTrack ;
33+
3034use crate :: { error:: Error , Args , Result } ;
3135
3236#[ tokio:: main]
@@ -44,8 +48,8 @@ pub async fn run(args: Args) -> Result<()> {
4448 tracing:: info!( "Concurrent limit: {}" , args. concurrent) ;
4549 tracing:: info!( "Bind address: {}" , args. bind) ;
4650
47- // init global layer provider
48- let global_layer = tower :: ServiceBuilder :: new ( )
51+ // Init global layer
52+ let global_layer = ServiceBuilder :: new ( )
4953 . layer (
5054 TraceLayer :: new_for_http ( )
5155 . make_span_with ( DefaultMakeSpan :: new ( ) . level ( Level :: INFO ) )
@@ -61,17 +65,50 @@ pub async fn run(args: Args) -> Result<()> {
6165 )
6266 . layer ( ConcurrencyLimitLayer :: new ( args. concurrent ) ) ;
6367
64- let router = Router :: new ( )
68+ // Create the router with the tracking endpoints
69+ #[ cfg_attr( not( target_os = "linux" ) , allow( unused_mut) ) ]
70+ let mut router = Router :: new ( )
6571 . route ( "/api/all" , any ( track) )
6672 . route ( "/api/tls" , any ( tls_track) )
6773 . route ( "/api/http1" , any ( http1_track) )
68- . route ( "/api/http2" , any ( http2_track) )
69- . layer ( global_layer) ;
74+ . route ( "/api/http2" , any ( http2_track) ) ;
7075
7176 // Signal the server to shutdown using Handle.
7277 let handle = Handle :: new ( ) ;
7378
79+ // Add TCP tracking layer
80+ #[ cfg( target_os = "linux" ) ]
81+ {
82+ let mut tcp_capture_track: Option < TcpCaptureTrack > = None ;
83+ if args. tcp_capture_packet {
84+ tracing:: info!( "Enabling TCP/IP packet capture (requires root)" ) ;
85+ let capture = TcpCaptureTrack :: new ( 128 , args. bind . port ( ) ) ;
86+ if let Err ( err) = capture. start_capture ( args. tcp_capture_interface . clone ( ) ) {
87+ tracing:: error!( "Failed to start TCP/IP packet capture: {err}" ) ;
88+ } else {
89+ if let Some ( iface) = args. tcp_capture_interface {
90+ tracing:: info!(
91+ "TCP/IP packet capture started successfully on interface {iface}"
92+ ) ;
93+ }
94+ tcp_capture_track = Some ( capture) ;
95+ }
96+ }
97+
98+ if let Some ( capture) = tcp_capture_track. clone ( ) {
99+ router = router
100+ . route ( "/api/tcp" , any ( tcp_track) )
101+ . layer ( Extension ( capture) ) ;
102+ }
103+
104+ tokio:: spawn ( signal:: graceful_shutdown (
105+ handle. clone ( ) ,
106+ tcp_capture_track. clone ( ) ,
107+ ) ) ;
108+ }
109+
74110 // Spawn a task to gracefully shutdown server.
111+ #[ cfg( not( target_os = "linux" ) ) ]
75112 tokio:: spawn ( signal:: graceful_shutdown ( handle. clone ( ) ) ) ;
76113
77114 // Load TLS configuration with HTTP/2 ALPN preference
@@ -99,7 +136,11 @@ pub async fn run(args: Args) -> Result<()> {
99136 server
100137 . handle ( handle)
101138 . map ( TrackAcceptor :: new)
102- . serve ( router. into_make_service_with_connect_info :: < SocketAddr > ( ) )
139+ . serve (
140+ router
141+ . layer ( global_layer)
142+ . into_make_service_with_connect_info :: < SocketAddr > ( ) ,
143+ )
103144 . await
104145 . map_err ( Into :: into)
105146}
@@ -115,12 +156,42 @@ impl IntoResponse for Error {
115156pub async fn track (
116157 Extension ( ConnectInfo ( addr) ) : Extension < ConnectInfo < SocketAddr > > ,
117158 Extension ( track) : Extension < ConnectionTrack > ,
159+ #[ cfg( target_os = "linux" ) ] tcp_capture : Option < Extension < TcpCaptureTrack > > ,
118160 req : Request < Body > ,
119161) -> Result < ErasedJson > {
120- tokio:: task:: spawn_blocking ( move || TrackInfo :: new ( Track :: All , addr, req, track) )
162+ // get TCP packets if capture is available
163+ #[ cfg( target_os = "linux" ) ]
164+ let tcp_packets = if let Some ( Extension ( capture) ) = tcp_capture {
165+ // small delay to capture packets
166+ tokio:: time:: sleep ( Duration :: from_millis ( 100 ) ) . await ;
167+
168+ let client_ip = addr. ip ( ) . to_string ( ) ;
169+ let client_port = addr. port ( ) ;
170+
171+ let packets = capture. get_packets_for_client ( & client_ip, client_port) ;
172+ capture. clear_packets_for_client ( & client_ip, client_port) ;
173+ packets
174+ } else {
175+ Vec :: new ( )
176+ } ;
177+
178+ #[ cfg( target_os = "linux" ) ]
179+ {
180+ tokio:: task:: spawn_blocking ( move || {
181+ TrackInfo :: new_with_tcp ( Track :: All , addr, req, track, tcp_packets)
182+ } )
121183 . await
122184 . map ( ErasedJson :: pretty)
123185 . map_err ( Error :: from)
186+ }
187+
188+ #[ cfg( not( target_os = "linux" ) ) ]
189+ {
190+ tokio:: task:: spawn_blocking ( move || TrackInfo :: new ( Track :: All , addr, req, track) )
191+ . await
192+ . map ( ErasedJson :: pretty)
193+ . map_err ( Error :: from)
194+ }
124195}
125196
126197#[ inline]
@@ -158,3 +229,21 @@ pub async fn http2_track(
158229 . map ( ErasedJson :: pretty)
159230 . map_err ( Error :: from)
160231}
232+
233+ #[ inline]
234+ #[ cfg( target_os = "linux" ) ]
235+ pub async fn tcp_track (
236+ Extension ( ConnectInfo ( addr) ) : Extension < ConnectInfo < SocketAddr > > ,
237+ Extension ( capture) : Extension < TcpCaptureTrack > ,
238+ ) -> Result < ErasedJson > {
239+ tokio:: time:: sleep ( Duration :: from_millis ( 100 ) ) . await ;
240+
241+ let client_ip = addr. ip ( ) . to_string ( ) ;
242+ let client_port = addr. port ( ) ;
243+
244+ let packets = capture. get_packets_for_client ( & client_ip, client_port) ;
245+
246+ capture. clear_packets_for_client ( & client_ip, client_port) ;
247+
248+ Ok ( ErasedJson :: pretty ( & packets) )
249+ }
0 commit comments