From 4935caed36ed50bdf3886188607db9b590de2939 Mon Sep 17 00:00:00 2001 From: kurok <22548029+kurok@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:06:42 +0000 Subject: [PATCH 1/2] [linux] Display connection state for UDP sockets Register UDP socket states in build_IPstates() so that connected UDP sockets (those that called connect()) display (ESTABLISHED), matching the behavior of netstat and ss. Linux /proc/net/udp reuses TCP state enum values: TCP_ESTABLISHED for connected sockets and TCP_CLOSE for unconnected ones. Only ESTABLISHED is registered; unconnected sockets remain displayed without a state to avoid noise. Update text, field (-F), and JSON (-j) output paths to handle UDP states via the existing UdpSt[] infrastructure. Add test case-20-udp-socket-state.bash to verify connected UDP sockets show (ESTABLISHED) in lsof output. Closes #287 --- Makefile.am | 1 + lib/dialects/linux/dsock.c | 11 +++++ .../linux/tests/case-20-udp-socket-state.bash | 42 +++++++++++++++++++ src/dialects/linux/dprint.c | 24 +++++++---- src/print.c | 24 ++++++++--- 5 files changed, 89 insertions(+), 13 deletions(-) create mode 100755 lib/dialects/linux/tests/case-20-udp-socket-state.bash diff --git a/Makefile.am b/Makefile.am index 8f714dec..248e348d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -153,6 +153,7 @@ LINUX_TESTS = lib/dialects/linux/tests/case-10-mqueue.bash \ lib/dialects/linux/tests/case-20-inet6-socket-endpoint.bash \ lib/dialects/linux/tests/case-20-inet-socket-endpoint.bash \ lib/dialects/linux/tests/case-20-mmap.bash \ + lib/dialects/linux/tests/case-20-udp-socket-state.bash \ lib/dialects/linux/tests/case-20-mqueue-endpoint.bash \ lib/dialects/linux/tests/case-20-open-flags-cx.bash \ lib/dialects/linux/tests/case-20-open-flags-path.bash \ diff --git a/lib/dialects/linux/dsock.c b/lib/dialects/linux/dsock.c index d0496437..5d7cc4de 100644 --- a/lib/dialects/linux/dsock.c +++ b/lib/dialects/linux/dsock.c @@ -401,6 +401,17 @@ void build_IPstates(struct lsof_context *ctx) { (void)enter_IPstate(ctx, "TCP", "CLOSED", 0); (void)enter_IPstate(ctx, "TCP", (char *)NULL, 0); } + if (!UdpSt) { + /* + * Linux /proc/net/udp reuses TCP state enum values: + * TCP_ESTABLISHED (1) for connected UDP sockets, + * TCP_CLOSE (7) for unconnected ones. + * Only register ESTABLISHED — unconnected (CLOSE) is the + * default state and not useful to display. + */ + (void)enter_IPstate(ctx, "UDP", "ESTABLISHED", TCP_ESTABLISHED); + (void)enter_IPstate(ctx, "UDP", (char *)NULL, 0); + } } /* diff --git a/lib/dialects/linux/tests/case-20-udp-socket-state.bash b/lib/dialects/linux/tests/case-20-udp-socket-state.bash new file mode 100755 index 00000000..8e874b99 --- /dev/null +++ b/lib/dialects/linux/tests/case-20-udp-socket-state.bash @@ -0,0 +1,42 @@ +#!/bin/bash +source tests/common.bash + +if [ -z "$(nc -h 2>&1 | grep '\s\-4')" ]; then + echo "nc does not support -4 option, skipping" >> $report + exit 77 +fi + +if [ -z "$(nc -h 2>&1 | grep '\s\-u')" ]; then + echo "nc does not support -u option, skipping" >> $report + exit 77 +fi + +nc -l -u -4 127.0.0.1 10001 > /dev/null < /dev/zero & +server=$! +sleep 1 +nc -u -4 127.0.0.1 10001 < /dev/zero > /dev/null & +client=$! + +sleep 1 + +killBoth() +{ + kill -9 $1 + sleep 1 + kill -9 $2 +} 2> /dev/null > /dev/null + +fclient=/tmp/${name}-client-$$ +$lsof -n -P -p $client -a -i UDP > $fclient +if ! cat $fclient | grep -q "UDP.*127.0.0.1:.*->127.0.0.1:10001 (ESTABLISHED)"; then + echo "connected UDP socket missing ESTABLISHED state" >> $report + cat $fclient >> $report + killBoth $client $server + rm -f $fclient + exit 1 +fi + +rm -f $fclient +killBoth $client $server + +exit 0 diff --git a/src/dialects/linux/dprint.c b/src/dialects/linux/dprint.c index f8d84226..392da458 100644 --- a/src/dialects/linux/dprint.c +++ b/src/dialects/linux/dprint.c @@ -77,15 +77,25 @@ void print_tcptpi(struct lsof_context *ctx, /* context */ print_unix(ctx, nl); return; } - if ((Ftcptpi & TCPTPI_STATE) && Lf->lts.type == 0) { + if ((Ftcptpi & TCPTPI_STATE) && Lf->lts.type >= 0) { if (!TcpSt) (void)build_IPstates(ctx); - if ((s = Lf->lts.state.i + TcpStOff) < 0 || s >= TcpNstates) { - (void)snpf(buf, sizeof(buf), "UNKNOWN_TCP_STATE_%d", - Lf->lts.state.i); - cp = buf; - } else - cp = TcpSt[s]; + switch (Lf->lts.type) { + case 0: /* TCP */ + if ((s = Lf->lts.state.i + TcpStOff) < 0 || s >= TcpNstates) { + (void)snpf(buf, sizeof(buf), "UNKNOWN_TCP_STATE_%d", + Lf->lts.state.i); + cp = buf; + } else + cp = TcpSt[s]; + break; + case 1: /* UDP */ + if (!UdpSt) + (void)build_IPstates(ctx); + if ((s = Lf->lts.state.i + UdpStOff) >= 0 && s < UdpNstates) + cp = UdpSt[s]; + break; + } if (cp) { if (Ffield) (void)printf("%cST=%s%c", LSOF_FID_TCPTPI, cp, Terminator); diff --git a/src/print.c b/src/print.c index 625ee42f..e5c41e5c 100644 --- a/src/print.c +++ b/src/print.c @@ -2001,16 +2001,28 @@ static void json_print_file(struct lsof_context *ctx, int *sep) { printf("\"tcp_info\":{"); int tsep = 0; - if ((Ftcptpi & TCPTPI_STATE) && Lf->lts.type == 0) { + if ((Ftcptpi & TCPTPI_STATE) && Lf->lts.type >= 0) { if (!TcpNstates) (void)build_IPstates(ctx); int s = Lf->lts.state.i; - if (s >= 0 && s < TcpNstates && TcpSt[s]) - json_print_str(&tsep, "state", TcpSt[s]); - else { - snprintf(buf, sizeof(buf), "UNKNOWN_%d", s); - json_print_str(&tsep, "state", buf); + char *st = NULL; + if (Lf->lts.type == 0) { /* TCP */ + int si = s + TcpStOff; + if (si >= 0 && si < TcpNstates) + st = TcpSt[si]; + if (!st) { + snprintf(buf, sizeof(buf), "UNKNOWN_TCP_STATE_%d", s); + st = buf; + } + } else if (Lf->lts.type == 1) { /* UDP */ + if (!UdpSt) + (void)build_IPstates(ctx); + int si = s + UdpStOff; + if (si >= 0 && si < UdpNstates) + st = UdpSt[si]; } + if (st) + json_print_str(&tsep, "state", st); } #if defined(HASTCPTPIQ) if (Ftcptpi & TCPTPI_QUEUES) { From bd7b110af00bd48026c9bab4f90e8fd7f3c1c26d Mon Sep 17 00:00:00 2001 From: YuriyRyabikov <22548029+kurok@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:49:16 +0000 Subject: [PATCH 2/2] [linux] Fix UDP socket state test for ncat (nmap) compatibility Use sleep as stdin instead of /dev/zero for nc UDP connections. ncat sends /dev/zero as a single datagram causing "Message too long" on UDP sockets. sleep keeps the process alive without flooding data. --- lib/dialects/linux/tests/case-20-udp-socket-state.bash | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/dialects/linux/tests/case-20-udp-socket-state.bash b/lib/dialects/linux/tests/case-20-udp-socket-state.bash index 8e874b99..7185be52 100755 --- a/lib/dialects/linux/tests/case-20-udp-socket-state.bash +++ b/lib/dialects/linux/tests/case-20-udp-socket-state.bash @@ -11,10 +11,12 @@ if [ -z "$(nc -h 2>&1 | grep '\s\-u')" ]; then exit 77 fi -nc -l -u -4 127.0.0.1 10001 > /dev/null < /dev/zero & +# Use sleep as stdin to keep nc alive without flooding data. +# /dev/zero causes "Message too long" with ncat (nmap) on UDP. +sleep 60 | nc -l -u -4 127.0.0.1 10001 > /dev/null & server=$! sleep 1 -nc -u -4 127.0.0.1 10001 < /dev/zero > /dev/null & +sleep 60 | nc -u -4 127.0.0.1 10001 > /dev/null & client=$! sleep 1