Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ lib/dialects/linux/tests/pidfd
lib/dialects/linux/tests/pipe
lib/dialects/linux/tests/pty
lib/dialects/linux/tests/ux
lib/dialects/linux/tests/udp_state
lib/dialects/linux/tests/mmap
lib/dialects/linux/tests/unbal_parens

Expand Down
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ LINUX_TESTS = lib/dialects/linux/tests/case-10-mqueue.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-udp-state-filter.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 \
Expand All @@ -179,6 +180,7 @@ check_PROGRAMS += lib/dialects/linux/tests/epoll \
lib/dialects/linux/tests/pipe \
lib/dialects/linux/tests/pty \
lib/dialects/linux/tests/ux \
lib/dialects/linux/tests/udp_state \
lib/dialects/linux/tests/unbal_parens
lib_dialects_linux_tests_mq_fork_LDADD = -lrt
lib_dialects_linux_tests_mq_open_LDADD = -lrt
Expand Down
103 changes: 51 additions & 52 deletions lib/dialects/linux/dsock.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,51 @@ static void get_tcpudp6(struct lsof_context *ctx, char *p, int pr, int clr);
static int hex_ipv6_to_in6(char *as, struct in6_addr *ad);
#endif /* defined(HASIPv6) */

/*
* excluded_by_state() -- check a TCP or UDP /proc record against the -s
* state inclusions and exclusions of its protocol
* return: 1 == exclude the file
* 0 == keep processing it
*/
static int excluded_by_state(struct lsof_context *ctx, /* context */
int proto, /* 0 = TCP, else UDP or UDPLITE */
int state) /* state from the /proc record */
{
unsigned char *inc, *exc;
int i, inn, excn, n, other_inn;

if (proto == 0) {
i = state + TcpStOff;
n = TcpNstates;
inc = TcpStI;
exc = TcpStX;
inn = TcpStIn;
excn = TcpStXn;
other_inn = UdpStIn;
} else {
i = state + UdpStOff;
n = UdpNstates;
inc = UdpStI;
exc = UdpStX;
inn = UdpStIn;
excn = UdpStXn;
other_inn = TcpStIn;
}
if (excn && i >= 0 && i < n && exc[i])
return (1);
if (inn) {
if (i >= 0 && i < n && inc[i]) {
inc[i] = 2;
return (0);
}
return (1);
}
/*
* Only states of the other protocol were asked for.
*/
return (other_inn ? 1 : 0);
}

/*
* build_IPstates() -- build the TCP and UDP state tables
*/
Expand Down Expand Up @@ -3853,32 +3898,9 @@ void process_proc_sock(struct lsof_context *ctx, /* context */
* and foreign IPv6 addresses; save the type and protocol; and
* (optionally) save the queue sizes.
*/
i = tp6->state + TcpStOff;
if (TcpStXn) {

/*
* Check for state exclusion.
*/
if (i >= 0 && i < TcpNstates) {
if (TcpStX[i]) {
Lf->sf |= SELEXCLF;
return;
}
}
}
if (TcpStIn) {

/*
* Check for state inclusion.
*/
if (i >= 0 && i < TcpNstates) {
if (TcpStI[i])
TcpStI[i] = 2;
else {
Lf->sf |= SELEXCLF;
return;
}
}
if (excluded_by_state(ctx, tp6->proto, tp6->state)) {
Lf->sf |= SELEXCLF;
return;
}
if (Fnet && (FnetTy != 4))
Lf->sf |= SELNET;
Expand Down Expand Up @@ -3961,32 +3983,9 @@ void process_proc_sock(struct lsof_context *ctx, /* context */
* save the local and foreign IPv4 addresses; save the type and
* protocol; and (optionally) save the queue sizes.
*/
i = tp->state + TcpStOff;
if (TcpStXn) {

/*
* Check for state exclusion.
*/
if (i >= 0 && i < TcpNstates) {
if (TcpStX[i]) {
Lf->sf |= SELEXCLF;
return;
}
}
}
if (TcpStIn) {

/*
* Check for state inclusion.
*/
if (i >= 0 && i < TcpNstates) {
if (TcpStI[i])
TcpStI[i] = 2;
else {
Lf->sf |= SELEXCLF;
return;
}
}
if (excluded_by_state(ctx, tp->proto, tp->state)) {
Lf->sf |= SELEXCLF;
return;
}
if (Fnet && (FnetTy != 6))
Lf->sf |= SELNET;
Expand Down
1 change: 1 addition & 0 deletions lib/dialects/linux/tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ HELPERS = \
pipe \
pty \
ux \
udp_state \
mmap \
unbal_parens \
\
Expand Down
18 changes: 18 additions & 0 deletions lib/dialects/linux/tests/case-20-udp-socket-state.bash
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ if ! cat $fclient | grep -q "UDP.*127.0.0.1:.*->127.0.0.1:10001 (ESTABLISHED)";
exit 1
fi

$lsof -n -P -p $client -a -i UDP -s UDP:ESTABLISHED > $fclient
if ! cat $fclient | grep -q "UDP.*127.0.0.1:.*->127.0.0.1:10001 (ESTABLISHED)"; then
echo "connected UDP socket missing with -s UDP:ESTABLISHED" >> $report
cat $fclient >> $report
killBoth $client $server
rm -f $fclient
exit 1
fi

$lsof -n -P -p $client -a -i UDP -s UDP:^ESTABLISHED > $fclient
if cat $fclient | grep -q "UDP"; then
echo "connected UDP socket listed with -s UDP:^ESTABLISHED" >> $report
cat $fclient >> $report
killBoth $client $server
rm -f $fclient
exit 1
fi

rm -f $fclient
killBoth $client $server

Expand Down
77 changes: 77 additions & 0 deletions lib/dialects/linux/tests/case-20-udp-state-filter.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
source tests/common.bash

work=$(mktemp -d "${TMPDIR:-/tmp}/lsof-udp-state.XXXXXX") || exit 1
pid=
failed=0
cleanup() {
if [[ -n $pid ]]; then
kill "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
fi
rm -rf "$work"
}
trap cleanup EXIT

check_fds() {
local label=$1 expected=$2 status actual
shift 2
"$lsof" -n -P -a -p "$pid" -Ff "$@" > "$work/output" 2> "$work/error"
status=$?
expected=$(printf '%s\n' "$expected" | tr ' ' '\n' | sort -n)
actual=$(sed -n 's/^f\([0-9][0-9]*\)$/\1/p' "$work/output" | sort -n)
if [[ $status -ne 0 || $actual != "$expected" ]]; then
echo "IPv$family $label: exit $status; expected FDs [$expected], got [$actual]"
cat "$work/output" "$work/error"
return 1
fi
}

{
for family in 4 6; do
: > "$work/ready"
"$tcasedir/udp_state" "$family" > "$work/ready" 2> "$work/setup-error" &
pid=$!
for ((attempt = 0; attempt < 100; attempt++)); do
[[ -s $work/ready ]] && break
kill -0 "$pid" 2>/dev/null || break
sleep 0.05
done
if [[ ! -s $work/ready ]]; then
if kill -0 "$pid" 2>/dev/null; then
echo "IPv$family socket setup timed out"
exit 1
fi
wait "$pid"
status=$?
pid=
cat "$work/setup-error"
if [[ $family == 6 && $status == 77 ]]; then
echo "SKIP: IPv6 unavailable (IPv4 coverage passed)"
continue
fi
exit 1
fi
read -r idle connected listener client server < "$work/ready"

check_fds baseline "$idle $connected" -i "${family}UDP" || failed=1
check_fds inclusion "$connected" -i "${family}UDP" -sUDP:ESTABLISHED || failed=1
check_fds exclusion "$idle" -i "${family}UDP" '-sUDP:^ESTABLISHED' || failed=1
check_fds mixed-inclusion "$listener $connected" -i "$family" \
-sTCP:LISTEN -sUDP:ESTABLISHED || failed=1
check_fds tcp-only "$listener" -i "$family" -sTCP:LISTEN || failed=1
check_fds udp-only "$connected" -i "$family" -sUDP:ESTABLISHED || failed=1
check_fds tcp-exclusion-only "$idle $connected $listener" -i "$family" \
'-sTCP:^ESTABLISHED' || failed=1
check_fds udp-exclusion-only "$idle $listener $client $server" -i "$family" \
'-sUDP:^ESTABLISHED' || failed=1
check_fds mixed-exclusion "$idle $listener" -i "$family" \
'-sTCP:^ESTABLISHED' '-sUDP:^ESTABLISHED' || failed=1

kill "$pid"
wait "$pid" 2>/dev/null
pid=
echo "IPv$family UDP state filter checks completed"
done
exit "$failed"
} > "$report" 2>&1
77 changes: 77 additions & 0 deletions lib/dialects/linux/tests/udp_state.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* Keep connected/unconnected UDP and listening/connected TCP sockets in one PID.
* Print their descriptors only once all sockets are ready for inspection.
*/
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

static int bound_socket(int family, int type, struct sockaddr_storage *address,
socklen_t *length) {
int fd = socket(family, type, 0);
if (fd < 0)
return -1;
memset(address, 0, sizeof(*address));
address->ss_family = family;
if (family == AF_INET) {
struct sockaddr_in *v4 = (struct sockaddr_in *)address;
v4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
*length = sizeof(*v4);
} else {
struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)address;
v6->sin6_addr = in6addr_loopback;
*length = sizeof(*v6);
}
if (bind(fd, (struct sockaddr *)address, *length) < 0 ||
getsockname(fd, (struct sockaddr *)address, length) < 0) {
int saved_errno = errno;
close(fd);
errno = saved_errno;
return -1;
}
return fd;
}

int main(int argc, char **argv) {
struct sockaddr_storage udp_address, tcp_address;
socklen_t udp_length, tcp_length;
int family, udp_idle, udp_connected, tcp_listener, tcp_client, tcp_server;

if (argc != 2 || (strcmp(argv[1], "4") && strcmp(argv[1], "6")))
return 1;
family = !strcmp(argv[1], "4") ? AF_INET : AF_INET6;
udp_idle = bound_socket(family, SOCK_DGRAM, &udp_address, &udp_length);
if (udp_idle < 0) {
int unavailable = family == AF_INET6 &&
(errno == EAFNOSUPPORT || errno == EADDRNOTAVAIL ||
errno == EPROTONOSUPPORT);
perror("UDP bind");
return unavailable ? 77 : 1;
}
udp_connected = socket(family, SOCK_DGRAM, 0);
if (udp_connected < 0 ||
connect(udp_connected, (struct sockaddr *)&udp_address, udp_length) < 0)
goto error;
tcp_listener = bound_socket(family, SOCK_STREAM, &tcp_address, &tcp_length);
if (tcp_listener < 0 || listen(tcp_listener, 1) < 0)
goto error;
tcp_client = socket(family, SOCK_STREAM, 0);
if (tcp_client < 0 ||
connect(tcp_client, (struct sockaddr *)&tcp_address, tcp_length) < 0)
goto error;
tcp_server = accept(tcp_listener, NULL, NULL);
if (tcp_server < 0)
goto error;

printf("%d %d %d %d %d\n", udp_idle, udp_connected, tcp_listener,
tcp_client, tcp_server);
fflush(stdout);
for (;;)
pause();
error:
perror("socket setup");
return 1;
}
Loading