From ae388becb529428ac926da102f1d025b3c3968da Mon Sep 17 00:00:00 2001 From: Rich Felker Date: Tue, 17 Dec 2019 20:12:03 -0500 Subject: [PATCH] implement SO_TIMESTAMP[NS] fallback for kernels without time64 versions the definitions of SO_TIMESTAMP* changed on 32-bit archs in commit 38143339646a4ccce8afe298c34467767c899f51 to the new versions that provide 64-bit versions of timeval/timespec structure in control message payload. socket options, being state attached to the socket rather than function calls, are not trivial to implement as fallbacks on ENOSYS, and support for them was initially omitted on the assumption that the ioctl-based polling alternatives (SIOCGSTAMP*) could be used instead by applications if setsockopt fails. unfortunately, it turns out that SO_TIMESTAMP is sufficiently old and widely supported that a number of applications assume it's available and treat errors as fatal. this patch introduces emulation of SO_TIMESTAMP[NS] on pre-time64 kernels by falling back to setting the "_OLD" (time32) versions of the options if the time64 ones are not recognized, and performing translation of the SCM_TIMESTAMP[NS] control messages in recvmsg. since recvmsg does not know whether its caller is legacy time32 code or time64, it performs translation for any SCM_TIMESTAMP[NS]_OLD control messages it sees, leaving the original time32 timestamp as-is (it can't be rewritten in-place anyway, and memmove would be mildly expensive) and appending the converted time64 control message at the end of the buffer. legacy time32 callers will see the converted one as a spurious control message of unknown type; time64 callers running on pre-time64 kernels will see the original one as a spurious control message of unknown type. a time64 caller running on a kernel with native time64 support will only see the time64 version of the control message. emulation of SO_TIMESTAMPING is not included at this time since (1) applications which use it seem to be prepared for the possibility that it's not present or working, and (2) it can also be used in sendmsg control messages, in a manner that looks complex to emulate completely, and costly even when running on a time64-supporting kernel. corresponding changes in recvmmsg are not made at this time; they will be done separately. --- src/internal/syscall.h | 7 +++++++ src/mman/mmap.c | 1 + src/network/getsockopt.c | 9 ++++++++ src/network/recvmsg.c | 45 ++++++++++++++++++++++++++++++++++++++++ src/network/setsockopt.c | 9 ++++++++ 5 files changed, 71 insertions(+) diff --git a/src/internal/syscall.h b/src/internal/syscall.h index 9f2784db..d768fb64 100644 --- a/src/internal/syscall.h +++ b/src/internal/syscall.h @@ -306,6 +306,13 @@ hidden long __syscall_ret(unsigned long), #define SO_SNDTIMEO_OLD 21 #endif +#define SO_TIMESTAMP_OLD 29 +#define SO_TIMESTAMPNS_OLD 35 +#define SO_TIMESTAMPING_OLD 37 +#define SCM_TIMESTAMP_OLD SO_TIMESTAMP_OLD +#define SCM_TIMESTAMPNS_OLD SO_TIMESTAMPNS_OLD +#define SCM_TIMESTAMPING_OLD SO_TIMESTAMPING_OLD + #ifndef SIOCGSTAMP_OLD #define SIOCGSTAMP_OLD 0x8906 #endif diff --git a/src/mman/mmap.c b/src/mman/mmap.c index eff88d82..2da11b87 100644 --- a/src/mman/mmap.c +++ b/src/mman/mmap.c @@ -1,3 +1,4 @@ +#define SYSCALL_NO_TLS 1 #include #include #include diff --git a/src/network/getsockopt.c b/src/network/getsockopt.c index e871d624..d3640d9c 100644 --- a/src/network/getsockopt.c +++ b/src/network/getsockopt.c @@ -26,6 +26,15 @@ int getsockopt(int fd, int level, int optname, void *restrict optval, socklen_t tv->tv_sec = tv32[0]; tv->tv_usec = tv32[1]; *optlen = sizeof *tv; + break; + case SO_TIMESTAMP: + case SO_TIMESTAMPNS: + if (SO_TIMESTAMP == SO_TIMESTAMP_OLD) break; + if (optname==SO_TIMESTAMP) optname=SO_TIMESTAMP_OLD; + if (optname==SO_TIMESTAMPNS) optname=SO_TIMESTAMPNS_OLD; + r = __socketcall(getsockopt, fd, level, + optname, optval, optlen, 0); + break; } } return __syscall_ret(r); diff --git a/src/network/recvmsg.c b/src/network/recvmsg.c index 4ca7da8b..5ce37e73 100644 --- a/src/network/recvmsg.c +++ b/src/network/recvmsg.c @@ -1,10 +1,54 @@ #include #include +#include +#include +#include #include "syscall.h" +static void convert_scm_timestamps(struct msghdr *msg, socklen_t csize) +{ + if (SCM_TIMESTAMP == SCM_TIMESTAMP_OLD) return; + if (!msg->msg_control || !msg->msg_controllen) return; + + struct cmsghdr *cmsg, *last=0; + long tmp; + long long tvts[2]; + int type = 0; + + for (cmsg=CMSG_FIRSTHDR(msg); cmsg; cmsg=CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level==SOL_SOCKET) switch (cmsg->cmsg_type) { + case SCM_TIMESTAMP_OLD: + if (type) break; + type = SCM_TIMESTAMP; + goto common; + case SCM_TIMESTAMPNS_OLD: + type = SCM_TIMESTAMPNS; + common: + memcpy(&tmp, CMSG_DATA(cmsg), sizeof tmp); + tvts[0] = tmp; + memcpy(&tmp, CMSG_DATA(cmsg) + sizeof tmp, sizeof tmp); + tvts[1] = tmp; + break; + } + last = cmsg; + } + if (!last || !type) return; + if (CMSG_SPACE(sizeof tvts) > csize-msg->msg_controllen) { + msg->msg_flags |= MSG_CTRUNC; + return; + } + msg->msg_controllen += CMSG_SPACE(sizeof tvts); + cmsg = CMSG_NXTHDR(msg, last); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = type; + cmsg->cmsg_len = CMSG_LEN(sizeof tvts); + memcpy(CMSG_DATA(cmsg), &tvts, sizeof tvts); +} + ssize_t recvmsg(int fd, struct msghdr *msg, int flags) { ssize_t r; + socklen_t orig_controllen = msg->msg_controllen; #if LONG_MAX > INT_MAX struct msghdr h, *orig = msg; if (msg) { @@ -14,6 +58,7 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) } #endif r = socketcall_cp(recvmsg, fd, msg, flags, 0, 0, 0); + if (r >= 0) convert_scm_timestamps(msg, orig_controllen); #if LONG_MAX > INT_MAX if (orig) *orig = h; #endif diff --git a/src/network/setsockopt.c b/src/network/setsockopt.c index 2c188a96..612a1947 100644 --- a/src/network/setsockopt.c +++ b/src/network/setsockopt.c @@ -31,6 +31,15 @@ int setsockopt(int fd, int level, int optname, const void *optval, socklen_t opt r = __socketcall(setsockopt, fd, level, optname, ((long[]){s, CLAMP(us)}), 2*sizeof(long), 0); + break; + case SO_TIMESTAMP: + case SO_TIMESTAMPNS: + if (SO_TIMESTAMP == SO_TIMESTAMP_OLD) break; + if (optname==SO_TIMESTAMP) optname=SO_TIMESTAMP_OLD; + if (optname==SO_TIMESTAMPNS) optname=SO_TIMESTAMPNS_OLD; + r = __socketcall(setsockopt, fd, level, + optname, optval, optlen, 0); + break; } } return __syscall_ret(r); -- 2.25.1