@@ -26,12 +26,17 @@ struct Shared {
2626 tls : TlsMode ,
2727}
2828
29+ /// An accepted connection queued to a worker, paired with the per-IP slot guard
30+ /// that must live for the connection's whole lifetime.
31+ type Job = ( TcpStream , common:: PeerGuard ) ;
32+
2933/// Run a blocking accept loop, dispatching connections to `workers` threads.
3034pub ( crate ) fn run (
3135 listener : TcpListener ,
3236 cfg : SessionConfig ,
3337 tls : TlsMode ,
3438 workers : usize ,
39+ limiter : Arc < common:: PeerLimiter > ,
3540 ready : Option < Sender < ( ) > > ,
3641) -> Result < ( ) > {
3742 // On-demand ACME (TLS-ALPN-01) can self-deadlock with a single worker: the
@@ -55,8 +60,7 @@ pub(crate) fn run(
5560 // the queue is full `send` blocks the accept loop; further connections wait
5661 // in the kernel backlog and are shed there if it too fills.
5762 let backlog = workers. saturating_mul ( 64 ) . clamp ( 256 , 4096 ) ;
58- let ( tx, rx) : ( SyncSender < TcpStream > , Receiver < TcpStream > ) =
59- std:: sync:: mpsc:: sync_channel ( backlog) ;
63+ let ( tx, rx) : ( SyncSender < Job > , Receiver < Job > ) = std:: sync:: mpsc:: sync_channel ( backlog) ;
6064 let rx = Arc :: new ( Mutex :: new ( rx) ) ;
6165
6266 for _ in 0 ..workers {
@@ -75,7 +79,14 @@ pub(crate) fn run(
7579 for incoming in listener. incoming ( ) {
7680 match incoming {
7781 Ok ( stream) => {
78- if tx. send ( stream) . is_err ( ) {
82+ // Enforce the per-IP cap before queueing. `continue` drops
83+ // `stream`, closing the over-limit connection (shed silently,
84+ // like the global caps).
85+ let guard = match limiter. admit ( stream. peer_addr ( ) . ok ( ) . map ( |a| a. ip ( ) ) ) {
86+ Some ( g) => g,
87+ None => continue ,
88+ } ;
89+ if tx. send ( ( stream, guard) ) . is_err ( ) {
7990 break ;
8091 }
8192 }
@@ -89,18 +100,22 @@ pub(crate) fn run(
89100 Ok ( ( ) )
90101}
91102
92- fn worker_loop ( rx : Arc < Mutex < Receiver < TcpStream > > > , shared : Arc < Shared > ) {
103+ fn worker_loop ( rx : Arc < Mutex < Receiver < Job > > > , shared : Arc < Shared > ) {
93104 loop {
94- let stream = {
95- let guard = match rx. lock ( ) {
105+ let received = {
106+ let lock = match rx. lock ( ) {
96107 Ok ( g) => g,
97108 Err ( _) => return ,
98109 } ;
99- guard . recv ( )
110+ lock . recv ( )
100111 } ;
101- let Ok ( stream) = stream else {
112+ let Ok ( ( stream, guard ) ) = received else {
102113 return ;
103114 } ;
115+ // Hold the per-IP slot for the whole connection: keep `guard` alive
116+ // across `catch_unwind` and drop it (releasing the slot) only after the
117+ // handler returns — even if it panics.
118+ let _guard = guard;
104119 // Isolate each connection: a panic while serving one client must not
105120 // kill the worker thread, which would permanently shrink the pool and,
106121 // once every worker died, wedge the whole server.
@@ -167,7 +182,11 @@ fn handle_acme(
167182/// Accept loop for the plain-HTTP listener (redirects + ACME HTTP-01). It spawns
168183/// a thread per connection — these are short-lived (read a request, reply, close)
169184/// — but caps how many run at once so a flood can't spawn unbounded threads.
170- pub ( crate ) fn run_http_redirect ( listener : TcpListener , ctx : HttpCtx ) {
185+ pub ( crate ) fn run_http_redirect (
186+ listener : TcpListener ,
187+ ctx : HttpCtx ,
188+ limiter : Arc < common:: PeerLimiter > ,
189+ ) {
171190 /// Maximum redirect connections served concurrently; excess is shed.
172191 const MAX_INFLIGHT : usize = 256 ;
173192
@@ -178,6 +197,11 @@ pub(crate) fn run_http_redirect(listener: TcpListener, ctx: HttpCtx) {
178197 Ok ( mut stream) => {
179198 common:: apply_timeouts ( & stream) ;
180199 stream. set_nodelay ( true ) . ok ( ) ;
200+ // Enforce the per-IP cap; `continue` drops the connection.
201+ let guard = match limiter. admit ( stream. peer_addr ( ) . ok ( ) . map ( |a| a. ip ( ) ) ) {
202+ Some ( g) => g,
203+ None => continue ,
204+ } ;
181205 // Shed load past the cap by closing the connection (drop).
182206 if inflight. fetch_add ( 1 , Ordering :: Relaxed ) >= MAX_INFLIGHT {
183207 inflight. fetch_sub ( 1 , Ordering :: Relaxed ) ;
@@ -186,6 +210,8 @@ pub(crate) fn run_http_redirect(listener: TcpListener, ctx: HttpCtx) {
186210 let ctx = Arc :: clone ( & ctx) ;
187211 let inflight = Arc :: clone ( & inflight) ;
188212 thread:: spawn ( move || {
213+ // Hold the per-IP slot until the redirect finishes.
214+ let _guard = guard;
189215 if let Err ( e) = redirect:: serve ( & mut stream, & ctx)
190216 && cfg ! ( debug_assertions)
191217 {
0 commit comments