git: 925ca9b8355d - main - linux: Add TCP_INFO support

From: Chuck Tuffli <chuck_at_FreeBSD.org>
Date: Wed, 10 Jun 2026 00:32:24 UTC
The branch main has been updated by chuck:

URL: https://cgit.FreeBSD.org/src/commit/?id=925ca9b8355d10a0dc85175dc865095c9b3370c4

commit 925ca9b8355d10a0dc85175dc865095c9b3370c4
Author:     Chuck Tuffli <chuck@FreeBSD.org>
AuthorDate: 2026-06-10 00:22:49 +0000
Commit:     Chuck Tuffli <chuck@FreeBSD.org>
CommitDate: 2026-06-10 00:23:24 +0000

    linux: Add TCP_INFO support
    
    Implement the getsockopt for TCP_INFO by mapping FreeBSD's version to
    what Linux expects.
    
    MFC after:      1 month
    Relnotes:       yes
    Reviewed by:    kib
    Differential Revision:  https://reviews.freebsd.org/D55882
---
 sys/compat/linux/linux_socket.c | 47 +++++++++++++++++++--
 sys/compat/linux/linux_socket.h | 92 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 136 insertions(+), 3 deletions(-)

diff --git a/sys/compat/linux/linux_socket.c b/sys/compat/linux/linux_socket.c
index 12ba6a3adfce..7b579958158a 100644
--- a/sys/compat/linux/linux_socket.c
+++ b/sys/compat/linux/linux_socket.c
@@ -591,9 +591,7 @@ linux_to_bsd_tcp_sockopt(int opt)
 	case LINUX_TCP_KEEPCNT:
 		return (TCP_KEEPCNT);
 	case LINUX_TCP_INFO:
-		LINUX_RATELIMIT_MSG_OPT1(
-		    "unsupported TCP socket option TCP_INFO (%d)", opt);
-		return (-2);
+		return (TCP_INFO);
 	case LINUX_TCP_MD5SIG:
 		return (TCP_MD5SIG);
 	case LINUX_TCP_USER_TIMEOUT:
@@ -2407,6 +2405,42 @@ linux_getsockopt_so_linger(struct thread *td,
 	return (linux_sockopt_copyout(td, &ling, len, args));
 }
 
+static int
+linux_getsockopt_tcp_info(struct thread *td,
+    struct linux_getsockopt_args *args)
+{
+	struct tcp_info tinfo;
+	struct l_tcp_info l_tinfo;
+	socklen_t len;
+	int error;
+
+	len = sizeof(tinfo);
+	error = kern_getsockopt(td, args->s, IPPROTO_TCP, TCP_INFO, &tinfo,
+	    UIO_SYSSPACE, &len);
+	if (error != 0)
+		return (error);
+	memset(&l_tinfo, 0, sizeof(l_tinfo));
+	l_tinfo.tcpi_state         = tinfo.tcpi_state;
+	l_tinfo.tcpi_options       = tinfo.tcpi_options;
+	l_tinfo.tcpi_snd_wscale    = tinfo.tcpi_snd_wscale;
+	l_tinfo.tcpi_rcv_wscale    = tinfo.tcpi_rcv_wscale;
+	l_tinfo.tcpi_rto           = tinfo.tcpi_rto;
+	l_tinfo.tcpi_snd_mss       = tinfo.tcpi_snd_mss;
+	l_tinfo.tcpi_rcv_mss       = tinfo.tcpi_rcv_mss;
+	l_tinfo.tcpi_last_data_recv = tinfo.tcpi_last_data_recv;
+	l_tinfo.tcpi_rtt           = tinfo.tcpi_rtt;
+	l_tinfo.tcpi_rttvar        = tinfo.tcpi_rttvar;
+	l_tinfo.tcpi_snd_ssthresh  = tinfo.tcpi_snd_ssthresh;
+	l_tinfo.tcpi_snd_cwnd      = tinfo.tcpi_snd_cwnd;
+	l_tinfo.tcpi_rcv_space     = tinfo.tcpi_rcv_space;
+	l_tinfo.tcpi_snd_wnd       = tinfo.tcpi_snd_wnd;
+	l_tinfo.tcpi_rcv_ooopack   = tinfo.tcpi_rcv_ooopack;
+	/* Eqivalent */
+	l_tinfo.tcpi_total_retrans = tinfo.tcpi_snd_rexmitpack;
+
+	return (linux_sockopt_copyout(td, &l_tinfo, len, args));
+}
+
 int
 linux_getsockopt(struct thread *td, struct linux_getsockopt_args *args)
 {
@@ -2505,6 +2539,13 @@ linux_getsockopt(struct thread *td, struct linux_getsockopt_args *args)
 		name = linux_to_bsd_ip6_sockopt(args->optname);
 		break;
 	case IPPROTO_TCP:
+		switch (args->optname) {
+		case LINUX_TCP_INFO:
+			return (linux_getsockopt_tcp_info(td, args));
+			/* NOTREACHED */
+		default:
+			break;
+		}
 		name = linux_to_bsd_tcp_sockopt(args->optname);
 		switch (name) {
 		case TCP_MAXUNACKTIME:
diff --git a/sys/compat/linux/linux_socket.h b/sys/compat/linux/linux_socket.h
index d30d68409496..47fc140259c5 100644
--- a/sys/compat/linux/linux_socket.h
+++ b/sys/compat/linux/linux_socket.h
@@ -359,6 +359,98 @@ struct l_ifreq {
 	} ifr_ifru;
 };
 
+/*
+ * Linux TCP_INFO structure as of v6.19.8
+ *
+ * Comments indicate last field for the given kernel version
+ */
+struct l_tcp_info {
+	uint8_t	tcpi_state;
+	uint8_t	tcpi_ca_state;
+	uint8_t	tcpi_retransmits;
+	uint8_t	tcpi_probes;
+	uint8_t	tcpi_backoff;
+	uint8_t	tcpi_options;
+	uint8_t	tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
+	uint8_t	tcpi_delivery_rate_app_limited:1, tcpi_fastopen_client_fail:2;
+
+	uint32_t	tcpi_rto;
+	uint32_t	tcpi_ato;
+	uint32_t	tcpi_snd_mss;
+	uint32_t	tcpi_rcv_mss;
+
+	uint32_t	tcpi_unacked;
+	uint32_t	tcpi_sacked;
+	uint32_t	tcpi_lost;
+	uint32_t	tcpi_retrans;
+	uint32_t	tcpi_fackets;
+
+	uint32_t	tcpi_last_data_sent;
+	uint32_t	tcpi_last_ack_sent;
+	uint32_t	tcpi_last_data_recv;
+	uint32_t	tcpi_last_ack_recv;
+
+	uint32_t	tcpi_pmtu;
+	uint32_t	tcpi_rcv_ssthresh;
+	uint32_t	tcpi_rtt;
+	uint32_t	tcpi_rttvar;
+	uint32_t	tcpi_snd_ssthresh;
+	uint32_t	tcpi_snd_cwnd;
+	uint32_t	tcpi_advmss;
+	uint32_t	tcpi_reordering;
+
+	uint32_t	tcpi_rcv_rtt;
+	uint32_t	tcpi_rcv_space;
+
+	uint32_t	tcpi_total_retrans;		/* v3.6 */
+
+	uint64_t	tcpi_pacing_rate;
+	uint64_t	tcpi_max_pacing_rate;	/* v3.14 */
+	uint64_t	tcpi_bytes_acked;
+	uint64_t	tcpi_bytes_received;
+	uint32_t	tcpi_segs_out;
+	uint32_t	tcpi_segs_in;					/* v4.1 */
+
+	uint32_t	tcpi_notsent_bytes;
+	uint32_t	tcpi_min_rtt;
+	uint32_t	tcpi_data_segs_in;
+	uint32_t	tcpi_data_segs_out;		/* v4.5 */
+
+	uint64_t   tcpi_delivery_rate;	/* v4.8 */
+
+	uint64_t	tcpi_busy_time;
+	uint64_t	tcpi_rwnd_limited;
+	uint64_t	tcpi_sndbuf_limited;	/* v4.9 */
+
+	uint32_t	tcpi_delivered;
+	uint32_t	tcpi_delivered_ce;		/* v4.16 */
+
+	uint64_t	tcpi_bytes_sent;
+	uint64_t	tcpi_bytes_retrans;
+	uint32_t	tcpi_dsack_dups;
+	uint32_t	tcpi_reord_seen;			/* v4.18 */
+
+	uint32_t	tcpi_rcv_ooopack;
+
+	uint32_t	tcpi_snd_wnd;					/* v5.3 */
+	uint32_t	tcpi_rcv_wnd;
+
+	uint32_t  tcpi_rehash;					/* v6.1 */
+
+	uint16_t	tcpi_total_rto;
+	uint16_t	tcpi_total_rto_recoveries;
+	uint32_t	tcpi_total_rto_time;	/* v6.6 */
+	uint32_t	tcpi_received_ce;
+	uint32_t	tcpi_delivered_e1_bytes;
+	uint32_t	tcpi_delivered_e0_bytes;
+	uint32_t	tcpi_delivered_ce_bytes;
+	uint32_t	tcpi_received_e1_bytes;
+	uint32_t	tcpi_received_e0_bytes;
+	uint32_t	tcpi_received_ce_bytes;
+	uint16_t	tcpi_accecn_fail_mode;
+	uint16_t	tcpi_accecn_opt_seen;	/* v6.17 */
+};
+
 /*
  * Define here members which are not exists in the FreeBSD struct ifreq.
  */