about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2019-12-17 20:12:03 -0500
committerRich Felker <dalias@aerifal.cx>2019-12-17 22:05:31 -0500
commitae388becb529428ac926da102f1d025b3c3968da (patch)
treed6c2c45d0c9e8a062cdab9eaba6bd9106cce2ded /src
parentf12bd8e05c8bb2c3e2b91d635887ec424ef8fbd9 (diff)
downloadmusl-ae388becb529428ac926da102f1d025b3c3968da.tar.gz
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')
-rw-r--r--src/internal/syscall.h7
-rw-r--r--src/mman/mmap.c1
-rw-r--r--src/network/getsockopt.c9
-rw-r--r--src/network/recvmsg.c45
-rw-r--r--src/network/setsockopt.c9
5 files changed, 71 insertions, 0 deletions
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 <unistd.h>
 #include <sys/mman.h>
 #include <errno.h>
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);