File Coverage

feersum_core.c.inc
Criterion Covered Total %
statement 585 730 80.1
branch 255 384 66.4
condition n/a
subroutine n/a
pod n/a
total 840 1114 75.4


line stmt bran cond sub pod time code
1              
2             INLINE_UNLESS_DEBUG
3             static SV*
4 188           fetch_av_normal (pTHX_ AV *av, I32 i)
5             {
6 188           SV **elt = av_fetch(av, i, 0);
7 188 50         if (elt == NULL) return NULL;
8 188           SV *sv = *elt;
9 188 100         if (unlikely(SvMAGICAL(sv))) sv = sv_2mortal(newSVsv(sv));
10 188 100         if (unlikely(!SvOK(sv))) return NULL;
11             // usually array ref elems aren't RVs (for PSGI anyway)
12 185 100         if (unlikely(SvROK(sv))) sv = SvRV(sv);
13 185           return sv;
14             }
15              
16             INLINE_UNLESS_DEBUG
17             static struct iomatrix *
18 1033           next_iomatrix (struct feer_conn *c)
19             {
20 1033           bool add_iomatrix = 0;
21             struct iomatrix *m;
22              
23 1033 100         if (!c->wbuf_rinq) {
24             trace3("next_iomatrix(%d): head\n", c->fd);
25 792           add_iomatrix = 1;
26             }
27             else {
28             // get the tail-end struct
29 241           m = (struct iomatrix *)c->wbuf_rinq->prev->ref;
30             trace3("next_iomatrix(%d): tail, count=%d, offset=%d\n",
31             c->fd, m->count, m->offset);
32 241 50         if (m->count >= FEERSUM_IOMATRIX_SIZE) {
33 0           add_iomatrix = 1;
34             }
35             }
36              
37 1033 100         if (add_iomatrix) {
38             trace3("next_iomatrix(%d): alloc\n", c->fd);
39 792 100         IOMATRIX_ALLOC(m);
40 792           m->offset = m->count = 0;
41 792           rinq_push(&c->wbuf_rinq, m);
42             }
43              
44             trace3("next_iomatrix(%d): end, count=%d, offset=%d\n",
45             c->fd, m->count, m->offset);
46 1033           return m;
47             }
48              
49             INLINE_UNLESS_DEBUG
50             static STRLEN
51 338           add_sv_to_wbuf(struct feer_conn *c, SV *sv)
52             {
53 338           struct iomatrix *m = next_iomatrix(c);
54 338           unsigned idx = m->count++;
55             STRLEN cur;
56 338 100         if (unlikely(SvMAGICAL(sv))) {
57 1           sv = newSVsv(sv); // copy to force it to be normal.
58             }
59 337 50         else if (unlikely(SvPADTMP(sv))) {
60             // PADTMPs have their PVs re-used, so we can't simply keep a
61             // reference. TEMPs maybe behave in a similar way and are potentially
62             // stealable. If not stealing, we must make a copy.
63             #ifdef FEERSUM_STEAL
64 0 0         if (SvFLAGS(sv) == (SVs_PADTMP|SVf_POK|SVp_POK)) {
65             trace3("STEALING\n");
66 0           SV *thief = newSV(0);
67 0           sv_upgrade(thief, SVt_PV);
68              
69 0           SvPV_set(thief, SvPVX(sv));
70 0           SvLEN_set(thief, SvLEN(sv));
71 0           SvCUR_set(thief, SvCUR(sv));
72              
73             // make the temp null
74 0 0         (void)SvOK_off(sv);
75 0           SvPV_set(sv, NULL);
76 0           SvLEN_set(sv, 0);
77 0           SvCUR_set(sv, 0);
78              
79 0           SvFLAGS(thief) |= SVf_READONLY|SVf_POK|SVp_POK;
80              
81 0           sv = thief;
82             }
83             else {
84 0           sv = newSVsv(sv);
85             }
86             #else
87             sv = newSVsv(sv);
88             #endif
89             }
90             else {
91 337           sv = SvREFCNT_inc(sv);
92             }
93              
94 338           m->iov[idx].iov_base = SvPV(sv, cur);
95 338           m->iov[idx].iov_len = cur;
96 338           m->sv[idx] = sv;
97              
98 338           c->wbuf_len += cur;
99 338           return cur;
100             }
101              
102             INLINE_UNLESS_DEBUG
103             static STRLEN
104 103           add_const_to_wbuf(struct feer_conn *c, const char *str, size_t str_len)
105             {
106 103           struct iomatrix *m = next_iomatrix(c);
107 103           unsigned idx = m->count++;
108 103           m->iov[idx].iov_base = (void*)str;
109 103           m->iov[idx].iov_len = str_len;
110 103           m->sv[idx] = NULL;
111 103           c->wbuf_len += str_len;
112 103           return str_len;
113             }
114              
115             INLINE_UNLESS_DEBUG
116             static void
117 75           add_placeholder_to_wbuf(struct feer_conn *c, SV **sv, struct iovec **iov_ref)
118             {
119 75           struct iomatrix *m = next_iomatrix(c);
120 75           unsigned idx = m->count++;
121 75           *sv = newSV(31);
122 75           SvPOK_on(*sv);
123 75           m->sv[idx] = *sv;
124 75           *iov_ref = &m->iov[idx];
125 75           }
126              
127             INLINE_UNLESS_DEBUG
128             static void
129 60           finish_wbuf(struct feer_conn *c)
130             {
131 60 100         if (!c->use_chunked) return; // nothing required unless chunked encoding
132 40           add_const_to_wbuf(c, "0\r\n\r\n", 5); // terminating chunk
133             }
134              
135             INLINE_UNLESS_DEBUG
136             static void
137 75           update_wbuf_placeholder(struct feer_conn *c, SV *sv, struct iovec *iov)
138             {
139             STRLEN cur;
140             // can't pass iov_len for cur; incompatible pointer type on some systems:
141 75           iov->iov_base = SvPV(sv,cur);
142 75           iov->iov_len = cur;
143 75           c->wbuf_len += cur;
144 75           }
145              
146             static void
147 59           add_chunk_sv_to_wbuf(struct feer_conn *c, SV *sv)
148             {
149             STRLEN len;
150 59           (void)SvPV(sv, len);
151 59 50         if (unlikely(len == 0)) return; /* skip: "0\r\n\r\n" is the terminal chunk */
152              
153             SV *chunk;
154             struct iovec *chunk_iov;
155 59           add_placeholder_to_wbuf(c, &chunk, &chunk_iov);
156 59           STRLEN cur = add_sv_to_wbuf(c, sv);
157 59           add_crlf_to_wbuf(c);
158 59           sv_setpvf(chunk, "%"Sz_xf CRLF, (Sz)cur);
159 59           update_wbuf_placeholder(c, chunk, chunk_iov);
160             }
161              
162             static const char *
163 215           http_code_to_msg (int code) {
164 215           switch (code) {
165 0           case 100: return "Continue";
166 0           case 101: return "Switching Protocols";
167 0           case 102: return "Processing"; // RFC 2518
168 0           case 200: return "OK";
169 0           case 201: return "Created";
170 0           case 202: return "Accepted";
171 0           case 203: return "Non Authoritative Information";
172 0           case 204: return "No Content";
173 0           case 205: return "Reset Content";
174 0           case 206: return "Partial Content";
175 0           case 207: return "Multi-Status"; // RFC 4918 (WebDav)
176 0           case 300: return "Multiple Choices";
177 0           case 301: return "Moved Permanently";
178 0           case 302: return "Found";
179 0           case 303: return "See Other";
180 0           case 304: return "Not Modified";
181 0           case 305: return "Use Proxy";
182 0           case 307: return "Temporary Redirect";
183 158           case 400: return "Bad Request";
184 0           case 401: return "Unauthorized";
185 0           case 402: return "Payment Required";
186 0           case 403: return "Forbidden";
187 0           case 404: return "Not Found";
188 5           case 405: return "Method Not Allowed";
189 0           case 406: return "Not Acceptable";
190 0           case 407: return "Proxy Authentication Required";
191 9           case 408: return "Request Timeout";
192 0           case 409: return "Conflict";
193 0           case 410: return "Gone";
194 4           case 411: return "Length Required";
195 0           case 412: return "Precondition Failed";
196 4           case 413: return "Request Entity Too Large";
197 3           case 414: return "Request URI Too Long";
198 0           case 415: return "Unsupported Media Type";
199 0           case 416: return "Requested Range Not Satisfiable";
200 9           case 417: return "Expectation Failed";
201 0           case 418: return "I'm a teapot";
202 0           case 421: return "Misdirected Request"; // RFC 9110
203 0           case 422: return "Unprocessable Entity"; // RFC 4918
204 0           case 423: return "Locked"; // RFC 4918
205 0           case 424: return "Failed Dependency"; // RFC 4918
206 0           case 425: return "Unordered Collection"; // RFC 3648
207 0           case 426: return "Upgrade Required"; // RFC 2817
208 0           case 429: return "Too Many Requests"; // RFC 6585
209 2           case 431: return "Request Header Fields Too Large"; // RFC 6585
210 0           case 449: return "Retry With"; // Microsoft
211 0           case 450: return "Blocked by Parental Controls"; // Microsoft
212 15           case 500: return "Internal Server Error";
213 6           case 501: return "Not Implemented";
214 0           case 502: return "Bad Gateway";
215 0           case 503: return "Service Unavailable";
216 0           case 504: return "Gateway Timeout";
217 0           case 505: return "HTTP Version Not Supported";
218 0           case 506: return "Variant Also Negotiates"; // RFC 2295
219 0           case 507: return "Insufficient Storage"; // RFC 4918
220 0           case 509: return "Bandwidth Limit Exceeded"; // Apache mod
221 0           case 510: return "Not Extended"; // RFC 2774
222 0           case 530: return "User access denied"; // ??
223 0           default: break;
224             }
225              
226             // default to the Nxx group names in RFC 2616
227 0           switch (code / 100) {
228 0           case 1: return "Informational";
229 0           case 2: return "Success";
230 0           case 3: return "Redirection";
231 0           case 4: return "Client Error";
232 0           default: return "Error";
233             }
234             }
235              
236             static int
237 662           prep_socket(int fd, int is_tcp)
238             {
239             #ifdef HAS_ACCEPT4
240 662           int flags = 1;
241             #else
242             int flags;
243              
244             // make it non-blocking and close-on-exec
245             flags = fcntl(fd, F_GETFL);
246             if (unlikely(flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0))
247             return -1;
248             if (unlikely(fcntl(fd, F_SETFD, FD_CLOEXEC) < 0))
249             return -1;
250              
251             flags = 1;
252             #endif
253 662 50         if (likely(is_tcp)) {
254 662 50         if (unlikely(setsockopt(fd, SOL_TCP, TCP_NODELAY, &flags, sizeof(int))))
255 0           return -1;
256             }
257              
258 662           return 0;
259             }
260              
261             // TCP cork/uncork for batching writes (Linux: TCP_CORK, BSD: TCP_NOPUSH)
262             #if defined(TCP_CORK)
263             # define FEERSUM_TCP_CORK TCP_CORK
264             #elif defined(TCP_NOPUSH)
265             # define FEERSUM_TCP_CORK TCP_NOPUSH
266             #endif
267              
268             #ifdef FEERSUM_TCP_CORK
269             INLINE_UNLESS_DEBUG static void
270 6           set_cork(struct feer_conn *c, int cork)
271             {
272 6 50         if (likely(c->cached_is_tcp)) {
273 6           setsockopt(c->fd, SOL_TCP, FEERSUM_TCP_CORK, &cork, sizeof(cork));
274             }
275 6           }
276             #else
277             # define set_cork(c, cork) ((void)0)
278             #endif
279              
280             static void
281 8           invoke_shutdown_cb(pTHX_ struct feer_server *server)
282             {
283 8           dSP;
284 8           SV *cb = server->shutdown_cb_cv;
285 8           server->shutdown_cb_cv = NULL;
286 8           ENTER;
287 8           SAVETMPS;
288 8 50         PUSHMARK(SP);
289 8           PUTBACK;
290 8           call_sv(cb, G_EVAL|G_VOID|G_DISCARD|G_NOARGS);
291 8           SPAGAIN;
292             trace3("called shutdown handler\n");
293 8 50         if (SvTRUE(ERRSV))
    100          
294 1 50         sv_setsv(ERRSV, &PL_sv_undef);
295 8           SvREFCNT_dec(cb);
296 8 50         FREETMPS;
297 8           LEAVE;
298 8           }
299              
300             static void
301 1276           safe_close_conn(struct feer_conn *c, const char *where)
302             {
303 1276 100         if (unlikely(c->fd < 0))
304 614           return;
305              
306             #ifdef FEERSUM_HAS_H2
307             if (c->is_h2_stream) {
308             /* Pseudo-conns share parent's fd - do NOT close or shutdown it.
309             * The parent connection owns the fd and will close it. */
310             c->fd = -1;
311             return;
312             }
313             #endif
314              
315 662 50         CLOSE_SENDFILE_FD(c);
    0          
316              
317             #ifdef FEERSUM_HAS_TLS
318             // Best-effort TLS close_notify before TCP shutdown
319 662 100         if (c->tls && c->tls_handshake_done) {
    100          
320             ptls_buffer_t closebuf;
321 51           ptls_buffer_init(&closebuf, "", 0);
322 51           ptls_send_alert(c->tls, &closebuf, PTLS_ALERT_LEVEL_WARNING, PTLS_ALERT_CLOSE_NOTIFY);
323 51 50         if (closebuf.off > 0) {
324             ssize_t wr PERL_UNUSED_DECL;
325 51           wr = write(c->fd, closebuf.base, closebuf.off);
326             }
327 51           ptls_buffer_dispose(&closebuf);
328             }
329             #endif
330              
331             // Graceful TCP shutdown: send FIN to peer before close
332             // This ensures client sees clean EOF instead of RST
333 662           shutdown(c->fd, SHUT_WR);
334              
335 662 50         if (unlikely(close(c->fd) < 0))
336 0           trouble("close(%s) fd=%d: %s\n", where, c->fd, strerror(errno));
337              
338 662           c->fd = -1;
339             }
340              
341             static struct feer_conn *
342 662           new_feer_conn (EV_P_ int conn_fd, struct sockaddr *sa, socklen_t sa_len,
343             struct feer_server *srvr, struct feer_listen *lsnr)
344             {
345 662           SV *self = newSV(0);
346 662 50         SvUPGRADE(self, SVt_PVMG); // ensures sv_bless doesn't reallocate
347 662 50         SvGROW(self, sizeof(struct feer_conn));
    50          
348 662           SvPOK_only(self);
349 662           SvIOK_on(self);
350 662           SvIV_set(self,conn_fd);
351              
352 662           struct feer_conn *c = (struct feer_conn *)SvPVX(self);
353 662           Zero(c, 1, struct feer_conn);
354              
355 662           c->self = self;
356 662           c->server = srvr;
357 662           c->listener = lsnr;
358 662           SvREFCNT_inc_void_NN(srvr->self); // prevent server GC while conn alive
359              
360             // Cache hot config fields to avoid c->server->/c->listener-> indirection
361 662           c->cached_read_timeout = srvr->read_timeout;
362 662           c->cached_write_timeout = srvr->write_timeout;
363 662           c->cached_max_conn_reqs = srvr->max_connection_reqs;
364 662           c->cached_is_tcp = lsnr->is_tcp;
365 662           c->cached_keepalive_default = srvr->is_keepalive;
366 662           c->cached_use_reverse_proxy = srvr->use_reverse_proxy;
367 662           c->cached_request_cb_is_psgi = srvr->request_cb_is_psgi;
368 662           c->cached_max_read_buf = srvr->max_read_buf;
369 662           c->cached_max_body_len = srvr->max_body_len;
370 662           c->cached_max_uri_len = srvr->max_uri_len;
371 662           c->cached_wbuf_low_water = srvr->wbuf_low_water;
372 662           c->fd = conn_fd;
373 662           memcpy(&c->sa, sa, sa_len); // copy into embedded storage
374 662 100         c->receiving = srvr->use_proxy_protocol ? RECEIVE_PROXY_HEADER : RECEIVE_HEADERS;
375 662           c->sendfile_fd = -1; // no sendfile pending
376              
377             #ifdef FEERSUM_HAS_TLS
378 662 100         if (lsnr->tls_ctx_ref) {
379 68           ev_io_init(&c->read_ev_io, try_tls_conn_read, conn_fd, EV_READ);
380 68           ev_io_init(&c->write_ev_io, try_tls_conn_write, conn_fd, EV_WRITE);
381 68           feer_tls_init_conn(c, lsnr->tls_ctx_ref);
382             } else
383             #endif
384             {
385 594           ev_io_init(&c->read_ev_io, try_conn_read, conn_fd, EV_READ);
386 594           ev_io_init(&c->write_ev_io, try_conn_write, conn_fd, EV_WRITE);
387             }
388 662           ev_set_priority(&c->read_ev_io, srvr->read_priority);
389 662           c->read_ev_io.data = (void *)c;
390              
391 662           ev_set_priority(&c->write_ev_io, srvr->write_priority);
392 662           c->write_ev_io.data = (void *)c;
393              
394 662           ev_init(&c->read_ev_timer, conn_read_timeout);
395 662           ev_set_priority(&c->read_ev_timer, srvr->read_priority);
396 662           c->read_ev_timer.data = (void *)c;
397              
398             // Slowloris protection: header deadline timer (non-resetting)
399 662           ev_init(&c->header_ev_timer, conn_header_timeout);
400 662           ev_set_priority(&c->header_ev_timer, srvr->read_priority);
401 662           c->header_ev_timer.data = (void *)c;
402              
403 662           ev_init(&c->write_ev_timer, conn_write_timeout);
404 662           ev_set_priority(&c->write_ev_timer, srvr->write_priority);
405 662           c->write_ev_timer.data = (void *)c;
406              
407             trace3("made conn fd=%d self=%p, c=%p, cur=%"Sz_uf", len=%"Sz_uf"\n",
408             c->fd, self, c, (Sz)SvCUR(self), (Sz)SvLEN(self));
409              
410             if (FEERSUM_CONN_NEW_ENABLED()) {
411             feersum_set_conn_remote_info(aTHX_ c);
412             FEERSUM_CONN_NEW(c->fd, SvPV_nolen(c->remote_addr), (int)SvIV(c->remote_port));
413             }
414              
415 662           SV *rv = newRV_inc(c->self);
416 662           sv_bless(rv, feer_conn_stash); // so DESTROY can get called on read errors
417 662           SvREFCNT_dec(rv);
418              
419 662           SvREADONLY_on(self);
420 662           srvr->active_conns++;
421 662           return c;
422             }
423              
424             INLINE_UNLESS_DEBUG
425             static struct feer_conn *
426 1544           sv_2feer_conn (SV *rv)
427             {
428 1544 50         if (unlikely(!sv_isa(rv,"Feersum::Connection")))
429 0           croak("object is not of type Feersum::Connection");
430 1544           return (struct feer_conn *)SvPVX(SvRV(rv));
431             }
432              
433             INLINE_UNLESS_DEBUG
434             static SV*
435 569           feer_conn_2sv (struct feer_conn *c)
436             {
437 569           return newRV_inc(c->self);
438             }
439              
440             static feer_conn_handle *
441 679           sv_2feer_conn_handle (SV *rv, bool can_croak)
442             {
443             trace3("sv 2 conn_handle\n");
444 679 50         if (unlikely(!SvROK(rv))) croak("Expected a reference");
445             // do not allow subclassing
446 679           SV *sv = SvRV(rv);
447 679 50         if (likely(
    100          
    50          
    50          
448             sv_isobject(rv) &&
449             (SvSTASH(sv) == feer_conn_writer_stash ||
450             SvSTASH(sv) == feer_conn_reader_stash)
451             )) {
452 679           UV uv = SvUV(sv);
453 679 100         if (uv == 0) {
454 88 100         if (can_croak) croak("Operation not allowed: Handle is closed.");
455 73           return NULL;
456             }
457 591           return INT2PTR(feer_conn_handle*,uv);
458             }
459              
460 0 0         if (can_croak)
461 0           croak("Expected a Feersum::Connection::Writer or ::Reader object");
462 0           return NULL;
463             }
464              
465             static SV *
466 395           new_feer_conn_handle (pTHX_ struct feer_conn *c, bool is_writer)
467             {
468             SV *sv;
469 395           SvREFCNT_inc_void_NN(c->self);
470 395           sv = newRV_noinc(newSVuv(PTR2UV(c)));
471 395 100         sv_bless(sv, is_writer ? feer_conn_writer_stash : feer_conn_reader_stash);
472 395           return sv;
473             }
474              
475             static void
476 220           init_feer_server (struct feer_server *s)
477             {
478 220           Zero(s, 1, struct feer_server);
479 220           s->read_timeout = READ_TIMEOUT;
480 220           s->header_timeout = HEADER_TIMEOUT;
481 220           s->write_timeout = WRITE_TIMEOUT;
482 220           s->max_accept_per_loop = DEFAULT_MAX_ACCEPT_PER_LOOP;
483 220           s->max_connections = 10000;
484 220           s->max_read_buf = MAX_READ_BUF;
485 220           s->max_body_len = MAX_BODY_LEN;
486 220           s->max_uri_len = MAX_URI_LEN;
487 220           s->psgix_io = true;
488             #ifdef FEERSUM_HAS_H2
489             s->max_h2_concurrent_streams = FEER_H2_MAX_CONCURRENT_STREAMS;
490             #endif
491 3740 100         for (int i = 0; i < FEER_MAX_LISTENERS; i++) {
492 3520           s->listeners[i].fd = -1;
493 3520           s->listeners[i].server = s;
494             #ifdef __linux__
495 3520           s->listeners[i].epoll_fd = -1;
496             #endif
497             }
498 220           }
499              
500             static struct feer_server *
501 220           new_feer_server (pTHX)
502             {
503 220           SV *self = newSV(0);
504 220 50         SvUPGRADE(self, SVt_PVMG);
505 220 50         SvGROW(self, sizeof(struct feer_server));
    50          
506 220           SvPOK_only(self);
507 220           SvIOK_on(self);
508              
509 220           struct feer_server *s = (struct feer_server *)SvPVX(self);
510 220           init_feer_server(s);
511 220           s->self = self;
512              
513 220           SV *rv = newRV_inc(self);
514 220           sv_bless(rv, feer_stash);
515 220           SvREFCNT_dec(rv);
516              
517 220           SvREADONLY_on(self);
518 220           return s;
519             }
520              
521             INLINE_UNLESS_DEBUG
522             static struct feer_server *
523 27522           sv_2feer_server (SV *rv)
524             {
525             // Accept both instance ($obj->method) and class (Feersum->method) calls
526 27522 100         if (sv_isa(rv, "Feersum")) {
527 27489           return (struct feer_server *)SvPVX(SvRV(rv));
528             }
529             // Class method call: "Feersum"->method() - return default server
530 33 50         if (SvPOK(rv) && strEQ(SvPV_nolen(rv), "Feersum")) {
    50          
531 33 50         if (unlikely(!default_server))
532 0           croak("Feersum: no server instance (call Feersum->new first)");
533 33           return default_server;
534             }
535 0           croak("object is not of type Feersum");
536             return NULL; // unreachable
537             }
538              
539             INLINE_UNLESS_DEBUG
540             static SV*
541 207           feer_server_2sv (struct feer_server *s)
542             {
543 207           return newRV_inc(s->self);
544             }
545              
546             INLINE_UNLESS_DEBUG static void
547 157           start_read_watcher(struct feer_conn *c) {
548             ASSERT_EV_LOOP_INITIALIZED();
549 157 100         if (unlikely(ev_is_active(&c->read_ev_io)))
550 12           return;
551             trace("start read watcher %d\n",c->fd);
552 145           ev_io_start(feersum_ev_loop, &c->read_ev_io);
553 145           SvREFCNT_inc_void_NN(c->self);
554             }
555              
556             INLINE_UNLESS_DEBUG static void
557 1024           stop_read_watcher(struct feer_conn *c) {
558 1024 100         if (unlikely(!ev_is_active(&c->read_ev_io)))
559 889           return;
560             trace("stop read watcher %d\n",c->fd);
561 135           ev_io_stop(feersum_ev_loop, &c->read_ev_io);
562 135           SvREFCNT_dec(c->self);
563             }
564              
565             INLINE_UNLESS_DEBUG static void
566 141           restart_read_timer(struct feer_conn *c) {
567 141 100         if (likely(!ev_is_active(&c->read_ev_timer))) {
568             trace("restart read timer %d\n",c->fd);
569 120           c->read_ev_timer.repeat = c->cached_read_timeout;
570 120           SvREFCNT_inc_void_NN(c->self);
571             }
572 141           ev_timer_again(feersum_ev_loop, &c->read_ev_timer);
573 141           }
574              
575             INLINE_UNLESS_DEBUG static void
576 1033           stop_read_timer(struct feer_conn *c) {
577 1033 100         if (unlikely(!ev_is_active(&c->read_ev_timer)))
578 918           return;
579             trace("stop read timer %d\n",c->fd);
580 115           ev_timer_stop(feersum_ev_loop, &c->read_ev_timer);
581 115           SvREFCNT_dec(c->self);
582             }
583              
584             INLINE_UNLESS_DEBUG static void
585 653           start_write_watcher(struct feer_conn *c) {
586             ASSERT_EV_LOOP_INITIALIZED();
587 653 100         if (unlikely(ev_is_active(&c->write_ev_io)))
588 122           return;
589             trace("start write watcher %d\n",c->fd);
590 531           ev_io_start(feersum_ev_loop, &c->write_ev_io);
591 531           SvREFCNT_inc_void_NN(c->self);
592             }
593              
594             INLINE_UNLESS_DEBUG static void
595 821           stop_write_watcher(struct feer_conn *c) {
596 821 100         if (unlikely(!ev_is_active(&c->write_ev_io)))
597 290           return;
598             trace("stop write watcher %d\n",c->fd);
599 531           ev_io_stop(feersum_ev_loop, &c->write_ev_io);
600 531           SvREFCNT_dec(c->self);
601             }
602              
603             INLINE_UNLESS_DEBUG static void
604 789           restart_write_timer(struct feer_conn *c) {
605 789 100         if (c->cached_write_timeout <= 0.0) return;
606 1 50         if (likely(!ev_is_active(&c->write_ev_timer))) {
607             trace("restart write timer %d\n",c->fd);
608 1           c->write_ev_timer.repeat = c->cached_write_timeout;
609 1           SvREFCNT_inc_void_NN(c->self);
610             }
611 1           ev_timer_again(feersum_ev_loop, &c->write_ev_timer);
612             }
613              
614             INLINE_UNLESS_DEBUG static void
615 842           stop_write_timer(struct feer_conn *c) {
616 842 100         if (unlikely(!ev_is_active(&c->write_ev_timer)))
617 841           return;
618             trace("stop write timer %d\n",c->fd);
619 1           ev_timer_stop(feersum_ev_loop, &c->write_ev_timer);
620 1           SvREFCNT_dec(c->self);
621             }
622              
623             INLINE_UNLESS_DEBUG static void
624 1791           stop_header_timer(struct feer_conn *c) {
625 1791 100         if (unlikely(!ev_is_active(&c->header_ev_timer)))
626 1003           return;
627             trace("stop header timer %d\n", c->fd);
628 788           ev_timer_stop(feersum_ev_loop, &c->header_ev_timer);
629 788           SvREFCNT_dec(c->self);
630             }
631              
632             INLINE_UNLESS_DEBUG static void
633 145           restart_header_timer(struct feer_conn *c) {
634 145           double timeout = c->server->header_timeout;
635 145 50         if (timeout <= 0.0) return;
636 145           stop_header_timer(c);
637 145           ev_timer_set(&c->header_ev_timer, timeout, 0.0);
638 145           ev_timer_start(feersum_ev_loop, &c->header_ev_timer);
639 145           SvREFCNT_inc_void_NN(c->self);
640             }
641              
642             INLINE_UNLESS_DEBUG static void
643 53           stop_all_watchers(struct feer_conn *c) {
644 53           stop_read_watcher(c);
645 53           stop_read_timer(c);
646 53           stop_header_timer(c);
647 53           stop_write_watcher(c);
648 53           stop_write_timer(c);
649 53           }
650              
651             /* Common shutdown sequence: stop watchers, close fd, mark for cleanup.
652             * Used by error/EOF/timeout paths across H1, H2, and TLS code. */
653             INLINE_UNLESS_DEBUG static void
654 13           feer_shutdown_conn(struct feer_conn *c, const char *where) {
655 13           stop_all_watchers(c);
656 13           safe_close_conn(c, where);
657 13           change_responding_state(c, RESPOND_SHUTDOWN);
658 13           }
659              
660             static void
661 1765           feer_conn_set_busy(struct feer_conn *c)
662             {
663 1765 100         if (c->idle_rinq_node) {
664 34           rinq_remove(&c->server->idle_keepalive_rinq, c->idle_rinq_node);
665 34           c->idle_rinq_node = NULL;
666             }
667 1765           }
668              
669             static void
670 36           feer_conn_set_idle(struct feer_conn *c)
671             {
672 36 50         if (c->idle_rinq_node) return; // already idle
673              
674 36           c->idle_rinq_node = rinq_push(&c->server->idle_keepalive_rinq, c);
675             trace("conn fd=%d is now idle (added to MRU)\n", c->fd);
676             }
677              
678             static int
679 11           feer_server_recycle_idle_conn(struct feer_server *srvr)
680             {
681 11 100         if (!srvr->idle_keepalive_rinq) return 0;
682              
683 2           struct feer_conn *c = (struct feer_conn *)rinq_shift(&srvr->idle_keepalive_rinq);
684 2 50         if (unlikely(!c)) return 0;
685              
686 2           c->idle_rinq_node = NULL; // node was shifted
687              
688             trace("recycling idle keepalive conn fd=%d to make room for new accept\n", c->fd);
689              
690             // Gracefully shut down the idle connection.
691             // Guard: after setup_accepted_conn drops the base refcount, connections
692             // are alive only via watcher refcounts. stop_all_watchers can drop
693             // refcount to 0 -> DESTROY fires before safe_close_conn.
694 2           SvREFCNT_inc_void_NN(c->self);
695             #ifdef FEERSUM_HAS_H2
696             /* For an idle HTTP/2 connection, tell the peer we are going away before
697             * closing so it does not see an abrupt FIN (RFC 9113 6.8); mirrors the
698             * H2 idle-timeout path. Covers both graceful_shutdown draining and
699             * max_connections eviction. */
700             if (c->h2_session && !c->h2_goaway_sent) {
701             nghttp2_submit_goaway(c->h2_session, NGHTTP2_FLAG_NONE,
702             nghttp2_session_get_last_proc_stream_id(c->h2_session),
703             NGHTTP2_NO_ERROR, NULL, 0);
704             c->h2_goaway_sent = 1;
705             feer_h2_session_send(c);
706             }
707             #endif
708 2           feer_shutdown_conn(c, "recycled for new connection");
709 2           SvREFCNT_dec(c->self);
710              
711             // active_conns will be decremented in DESTROY when refcount drops.
712 2           return 1;
713             }
714              
715              
716             static void
717 390           process_request_ready_rinq (struct feer_server *server)
718             {
719 943 100         while (server->request_ready_rinq) {
720             struct feer_conn *c =
721 553           (struct feer_conn *)rinq_shift(&server->request_ready_rinq);
722 553 50         if (unlikely(!c)) break;
723              
724 553           call_request_callback(c);
725              
726 553 100         if (likely(c->wbuf_rinq)) {
727             // this was deferred until after the perl callback
728 525           conn_write_ready(c);
729             }
730             #ifdef FEERSUM_HAS_H2
731             else if (c->is_h2_stream) {
732             // H2 pseudo-conns don't use wbuf_rinq; flush deferred session_send
733             dTHX;
734             struct feer_h2_stream *stream = H2_STREAM_FROM_PC(c);
735             if (stream && stream->parent)
736             h2_session_send_and_poll(aTHX_ stream->parent);
737             }
738             #endif
739 553           SvREFCNT_dec(c->self); // for the rinq
740             }
741 390           }
742              
743             static void
744 163           prepare_cb (EV_P_ ev_prepare *w, int revents)
745             {
746 163           struct feer_server *srvr = (struct feer_server *)w->data;
747              
748 163 50         if (unlikely(revents & EV_ERROR)) {
749 0           trouble("EV error in prepare, revents=0x%08x\n", revents);
750 0           ev_break(EV_A, EVBREAK_ALL);
751 0           return;
752             }
753              
754 163 100         if (!srvr->shutting_down) {
755 408 100         for (int i = 0; i < srvr->n_listeners; i++) {
756 247           struct feer_listen *lsnr = &srvr->listeners[i];
757 247 100         if (!ev_is_active(&lsnr->accept_w) && !lsnr->pause_flags) {
    50          
758 169           ev_io_start(EV_A, &lsnr->accept_w);
759             }
760             }
761             }
762 163           ev_prepare_stop(EV_A, w);
763             }
764              
765             static void
766 5164           check_cb (EV_P_ ev_check *w, int revents)
767             {
768 5164           struct feer_server *server = (struct feer_server *)w->data;
769              
770 5164 50         if (unlikely(revents & EV_ERROR)) {
771 0           trouble("EV error in check, revents=0x%08x\n", revents);
772 0           ev_break(EV_A, EVBREAK_ALL);
773 0           return;
774             }
775              
776             trace3("check! head=%p\n", server->request_ready_rinq);
777 5164 100         if (server->request_ready_rinq)
778 390           process_request_ready_rinq(server);
779             }
780              
781             static void
782 386           idle_cb (EV_P_ ev_idle *w, int revents)
783             {
784 386           struct feer_server *server = (struct feer_server *)w->data;
785              
786             trace("idle_cb called, revents=0x%08x\n", revents);
787 386 50         if (unlikely(revents & EV_ERROR)) {
788 0           trouble("EV error in idle, revents=0x%08x\n", revents);
789 0           ev_break(EV_A, EVBREAK_ALL);
790 0           return;
791             }
792             trace3("idle! head=%p\n", server->request_ready_rinq);
793 386 50         if (server->request_ready_rinq)
794 0           process_request_ready_rinq(server);
795 386           ev_idle_stop(EV_A, w);
796             }
797              
798             /*
799             * Shared keepalive-or-close logic for both plain and TLS write paths.
800             * read_cb is try_conn_read (plain) or try_tls_conn_read (TLS).
801             */
802             static void
803 732           handle_keepalive_or_close(struct feer_conn *c, conn_read_cb_t read_cb)
804             {
805 732           stop_write_watcher(c);
806 732           stop_write_timer(c);
807              
808             if (FEERSUM_RESP_DONE_ENABLED()) {
809             FEERSUM_RESP_DONE(c->fd, 0);
810             }
811              
812             /* If the request had a body the app didn't fully consume, strip the
813             * remaining body bytes from rbuf so they don't get parsed as the next
814             * HTTP request. Any pipelined data after the body is preserved.
815             *
816             * For Content-Length bodies: rbuf starts with body data (headers already
817             * stripped by picohttpparser). consumed = bytes the app read via
818             * $input->read (which does sv_chop). remaining = body bytes still at
819             * the start of rbuf.
820             *
821             * For chunked bodies: the chunked parser decoded in-place. rbuf has
822             * decoded body (received_cl bytes) followed by any pipelined data.
823             * consumed works the same way (sv_chop by $input->read). */
824 732 100         if (c->is_keepalive && c->rbuf) {
    100          
825             /* expected_cl is set by both Content-Length and chunked paths
826             * (try_parse_chunked sets expected_cl = decoded body size). */
827 136           ssize_t body_total = (c->expected_cl > 0) ? c->expected_cl : 0;
828 136 100         if (body_total > 0) {
829 14           ssize_t consumed = c->received_cl - (ssize_t)SvCUR(c->rbuf);
830 14           ssize_t remaining = body_total - consumed;
831 14 100         if (remaining > 0 && remaining <= (ssize_t)SvCUR(c->rbuf)) {
    50          
832 5           sv_chop(c->rbuf, SvPVX(c->rbuf) + remaining);
833             trace("drained body fd=%d stripped=%"Ssz_df" pipeline=%"Sz_uf"\n",
834             c->fd, (Ssz)remaining, (Sz)SvCUR(c->rbuf));
835             }
836             }
837             }
838              
839 732 100         if (c->is_keepalive) {
840 142           change_responding_state(c, RESPOND_NOT_STARTED);
841 142           change_receiving_state(c, RECEIVE_WAIT);
842 142           STRLEN pipelined = 0;
843 142 100         if (c->rbuf) { pipelined = SvCUR(c->rbuf); }
844 142 50         if (likely(c->req)) {
845 172 100         if (likely(pipelined == 0) && c->req->buf && c->rbuf) {
    50          
    100          
846 30           SV *tmp = c->rbuf;
847 30           c->rbuf = c->req->buf;
848 30           c->req->buf = NULL;
849 30           SvCUR_set(c->rbuf, 0);
850             // Shrink oversized buffers from large bodies to avoid
851             // holding max_body_len per keepalive connection
852 30 50         if (SvLEN(c->rbuf) > READ_BUFSZ * 4)
853 0           SvPV_renew(c->rbuf, READ_BUFSZ + 1);
854 30           SvREFCNT_dec(tmp);
855 112 50         } else if (c->req->buf) {
856 112           SvREFCNT_dec(c->req->buf);
857 112           c->req->buf = NULL;
858             }
859 142           free_request(c);
860             }
861 142 100         if (unlikely(pipelined > 0 && c->is_http11)) {
    50          
862 106           c->pipelined = pipelined;
863 106 50         if (c->pipeline_depth <= MAX_PIPELINE_DEPTH) {
864 106           c->pipeline_depth++;
865 106           restart_header_timer(c);
866 106           read_cb(feersum_ev_loop, &c->read_ev_io, 0);
867 106           c->pipeline_depth--;
868 106           return;
869             }
870             trace("pipeline depth limit reached on %d\n", c->fd);
871             } else {
872 36           c->pipelined = 0;
873             }
874 36           start_read_watcher(c);
875 36           restart_read_timer(c);
876 36           restart_header_timer(c);
877 36           feer_conn_set_idle(c);
878             } else {
879 590 50         if (c->responding != RESPOND_SHUTDOWN)
880 0           change_responding_state(c, RESPOND_SHUTDOWN);
881 590           safe_close_conn(c, "close at write shutdown");
882             }
883             }
884              
885             static void
886 732           try_conn_write(EV_P_ struct ev_io *w, int revents)
887             {
888 732           dCONN;
889             unsigned i;
890             struct iomatrix *m;
891              
892 732           SvREFCNT_inc_void_NN(c->self);
893              
894             // if it's marked writeable EV suggests we simply try write to it.
895             // Otherwise it is stopped and we should ditch this connection.
896 732 50         if (unlikely(revents & EV_ERROR && !(revents & EV_WRITE))) {
    0          
897             trace("EV error on write, fd=%d revents=0x%08x\n", w->fd, revents);
898 0           change_responding_state(c, RESPOND_SHUTDOWN);
899 0           goto try_write_finished;
900             }
901              
902 732 100         if (unlikely(!c->wbuf_rinq)) {
903 23 100         if (unlikely(c->responding >= RESPOND_SHUTDOWN))
904 3           goto try_write_finished;
905              
906             #ifdef __linux__
907             // Check for sendfile pending (headers already sent)
908 20 50         if (c->sendfile_fd >= 0)
909 0           goto try_sendfile;
910             #endif
911              
912 20 50         if (!c->poll_write_cb) {
913             // no callback and no data: wait for app to push to us.
914 0 0         if (c->responding == RESPOND_STREAMING)
915 0           goto try_write_paused;
916              
917             trace("tried to write with an empty buffer %d resp=%d\n",w->fd,c->responding);
918 0           change_responding_state(c, RESPOND_SHUTDOWN);
919 0           goto try_write_finished;
920             }
921              
922 20 100         if (c->poll_write_cb_is_io_handle)
923 11           pump_io_handle(c);
924             else
925 9           call_poll_callback(c, 1);
926              
927             // callback didn't write anything:
928 20 50         if (unlikely(!c->wbuf_rinq)) goto try_write_again;
929             }
930             // Low-water-mark: buffer not empty but below threshold - refill before writing
931 709 100         else if (c->cached_wbuf_low_water > 0
932 1 50         && c->wbuf_len <= c->cached_wbuf_low_water
933 1 50         && c->responding == RESPOND_STREAMING && c->poll_write_cb) {
    50          
934 1 50         if (c->poll_write_cb_is_io_handle)
935 0           pump_io_handle(c);
936             else
937 1           call_poll_callback(c, 1);
938             }
939              
940 729           try_write_again_immediately:
941             #if defined(__linux__) && defined(FEERSUM_TCP_CORK)
942             // Cork socket when writing headers before sendfile for optimal packet framing
943 729 100         if (c->sendfile_fd >= 0)
944 3           set_cork(c, 1);
945             #endif
946 729           m = (struct iomatrix *)c->wbuf_rinq->ref;
947             #if DEBUG >= 2
948             warn("going to write to %d:\n",c->fd);
949             for (i=0; i < m->count; i++) {
950             fprintf(stderr,"%.*s",
951             (int)m->iov[i].iov_len, (char*)m->iov[i].iov_base);
952             }
953             #endif
954              
955             trace("going to write %d off=%d count=%d\n", w->fd, m->offset, m->count);
956 729           errno = 0;
957 729           int iov_count = m->count - m->offset;
958             #ifdef IOV_MAX
959 729 50         if (iov_count > IOV_MAX) iov_count = IOV_MAX;
960             #endif
961             ssize_t wrote;
962 729 100         if (iov_count == 1) {
963             // Single element: write() is slightly faster than writev()
964 652           wrote = write(w->fd, m->iov[m->offset].iov_base, m->iov[m->offset].iov_len);
965             } else {
966 77           wrote = writev(w->fd, &m->iov[m->offset], iov_count);
967             }
968             trace("wrote %"Ssz_df" bytes to %d, errno=%d\n", (Ssz)wrote, w->fd, errno);
969              
970 729 50         if (unlikely(wrote <= 0)) {
971 0 0         if (likely(errno == EAGAIN || errno == EINTR))
    0          
972 0           goto try_write_again;
973 0           trouble("try_conn_write fd=%d: %s\n", w->fd, strerror(errno));
974 0 0         CLOSE_SENDFILE_FD(c);
    0          
975 0           set_cork(c, 0);
976 0           stop_write_timer(c);
977 0           change_responding_state(c, RESPOND_SHUTDOWN);
978 0           goto try_write_finished;
979             }
980              
981 729           c->wbuf_len -= wrote;
982 729           restart_write_timer(c);
983              
984 729           bool consume = 1;
985 1697 100         for (i = m->offset; i < m->count && consume; i++) {
    50          
986 968           struct iovec *v = &m->iov[i];
987 968 50         if (unlikely(v->iov_len > wrote)) {
988             trace3("offset vector %d base=%p len=%"Sz_uf"\n",
989             w->fd, v->iov_base, (Sz)v->iov_len);
990 0           v->iov_base = (char*)v->iov_base + wrote;
991 0           v->iov_len -= wrote;
992             // don't consume any more:
993 0           consume = 0;
994             }
995             else {
996             trace3("consume vector %d base=%p len=%"Sz_uf" sv=%p\n",
997             w->fd, v->iov_base, (Sz)v->iov_len, m->sv[i]);
998 968           wrote -= v->iov_len;
999 968           m->offset++;
1000 968 100         if (m->sv[i]) {
1001 865           SvREFCNT_dec(m->sv[i]);
1002 865           m->sv[i] = NULL;
1003             }
1004             }
1005             }
1006              
1007 729 50         if (likely(m->offset >= m->count)) {
1008             trace2("all done with iomatrix %d state=%d\n",w->fd,c->responding);
1009 729           rinq_shift(&c->wbuf_rinq);
1010 729 50         IOMATRIX_FREE(m);
1011 729 50         if (!c->wbuf_rinq) {
1012             #ifdef __linux__
1013             // sendfile pending? do zero-copy file transfer
1014 729 100         if (c->sendfile_fd >= 0)
1015 3           goto try_sendfile;
1016             #endif
1017 726           goto try_write_finished;
1018             }
1019             // Low-water-mark: yield to event loop so poll_cb can fire
1020 0 0         if (c->cached_wbuf_low_water > 0
1021 0 0         && c->wbuf_len <= c->cached_wbuf_low_water
1022 0 0         && c->responding == RESPOND_STREAMING && c->poll_write_cb) {
    0          
1023 0           goto try_write_again;
1024             }
1025             trace2("write again immediately %d state=%d\n",w->fd,c->responding);
1026 0           goto try_write_again_immediately;
1027             }
1028             // else, fallthrough:
1029             trace2("write fallthrough %d state=%d\n",w->fd,c->responding);
1030 0           goto try_write_again;
1031              
1032             #ifdef __linux__
1033 3           try_sendfile:
1034             {
1035             trace("sendfile %d: fd=%d off=%ld remain=%zu\n",
1036             w->fd, c->sendfile_fd, (long)c->sendfile_off, c->sendfile_remain);
1037 3           ssize_t sent = sendfile(w->fd, c->sendfile_fd,
1038 3           &c->sendfile_off, c->sendfile_remain);
1039 3 50         if (sent > 0) {
1040 3           c->sendfile_remain -= sent;
1041             trace("sendfile sent %zd, remain=%zu\n", sent, c->sendfile_remain);
1042 3 50         if (c->sendfile_remain == 0) {
1043 3 50         CLOSE_SENDFILE_FD(c);
    50          
1044 3           set_cork(c, 0);
1045 3           change_responding_state(c, RESPOND_SHUTDOWN);
1046 3           goto try_write_finished;
1047             }
1048             // More to send, wait for socket to be writable again
1049 0           goto try_write_again;
1050             }
1051 0 0         else if (sent == 0) {
1052             // EOF on file (shouldn't happen if sendfile_remain was correct)
1053 0 0         CLOSE_SENDFILE_FD(c);
    0          
1054 0           set_cork(c, 0);
1055 0 0         if (c->responding == RESPOND_STREAMING) {
1056 0           change_responding_state(c, RESPOND_SHUTDOWN);
1057             }
1058 0           goto try_write_finished;
1059             }
1060             else {
1061             // sent < 0, error
1062 0 0         if (errno == EAGAIN || errno == EINTR) {
    0          
1063             // Socket not ready, wait
1064 0           goto try_write_again;
1065             }
1066             // Real error
1067 0           trouble("sendfile fd=%d: %s\n", c->fd, strerror(errno));
1068 0 0         CLOSE_SENDFILE_FD(c);
    0          
1069 0           set_cork(c, 0);
1070 0           change_responding_state(c, RESPOND_SHUTDOWN);
1071 0           goto try_write_finished;
1072             }
1073             }
1074             #endif
1075              
1076 14           try_write_again:
1077             trace("write again %d state=%d\n",w->fd,c->responding);
1078 14           start_write_watcher(c);
1079 14           goto try_write_cleanup;
1080              
1081 732           try_write_finished:
1082             // should always be responding, but just in case
1083 732           switch(c->responding) {
1084 0           case RESPOND_NOT_STARTED:
1085             // the write watcher shouldn't ever get called before starting to
1086             // respond. Shut it down if it does.
1087             trace("unexpected try_write when response not started %d\n",c->fd);
1088 0           goto try_write_shutdown;
1089 0           case RESPOND_NORMAL:
1090 0           goto try_write_shutdown;
1091 50           case RESPOND_STREAMING:
1092 50 100         if (c->poll_write_cb) goto try_write_again;
1093 36           else goto try_write_paused;
1094 682           case RESPOND_SHUTDOWN:
1095 682           goto try_write_shutdown;
1096 0           default:
1097 0           goto try_write_cleanup;
1098             }
1099              
1100 36           try_write_paused:
1101             trace3("write PAUSED %d, refcnt=%d, state=%d\n", c->fd, SvREFCNT(c->self), c->responding);
1102 36           stop_write_watcher(c);
1103 36           stop_write_timer(c);
1104 36           goto try_write_cleanup;
1105              
1106 682           try_write_shutdown:
1107 682           handle_keepalive_or_close(c, try_conn_read);
1108              
1109 732           try_write_cleanup:
1110 732           SvREFCNT_dec(c->self);
1111 732           return;
1112             }
1113              
1114             // Parse PROXY protocol v1 text header
1115             // Format: "PROXY TCP4|TCP6|UNKNOWN src_addr dst_addr src_port dst_port\r\n"
1116             // Returns: bytes consumed on success, -1 on error, -2 if need more data
1117             static int
1118 154           parse_proxy_v1(struct feer_conn *c)
1119             {
1120 154           char *buf = SvPVX(c->rbuf);
1121 154           STRLEN len = SvCUR(c->rbuf);
1122              
1123             // Need at least "PROXY \r\n" (minimum valid line)
1124 154 100         if (len < 8)
1125 21           return -2; // need more data
1126              
1127             // Verify prefix
1128 133 50         if (memcmp(buf, PROXY_V1_PREFIX, PROXY_V1_PREFIX_LEN) != 0)
1129 0           return -1; // invalid
1130              
1131             // Find CRLF (max 108 bytes total)
1132 133           char *crlf = NULL;
1133 133           STRLEN search_len = len > PROXY_V1_MAX_LINE ? PROXY_V1_MAX_LINE : len;
1134 3052 100         for (STRLEN i = PROXY_V1_PREFIX_LEN; i < search_len - 1; i++) {
1135 2949 100         if (buf[i] == '\r' && buf[i+1] == '\n') {
    50          
1136 30           crlf = buf + i;
1137 30           break;
1138             }
1139             }
1140              
1141 133 100         if (!crlf) {
1142 103 100         if (len >= PROXY_V1_MAX_LINE)
1143 1           return -1; // line too long, invalid
1144 102           return -2; // need more data
1145             }
1146              
1147 30           size_t header_len = (crlf - buf) + 2; // include CRLF
1148              
1149             // Null-terminate the line for parsing (temporarily)
1150 30           char saved = *crlf;
1151 30           *crlf = '\0';
1152              
1153             // Parse protocol family: TCP4, TCP6, or UNKNOWN
1154 30           char *p = buf + PROXY_V1_PREFIX_LEN;
1155              
1156 30 100         if (strncmp(p, "UNKNOWN", 7) == 0 && (p[7] == '\0' || p[7] == ' ')) {
    50          
    0          
1157             // UNKNOWN - keep original address (used for health checks)
1158 1           *crlf = saved;
1159 1           c->proxy_proto_version = 1;
1160             trace("PROXY v1 UNKNOWN, keeping original address\n");
1161 1           return (int)header_len;
1162             }
1163              
1164 29           int is_ipv6 = 0;
1165 29 100         if (strncmp(p, "TCP4 ", 5) == 0) {
1166 26           p += 5;
1167 3 100         } else if (strncmp(p, "TCP6 ", 5) == 0) {
1168 2           p += 5;
1169 2           is_ipv6 = 1;
1170             } else {
1171 1           *crlf = saved;
1172 1           return -1; // unknown protocol
1173             }
1174              
1175             // Parse: src_addr dst_addr src_port dst_port
1176             char src_addr[46], dst_addr[46]; // max IPv6 length
1177             int src_port, dst_port;
1178              
1179             // Use sscanf to parse the addresses and ports
1180 28 50         if (sscanf(p, "%45s %45s %d %d", src_addr, dst_addr, &src_port, &dst_port) != 4) {
1181 0           *crlf = saved;
1182 0           return -1; // parse error
1183             }
1184              
1185             // Validate port ranges
1186 28 50         if (src_port < 0 || src_port > 65535 || dst_port < 0 || dst_port > 65535) {
    100          
    50          
    50          
1187 1           *crlf = saved;
1188 1           return -1;
1189             }
1190              
1191 27           *crlf = saved; // restore
1192              
1193             // Update connection's source address
1194 27 100         if (is_ipv6) {
1195 2           struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&c->sa;
1196 2           sa6->sin6_family = AF_INET6;
1197 2 50         if (inet_pton(AF_INET6, src_addr, &sa6->sin6_addr) != 1) {
1198 0           return -1; // invalid address
1199             }
1200 2           sa6->sin6_port = htons((uint16_t)src_port);
1201             } else {
1202 25           struct sockaddr_in *sa4 = (struct sockaddr_in *)&c->sa;
1203 25           sa4->sin_family = AF_INET;
1204 25 100         if (inet_pton(AF_INET, src_addr, &sa4->sin_addr) != 1) {
1205 1           return -1; // invalid address
1206             }
1207 24           sa4->sin_port = htons((uint16_t)src_port);
1208             }
1209              
1210 26           c->proxy_proto_version = 1;
1211 26           c->proxy_dst_port = (uint16_t)dst_port;
1212             trace("PROXY v1 %s src=%s:%d dst_port=%d\n", is_ipv6 ? "TCP6" : "TCP4", src_addr, src_port, dst_port);
1213 26           return (int)header_len;
1214             }
1215              
1216             // Parse PROXY protocol v2 binary header
1217             // Returns: bytes consumed on success, -1 on error, -2 if need more data
1218             static int
1219 106           parse_proxy_v2(struct feer_conn *c)
1220             {
1221 106           unsigned char *buf = (unsigned char *)SvPVX(c->rbuf);
1222 106           STRLEN len = SvCUR(c->rbuf);
1223              
1224             // Need at least minimum header (16 bytes)
1225 106 100         if (len < PROXY_V2_HDR_MIN)
1226 3           return -2;
1227              
1228             // Verify signature
1229 103 100         if (memcmp(buf, PROXY_V2_SIG, PROXY_V2_SIG_LEN) != 0)
1230 19           return -1;
1231              
1232             // Parse version and command (byte 12)
1233 84           unsigned char ver_cmd = buf[12];
1234 84           unsigned char version = ver_cmd & 0xF0;
1235 84           unsigned char command = ver_cmd & 0x0F;
1236              
1237 84 100         if (version != PROXY_V2_VERSION)
1238 27           return -1; // unsupported version
1239              
1240             // Parse family and protocol (byte 13)
1241 57           unsigned char fam_proto = buf[13];
1242 57           unsigned char family = fam_proto & 0xF0;
1243              
1244             // Parse address length (bytes 14-15, big-endian)
1245 57           uint16_t addr_len = (buf[14] << 8) | buf[15];
1246 57 100         if (unlikely(addr_len > 4096)) { /* spec allows 65535, cap for sanity */
1247 3           trouble("PROXY v2 addr_len too large: %u\n", addr_len);
1248 3           return -1;
1249             }
1250              
1251             // Total header length
1252 54           size_t total_len = PROXY_V2_HDR_MIN + addr_len;
1253 54 100         if (len < total_len)
1254 4           return -2; // need more data
1255              
1256             // Handle command
1257 50 100         if (command == PROXY_V2_CMD_LOCAL) {
1258             // LOCAL command - keep original address (health checks, etc.)
1259 1           c->proxy_proto_version = 2;
1260             trace("PROXY v2 LOCAL, keeping original address\n");
1261 1           return (int)total_len;
1262             }
1263              
1264 49 50         if (command != PROXY_V2_CMD_PROXY) {
1265 0           return -1; // unknown command
1266             }
1267              
1268             // PROXY command - update source address
1269 49           unsigned char *addr_data = buf + PROXY_V2_HDR_MIN;
1270              
1271 49 100         if (family == PROXY_V2_FAM_INET) {
1272             // IPv4 - need 12 bytes: src_addr(4) + dst_addr(4) + src_port(2) + dst_port(2)
1273 46 50         if (addr_len < PROXY_V2_ADDR_V4_LEN)
1274 0           return -1;
1275              
1276 46           struct sockaddr_in *sa4 = (struct sockaddr_in *)&c->sa;
1277 46           sa4->sin_family = AF_INET;
1278 46           memcpy(&sa4->sin_addr, addr_data, 4); // src addr
1279 46           memcpy(&sa4->sin_port, addr_data + 8, 2); // src port (already network order)
1280              
1281             // Extract dst_port for scheme inference (offset 10, network byte order)
1282             uint16_t dst_port_n;
1283 46           memcpy(&dst_port_n, addr_data + 10, 2);
1284 46           c->proxy_dst_port = ntohs(dst_port_n);
1285              
1286             trace("PROXY v2 TCP4 src=%d.%d.%d.%d:%d dst_port=%d\n",
1287             addr_data[0], addr_data[1], addr_data[2], addr_data[3],
1288             ntohs(sa4->sin_port), c->proxy_dst_port);
1289 3 100         } else if (family == PROXY_V2_FAM_INET6) {
1290             // IPv6 - need 36 bytes: src_addr(16) + dst_addr(16) + src_port(2) + dst_port(2)
1291 1 50         if (addr_len < PROXY_V2_ADDR_V6_LEN)
1292 0           return -1;
1293              
1294 1           struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&c->sa;
1295 1           sa6->sin6_family = AF_INET6;
1296 1           memcpy(&sa6->sin6_addr, addr_data, 16); // src addr
1297 1           memcpy(&sa6->sin6_port, addr_data + 32, 2); // src port (already network order)
1298              
1299             // Extract dst_port for scheme inference (offset 34, network byte order)
1300             uint16_t dst_port_n;
1301 1           memcpy(&dst_port_n, addr_data + 34, 2);
1302 1           c->proxy_dst_port = ntohs(dst_port_n);
1303              
1304             trace("PROXY v2 TCP6 port=%d dst_port=%d\n", ntohs(sa6->sin6_port), c->proxy_dst_port);
1305 2 100         } else if (family == PROXY_V2_FAM_UNSPEC) {
1306             // Unspecified - keep original address
1307             trace("PROXY v2 UNSPEC, keeping original address\n");
1308 1 50         } else if (family == PROXY_V2_FAM_UNIX) {
1309             // AF_UNIX - address is 2x108-byte paths, not meaningful as an HTTP
1310             // REMOTE_ADDR, so keep the original address (like UNSPEC). Per PROXY
1311             // protocol spec section 2.2; HAProxy/nginx unix-socket frontends emit it.
1312             // The spec mandates a 216-byte address block; reject short ones
1313             // (consistent with the INET/INET6 minimum-length checks above).
1314 1 50         if (addr_len < PROXY_V2_ADDR_UNIX_LEN)
1315 0           return -1;
1316             trace("PROXY v2 UNIX, keeping original address\n");
1317             } else {
1318 0           return -1; // unsupported family
1319             }
1320              
1321             // Parse TLVs if present
1322 49           size_t addr_size = 0;
1323 49 100         if (family == PROXY_V2_FAM_INET) addr_size = PROXY_V2_ADDR_V4_LEN;
1324 3 100         else if (family == PROXY_V2_FAM_INET6) addr_size = PROXY_V2_ADDR_V6_LEN;
1325 2 100         else if (family == PROXY_V2_FAM_UNIX) addr_size = PROXY_V2_ADDR_UNIX_LEN;
1326              
1327 49 100         if (addr_len > addr_size) {
1328             dTHX; /* Perl API calls below (newHV, newSVpvn, hv_store, etc.) */
1329             // TLVs are present
1330 24           unsigned char *tlv_start = addr_data + addr_size;
1331 24           size_t tlv_remaining = addr_len - addr_size;
1332              
1333             // Create hash for TLVs
1334 24           HV *tlv_hv = newHV();
1335 24           unsigned int tlv_count = 0;
1336              
1337 92 100         while (tlv_remaining >= 3) { // minimum TLV: 1 type + 2 length
1338             // Bound iteration: a malicious sender can pack ~1365 zero-length
1339             // TLVs into a 4096-byte payload. 64 covers all real-world headers.
1340 89 100         if (++tlv_count > 64) {
1341             trace("PROXY v2 too many TLVs (>64), rejecting\n");
1342 1           SvREFCNT_dec((SV *)tlv_hv);
1343 1           return -1;
1344             }
1345 88           unsigned char tlv_type = tlv_start[0];
1346 88           uint16_t tlv_len = (tlv_start[1] << 8) | tlv_start[2];
1347              
1348 88 100         if (tlv_remaining < 3 + (size_t)tlv_len) {
1349             // Malformed TLV - reject the whole PROXY header per spec
1350             trace("PROXY v2 malformed TLV: need %u bytes, have %zu\n",
1351             tlv_len, tlv_remaining - 3);
1352 20           SvREFCNT_dec((SV *)tlv_hv);
1353 20           return -1;
1354             }
1355              
1356             // Check for SSL TLV (indicates connection was over SSL/TLS)
1357             // PP2_TYPE_SSL requires minimum 5 bytes (client flags + verify)
1358 68 100         if (tlv_type == PP2_TYPE_SSL && tlv_len >= 5) {
    50          
1359 2           c->proxy_ssl = 1;
1360             trace("PROXY v2 TLV PP2_TYPE_SSL detected\n");
1361             }
1362              
1363             // Store TLV value (skip NOOP type)
1364 68 50         if (tlv_type != PP2_TYPE_NOOP) {
1365 68           SV *val = newSVpvn((char *)(tlv_start + 3), tlv_len);
1366             char key[8];
1367 68           int key_len = snprintf(key, sizeof(key), "%u", tlv_type);
1368 68           hv_store(tlv_hv, key, key_len, val, 0);
1369             trace("PROXY v2 TLV type=%u len=%u\n", tlv_type, tlv_len);
1370             }
1371              
1372 68           tlv_start += 3 + (size_t)tlv_len;
1373 68           tlv_remaining -= 3 + (size_t)tlv_len;
1374             }
1375              
1376             // Store hash in connection if non-empty
1377 3 50         if (HvKEYS(tlv_hv) > 0) {
    50          
1378 3           c->proxy_tlvs = newRV_noinc((SV *)tlv_hv);
1379             } else {
1380 0           SvREFCNT_dec((SV *)tlv_hv);
1381             }
1382             }
1383              
1384 28           c->proxy_proto_version = 2;
1385 28           return (int)total_len;
1386             }
1387              
1388             // Try to parse PROXY protocol header (auto-detect v1 or v2)
1389             // Returns: bytes consumed on success, -1 on error, -2 if need more data
1390             static int
1391 290           try_parse_proxy_header(struct feer_conn *c)
1392             {
1393 290 50         if (SvCUR(c->rbuf) == 0)
1394 0           return -2; // need data
1395              
1396 290           unsigned char first = ((unsigned char *)SvPVX(c->rbuf))[0];
1397              
1398 290 100         if (first == 'P') {
1399 154           return parse_proxy_v1(c);
1400 136 100         } else if (first == 0x0D) {
1401 106           return parse_proxy_v2(c);
1402             } else {
1403 30           return -1; // neither v1 nor v2
1404             }
1405             }
1406