From c42134cb91ddecce90c921ecff47ba816078767b Mon Sep 17 00:00:00 2001 From: Christopher Griffin Date: Sat, 31 Oct 2026 15:22:38 -0500 Subject: [PATCH] socket: decode receive timestamps on a 32-bit system with a 64-bit time_t musl names one SO_TIMESTAMP and one SO_TIMESTAMPNS, with no _OLD and _NEW variants, and on a 32-bit system they are the kernel's _NEW values (63 and 64 on ARM), whose control messages carry 64-bit fields. Without SO_TIMESTAMP_NEW, the _NEW decoders were left out and the _OLD fallbacks, defined to SO_TIMESTAMP and SO_TIMESTAMPNS, decoded those messages with the old layout: tv_sec from the low half of the 64-bit seconds and the fraction from the high half, which is 0. Every receive timestamp came out in whole seconds. Build the 64-bit-field decoders on every Linux system. Where the libc names both variants, keep an entry for each; where it names only one, decode it with the layout its time_t gives: the 64-bit fields where time_t is wider than long. Also give st_timeval_new its own size; it had the size of the old struct. Seen on an Arista W-118 (IPQ4019, armv7, musl): SO_TIMESTAMP gave { sec, usec: 0 } and SO_TIMESTAMPNS { sec, nsec: 0 } for every packet. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Christopher Griffin --- lib/socket.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/socket.c b/lib/socket.c index 16eae444..c2b91297 100644 --- a/lib/socket.c +++ b/lib/socket.c @@ -1677,17 +1677,18 @@ static struct_t st_timeval_old = { } }; -# ifdef SO_TIMESTAMP_NEW +/* the kernel's SO_TIMESTAMP_NEW and SO_TIMESTAMPNS_NEW layouts, 64-bit + * fields everywhere; also what a libc with only one SO_TIMESTAMP (musl) + * gets where its time_t is wider than long */ struct timeval_new_local { int64_t tv_sec; int64_t tv_usec; }; static struct_t st_timeval_new = { - .size = sizeof(struct timeval_old_local), + .size = sizeof(struct timeval_new_local), .members = (member_t []){ STRUCT_MEMBER(timeval_new_local, tv, sec, DT_SIGNED), STRUCT_MEMBER(timeval_new_local, tv, usec, DT_SIGNED), { 0 } } }; -# endif struct timespec_old_local { long tv_sec; long tv_nsec; }; static struct_t st_timespec_old = { @@ -1699,7 +1700,6 @@ static struct_t st_timespec_old = { } }; -# ifdef SO_TIMESTAMPNS_NEW struct timespec_new_local { long long tv_sec; long long tv_nsec; }; static struct_t st_timespec_new = { .size = sizeof(struct timespec_new_local), @@ -1709,7 +1709,6 @@ static struct_t st_timespec_new = { { 0 } } }; -# endif #endif #define SV_VOID (struct_t *)0 @@ -1906,13 +1905,18 @@ static cmsgtype_t cmsgtypes[] = { #if defined(__linux__) { SOL_PACKET, PACKET_AUXDATA, &st_tpacket_auxdata }, +# if defined(SO_TIMESTAMP_NEW) && defined(SO_TIMESTAMPNS_NEW) { SOL_SOCKET, SO_TIMESTAMP_OLD, &st_timeval_old }, -# ifdef SO_TIMESTAMP_NEW { SOL_SOCKET, SO_TIMESTAMP_NEW, &st_timeval_new }, -# endif { SOL_SOCKET, SO_TIMESTAMPNS_OLD, &st_timespec_old }, -# ifdef SO_TIMESTAMPNS_NEW { SOL_SOCKET, SO_TIMESTAMPNS_NEW, &st_timespec_new }, +# else + /* a libc without the _OLD and _NEW names (musl) has one SO_TIMESTAMP, + * the kernel's _NEW where its time_t is wider than long */ + { SOL_SOCKET, SO_TIMESTAMP, + sizeof(time_t) > sizeof(long) ? &st_timeval_new : &st_timeval_old }, + { SOL_SOCKET, SO_TIMESTAMPNS, + sizeof(time_t) > sizeof(long) ? &st_timespec_new : &st_timespec_old }, # endif { SOL_SOCKET, SCM_CREDENTIALS, &st_ucred },