implement SO_TIMESTAMP[NS] fallback for kernels without time64 versions
authorRich Felker <dalias@aerifal.cx>
Wed, 18 Dec 2019 01:12:03 +0000 (20:12 -0500)
committerRich Felker <dalias@aerifal.cx>
Wed, 18 Dec 2019 03:05:31 +0000 (22:05 -0500)
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
src/mman/mmap.c
src/network/getsockopt.c
src/network/recvmsg.c
src/network/setsockopt.c

index 9f2784dbafea868dd3f4eb8a69b2ad81d32cc0c8..d768fb642f81afdb197914ab46448159c3bd4157 100644 (file)
@@ -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
index eff88d82a8fcff843247a5436f42016690daac24..2da11b877a3fc1931b973930175d115a9681d00f 100644 (file)
@@ -1,3 +1,4 @@
+#define SYSCALL_NO_TLS 1
 #include <unistd.h>
 #include <sys/mman.h>
 #include <errno.h>
index e871d624b7dc5f836847e0285887138b2704386c..d3640d9c91f26e77360cfe3161902e198aaaa6c3 100644 (file)
@@ -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);
index 4ca7da8b7295285bcd787f95b194b52ac606506d..5ce37e738f727db834e9e1643019e0a85a24df1f 100644 (file)
@@ -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
index 2c188a96e36427122868b76c5b90479f454913b8..612a1947a1f6182b4fd057fef4300c3df32eae5b 100644 (file)
@@ -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);