core: tcp - fix close events missing or wrong reason

- audit all sites for TCP close: TCP events missing or
  wrong reason so that tcpops will run the correct
  event route: tcp:closed / tcp:timeout / tcp:reset
This commit is contained in:
S-P Chan 2026-07-25 07:37:22 +08:00
parent ba732b2340
commit 8bfb74bb10
2 changed files with 54 additions and 7 deletions

View file

@ -4124,6 +4124,9 @@ inline static int handle_tcp_child(struct tcp_child *tcp_c, int fd_i)
#endif /* TCP_ASYNC */
break;
}
/* the connection is returning to service:
* clear any stale close reason a prior timeout left on it. */
tcpconn->event = 0;
/* update the timeout*/
t = get_ticks_raw();
con_lifetime = tcpconn->lifetime;
@ -4359,6 +4362,8 @@ inline static int handle_ser_child(struct process_table *p, int fd_i)
io_watch_del(&io_h, tcpconn->s, -1, IO_FD_CLOSING);
tcpconn->flags &= ~(F_CONN_WRITE_W | F_CONN_READ_W);
}
/* emit the close event route (tcp:closed/timeout/reset) */
tcp_emit_closed_event(tcpconn);
tcpconn_put_destroy(tcpconn); /* dec refcnt & destroy on 0 */
break;
case CONN_GET_FD:
@ -5157,6 +5162,11 @@ static ticks_t tcpconn_main_timeout(ticks_t t, struct timer_ln *tl, void *data)
TCP_STATS_CON_TIMEOUT();
#endif /* TCP_ASYNC */
LM_DBG("timeout for %p\n", c);
/* The connection timed out and is torn down here.
* Emit the close event so the tcp:timeout event route fires */
if(c->event == 0)
c->event = TCP_CLOSED_TIMEOUT;
tcp_emit_closed_event(c);
if(likely(c->flags & F_CONN_HASHED)) {
c->flags &= ~(F_CONN_HASHED | F_CONN_MAIN_TIMER);
c->state = S_CONN_BAD;
@ -5893,6 +5903,9 @@ void tcp_timer_check_connections(unsigned int ticks, void *param)
mcmd[0] = (long)con;
mcmd[1] = CONN_EOF;
/* message read/data timeout: record the close reason
* so that tcpops can identify tcp:timeout */
con->event = TCP_CLOSED_TIMEOUT;
con->send_flags.f |= SND_F_CON_CLOSE;
con->flags |= F_CONN_FORCE_EOF;

View file

@ -313,9 +313,14 @@ int tcp_read_data(int fd, struct tcp_connection *c, char *buf, int b_size,
rd_conn_flags_t *flags)
{
int bytes_read;
int read_errno = 0; /* errno captured at the read() syscall (see below) */
again:
bytes_read = read(fd, buf, b_size);
/* Snapshot errno immediately: switch()/dst_blocklist and
* LOG()/async_tkv_emit() may change errno.
* Needed to set the correct close reason for tcpops */
read_errno = errno;
if(likely(bytes_read != b_size)) {
if(unlikely(bytes_read == -1)) {
@ -366,16 +371,30 @@ again:
}
LOG(cfg_get(core, core_cfg, corelog),
"error reading: %s (%d) ([%s]:%u -> [%s]:%u)\n",
strerror(errno), errno, ip_addr2xa(&c->rcv.src_ip),
c->rcv.src_port, ip_addr2xa(&c->rcv.dst_ip),
c->rcv.dst_port);
strerror(read_errno), read_errno,
ip_addr2xa(&c->rcv.src_ip), c->rcv.src_port,
ip_addr2xa(&c->rcv.dst_ip), c->rcv.dst_port);
async_tkv_emit(1200, "tcp-read-error",
"erno=%d;srcip=%s;dstip=%s", errno,
"erno=%d;srcip=%s;dstip=%s", read_errno,
ip_addr2xa(&c->rcv.src_ip), ip_addr2xa(&c->rcv.dst_ip));
if(errno == ETIMEDOUT) {
/* classify using read_errno */
if(read_errno == ETIMEDOUT) {
c->event = TCP_CLOSED_TIMEOUT;
} else if(errno == ECONNRESET) {
} else if(read_errno == ECONNRESET) {
c->event = TCP_CLOSED_RESET;
} else {
/* read_errno itself unhelpful (e.g. a TLS-layer failure that
* left no socket errno) - recover the true socket error from
* SO_ERROR so a reset is still classified as such. */
int soerr = 0;
socklen_t soerr_len = sizeof(soerr);
int grc = getsockopt(
fd, SOL_SOCKET, SO_ERROR, &soerr, &soerr_len);
if(grc == 0 && soerr == ECONNRESET) {
c->event = TCP_CLOSED_RESET;
} else if(grc == 0 && soerr == ETIMEDOUT) {
c->event = TCP_CLOSED_TIMEOUT;
}
}
return -1;
}
@ -387,7 +406,22 @@ again:
ip_addr2xa(&c->rcv.dst_ip), c->rcv.dst_port);
c->state = S_CONN_EOF;
*flags |= RD_CONN_EOF;
c->event = TCP_CLOSED_EOF;
/* Classify the close reason - otherwise
* tcp:reset may be mis-identified as tcp:closed
* and tcpops runs the wrong route. */
if(likely(c->event == 0)) {
int soerr = 0;
socklen_t soerr_len = sizeof(soerr);
if(unlikely(bytes_read != 0)
&& getsockopt(
fd, SOL_SOCKET, SO_ERROR, &soerr, &soerr_len)
== 0
&& soerr == ECONNRESET) {
c->event = TCP_CLOSED_RESET;
} else {
c->event = TCP_CLOSED_EOF;
}
}
} else {
if(unlikely(c->state == S_CONN_CONNECT
|| c->state == S_CONN_ACCEPT)) {