File Coverage

feersum_core.c.inc
Criterion Covered Total %
statement 577 725 79.5
branch 247 390 63.3
condition n/a
subroutine n/a
pod n/a
total 824 1115 73.9


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