diff options
author | Rich Felker <dalias@aerifal.cx> | 2019-12-17 20:12:03 -0500 |
---|---|---|
committer | Rich Felker <dalias@aerifal.cx> | 2019-12-17 22:05:31 -0500 |
commit | ae388becb529428ac926da102f1d025b3c3968da (patch) | |
tree | d6c2c45d0c9e8a062cdab9eaba6bd9106cce2ded /src/network | |
parent | f12bd8e05c8bb2c3e2b91d635887ec424ef8fbd9 (diff) | |
download | musl-ae388becb529428ac926da102f1d025b3c3968da.tar.gz musl-ae388becb529428ac926da102f1d025b3c3968da.tar.bz2 musl-ae388becb529428ac926da102f1d025b3c3968da.tar.xz musl-ae388becb529428ac926da102f1d025b3c3968da.zip |
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.
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/getsockopt.c | 9 | ||||
-rw-r--r-- | src/network/recvmsg.c | 45 | ||||
-rw-r--r-- | src/network/setsockopt.c | 9 |
3 files changed, 63 insertions, 0 deletions
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 <sys/socket.h> #include <limits.h> +#include <time.h> +#include <sys/time.h> +#include <string.h> #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); |