Message ID | 611db81c87911cb38a35e5f761e11b76e1f0d538.1597129029.git.scw@google.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | fcntl, sockopt, and ioctl options | expand |
Ping -- any comments on the four patches start with this? https://patchew.org/QEMU/cover.1597129029.git.scw@google.com/ On Tue, Aug 11, 2020 at 12:10 AM Shu-Chun Weng <scw@google.com> wrote: > Both guest options map to host SO_TIMESTAMP while keeping a global bit to > remember if the guest expects the old or the new format. Don't support > programs mixing two formats. > > Added a multiarch test to verify. > > Signed-off-by: Shu-Chun Weng <scw@google.com> > --- > v1 -> v2: > Only keep track of old or new format globally, remove support for > different > sockets mixing different formats. > Fix style problems. > > linux-user/alpha/sockbits.h | 8 +- > linux-user/generic/sockbits.h | 9 +- > linux-user/hppa/sockbits.h | 8 +- > linux-user/mips/sockbits.h | 8 +- > linux-user/sparc/sockbits.h | 8 +- > linux-user/strace.c | 7 +- > linux-user/syscall.c | 91 ++++++-- > tests/tcg/multiarch/socket_timestamp.c | 296 +++++++++++++++++++++++++ > 8 files changed, 408 insertions(+), 27 deletions(-) > create mode 100644 tests/tcg/multiarch/socket_timestamp.c > > diff --git a/linux-user/alpha/sockbits.h b/linux-user/alpha/sockbits.h > index d54dc98c09..40f0644df0 100644 > --- a/linux-user/alpha/sockbits.h > +++ b/linux-user/alpha/sockbits.h > @@ -48,8 +48,6 @@ > #define TARGET_SO_DETACH_FILTER 27 > > #define TARGET_SO_PEERNAME 28 > -#define TARGET_SO_TIMESTAMP 29 > -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP > > #define TARGET_SO_PEERSEC 30 > #define TARGET_SO_PASSSEC 34 > @@ -75,6 +73,12 @@ > /* Instruct lower device to use last 4-bytes of skb data as FCS */ > #define TARGET_SO_NOFCS 43 > > +#define TARGET_SO_TIMESTAMP_OLD 29 > +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > + > +#define TARGET_SO_TIMESTAMP_NEW 63 > +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > + > /* TARGET_O_NONBLOCK clashes with the bits used for socket types. > Therefore we > * have to define SOCK_NONBLOCK to a different value here. > */ > diff --git a/linux-user/generic/sockbits.h b/linux-user/generic/sockbits.h > index e44733c601..532cf2d3dc 100644 > --- a/linux-user/generic/sockbits.h > +++ b/linux-user/generic/sockbits.h > @@ -49,10 +49,15 @@ > #define TARGET_SO_DETACH_FILTER 27 > > #define TARGET_SO_PEERNAME 28 > -#define TARGET_SO_TIMESTAMP 29 > -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP > > #define TARGET_SO_ACCEPTCONN 30 > > #define TARGET_SO_PEERSEC 31 > + > +#define TARGET_SO_TIMESTAMP_OLD 29 > +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > + > +#define TARGET_SO_TIMESTAMP_NEW 63 > +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > + > #endif > diff --git a/linux-user/hppa/sockbits.h b/linux-user/hppa/sockbits.h > index 23f69a3293..284a47e74e 100644 > --- a/linux-user/hppa/sockbits.h > +++ b/linux-user/hppa/sockbits.h > @@ -29,8 +29,6 @@ > #define TARGET_SO_BSDCOMPAT 0x400e > #define TARGET_SO_PASSCRED 0x4010 > #define TARGET_SO_PEERCRED 0x4011 > -#define TARGET_SO_TIMESTAMP 0x4012 > -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP > #define TARGET_SO_TIMESTAMPNS 0x4013 > #define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS > > @@ -67,6 +65,12 @@ > > #define TARGET_SO_CNX_ADVICE 0x402E > > +#define TARGET_SO_TIMESTAMP_OLD 0x4012 > +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > + > +#define TARGET_SO_TIMESTAMP_NEW 0x4038 > +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > + > /* TARGET_O_NONBLOCK clashes with the bits used for socket types. > Therefore we > * have to define SOCK_NONBLOCK to a different value here. > */ > diff --git a/linux-user/mips/sockbits.h b/linux-user/mips/sockbits.h > index 0f022cd598..b4c39d9588 100644 > --- a/linux-user/mips/sockbits.h > +++ b/linux-user/mips/sockbits.h > @@ -61,14 +61,18 @@ > #define TARGET_SO_DETACH_FILTER 27 > > #define TARGET_SO_PEERNAME 28 > -#define TARGET_SO_TIMESTAMP 29 > -#define SCM_TIMESTAMP SO_TIMESTAMP > > #define TARGET_SO_PEERSEC 30 > #define TARGET_SO_SNDBUFFORCE 31 > #define TARGET_SO_RCVBUFFORCE 33 > #define TARGET_SO_PASSSEC 34 > > +#define TARGET_SO_TIMESTAMP_OLD 29 > +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > + > +#define TARGET_SO_TIMESTAMP_NEW 63 > +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > + > /** sock_type - Socket types > * > * Please notice that for binary compat reasons MIPS has to > diff --git a/linux-user/sparc/sockbits.h b/linux-user/sparc/sockbits.h > index 0a822e3e1f..07440efd14 100644 > --- a/linux-user/sparc/sockbits.h > +++ b/linux-user/sparc/sockbits.h > @@ -48,8 +48,6 @@ > #define TARGET_SO_GET_FILTER TARGET_SO_ATTACH_FILTER > > #define TARGET_SO_PEERNAME 0x001c > -#define TARGET_SO_TIMESTAMP 0x001d > -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP > > #define TARGET_SO_PEERSEC 0x001e > #define TARGET_SO_PASSSEC 0x001f > @@ -104,6 +102,12 @@ > > #define TARGET_SO_ZEROCOPY 0x003e > > +#define TARGET_SO_TIMESTAMP_OLD 0x001d > +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > + > +#define TARGET_SO_TIMESTAMP_NEW 0x0046 > +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > + > /* Security levels - as per NRL IPv6 - don't actually do anything */ > #define TARGET_SO_SECURITY_AUTHENTICATION 0x5001 > #define TARGET_SO_SECURITY_ENCRYPTION_TRANSPORT 0x5002 > diff --git a/linux-user/strace.c b/linux-user/strace.c > index 089fb3968e..a11a5e9e86 100644 > --- a/linux-user/strace.c > +++ b/linux-user/strace.c > @@ -2257,8 +2257,11 @@ print_optint: > case TARGET_SO_PASSCRED: > qemu_log("SO_PASSCRED,"); > goto print_optint; > - case TARGET_SO_TIMESTAMP: > - qemu_log("SO_TIMESTAMP,"); > + case TARGET_SO_TIMESTAMP_OLD: > + qemu_log("SO_TIMESTAMP_OLD,"); > + goto print_optint; > + case TARGET_SO_TIMESTAMP_NEW: > + qemu_log("SO_TIMESTAMP_NEW,"); > goto print_optint; > case TARGET_SO_RCVLOWAT: > qemu_log("SO_RCVLOWAT,"); > diff --git a/linux-user/syscall.c b/linux-user/syscall.c > index cda194a7cc..e6b1a18cc0 100644 > --- a/linux-user/syscall.c > +++ b/linux-user/syscall.c > @@ -1697,6 +1697,18 @@ static inline abi_long target_to_host_cmsg(struct > msghdr *msgh, > return 0; > } > > +/* > + * Linux kernel actually keeps track of whether the old version > (potentially > + * 32-bit time_t) or the new version is used for each socket. Instead of > + * replicate it will all the complexity, we only keep track of one global > state, > + * which is enough for guest programs that don't intentionally mix the two > + * versions. > + */ > +static enum TargetTimestampVersion { > + TARGET_TIMESTAMP_OLD, > + TARGET_TIMESTAMP_NEW, > +} target_expected_timestamp_version = TARGET_TIMESTAMP_OLD; > + > static inline abi_long host_to_target_cmsg(struct target_msghdr > *target_msgh, > struct msghdr *msgh) > { > @@ -1747,8 +1759,17 @@ static inline abi_long host_to_target_cmsg(struct > target_msghdr *target_msgh, > switch (cmsg->cmsg_level) { > case SOL_SOCKET: > switch (cmsg->cmsg_type) { > - case SO_TIMESTAMP: > - tgt_len = sizeof(struct target_timeval); > + case SCM_TIMESTAMP: > + switch (target_expected_timestamp_version) { > + case TARGET_TIMESTAMP_OLD: > + tgt_len = sizeof(struct target_timeval); > + target_cmsg->cmsg_type = > tswap32(TARGET_SCM_TIMESTAMP_OLD); > + break; > + case TARGET_TIMESTAMP_NEW: > + tgt_len = sizeof(struct target__kernel_sock_timeval); > + target_cmsg->cmsg_type = > tswap32(TARGET_SCM_TIMESTAMP_NEW); > + break; > + } > break; > default: > break; > @@ -1782,20 +1803,39 @@ static inline abi_long host_to_target_cmsg(struct > target_msghdr *target_msgh, > } > break; > } > - case SO_TIMESTAMP: > + case SCM_TIMESTAMP: > { > struct timeval *tv = (struct timeval *)data; > - struct target_timeval *target_tv = > - (struct target_timeval *)target_data; > - > - if (len != sizeof(struct timeval) || > - tgt_len != sizeof(struct target_timeval)) { > + if (len != sizeof(struct timeval)) { > goto unimplemented; > } > > - /* copy struct timeval to target */ > - __put_user(tv->tv_sec, &target_tv->tv_sec); > - __put_user(tv->tv_usec, &target_tv->tv_usec); > + switch (target_expected_timestamp_version) { > + case TARGET_TIMESTAMP_OLD: > + { > + struct target_timeval *target_tv = > + (struct target_timeval *)target_data; > + if (tgt_len != sizeof(struct target_timeval)) { > + goto unimplemented; > + } > + > + __put_user(tv->tv_sec, &target_tv->tv_sec); > + __put_user(tv->tv_usec, &target_tv->tv_usec); > + break; > + } > + case TARGET_TIMESTAMP_NEW: > + { > + struct target__kernel_sock_timeval *target_tv = > + (struct target__kernel_sock_timeval *)target_data; > + if (tgt_len != sizeof(struct > target__kernel_sock_timeval)) { > + goto unimplemented; > + } > + > + __put_user(tv->tv_sec, &target_tv->tv_sec); > + __put_user(tv->tv_usec, &target_tv->tv_usec); > + break; > + } > + } > break; > } > case SCM_CREDENTIALS: > @@ -1937,6 +1977,8 @@ static abi_long do_setsockopt(int sockfd, int level, > int optname, > int val; > struct ip_mreqn *ip_mreq; > struct ip_mreq_source *ip_mreq_source; > + enum TargetTimestampVersion target_timestamp_version = > + target_expected_timestamp_version; > > switch(level) { > case SOL_TCP: > @@ -2331,9 +2373,14 @@ set_timeout: > case TARGET_SO_PASSSEC: > optname = SO_PASSSEC; > break; > - case TARGET_SO_TIMESTAMP: > - optname = SO_TIMESTAMP; > - break; > + case TARGET_SO_TIMESTAMP_OLD: > + target_timestamp_version = TARGET_TIMESTAMP_OLD; > + optname = SO_TIMESTAMP; > + break; > + case TARGET_SO_TIMESTAMP_NEW: > + target_timestamp_version = TARGET_TIMESTAMP_NEW; > + optname = SO_TIMESTAMP; > + break; > case TARGET_SO_RCVLOWAT: > optname = SO_RCVLOWAT; > break; > @@ -2346,6 +2393,9 @@ set_timeout: > if (get_user_u32(val, optval_addr)) > return -TARGET_EFAULT; > ret = get_errno(setsockopt(sockfd, SOL_SOCKET, optname, &val, > sizeof(val))); > + if (!is_error(ret) && optname == SO_TIMESTAMP) { > + target_expected_timestamp_version = target_timestamp_version; > + } > break; > #ifdef SOL_NETLINK > case SOL_NETLINK: > @@ -2396,6 +2446,7 @@ static abi_long do_getsockopt(int sockfd, int level, > int optname, > abi_long ret; > int len, val; > socklen_t lv; > + int timestamp_format_matches = 0; > > switch(level) { > case TARGET_SOL_SOCKET: > @@ -2576,7 +2627,14 @@ get_timeout: > case TARGET_SO_PASSCRED: > optname = SO_PASSCRED; > goto int_case; > - case TARGET_SO_TIMESTAMP: > + case TARGET_SO_TIMESTAMP_OLD: > + timestamp_format_matches = > + (target_expected_timestamp_version == > TARGET_TIMESTAMP_OLD); > + optname = SO_TIMESTAMP; > + goto int_case; > + case TARGET_SO_TIMESTAMP_NEW: > + timestamp_format_matches = > + (target_expected_timestamp_version == > TARGET_TIMESTAMP_NEW); > optname = SO_TIMESTAMP; > goto int_case; > case TARGET_SO_RCVLOWAT: > @@ -2604,6 +2662,9 @@ get_timeout: > if (optname == SO_TYPE) { > val = host_to_target_sock_type(val); > } > + if (optname == SO_TIMESTAMP) { > + val = val && timestamp_format_matches; > + } > if (len > lv) > len = lv; > if (len == 4) { > diff --git a/tests/tcg/multiarch/socket_timestamp.c > b/tests/tcg/multiarch/socket_timestamp.c > new file mode 100644 > index 0000000000..71ab1845de > --- /dev/null > +++ b/tests/tcg/multiarch/socket_timestamp.c > @@ -0,0 +1,296 @@ > +#include <assert.h> > +#include <errno.h> > +#include <linux/types.h> > +#include <netinet/in.h> > +#include <stdint.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <sys/ioctl.h> > +#include <sys/socket.h> > +#include <sys/time.h> > +#include <sys/types.h> > +#include <sys/wait.h> > +#include <unistd.h> > + > +#ifdef __kernel_old_timeval > +#define kernel_old_timeval __kernel_old_timeval > +#else > +struct kernel_old_timeval { > + __kernel_long_t tv_sec; > + __kernel_long_t tv_usec; > +}; > +#endif > + > +struct kernel_sock_timeval { > + int64_t tv_sec; > + int64_t tv_usec; > +}; > + > +int create_udp_socket(struct sockaddr_in *sockaddr) > +{ > + socklen_t sockaddr_len; > + int sock = socket(AF_INET, SOCK_DGRAM, 0); > + if (sock < 0) { > + int err = errno; > + fprintf(stderr, "Failed to create server socket: %s\n", > strerror(err)); > + exit(err); > + } > + > + memset(sockaddr, 0, sizeof(*sockaddr)); > + sockaddr->sin_family = AF_INET; > + sockaddr->sin_port = htons(0); /* let kernel select a port for us */ > + sockaddr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); > + > + if (bind(sock, (struct sockaddr *)sockaddr, sizeof(*sockaddr)) < 0) { > + int err = errno; > + fprintf(stderr, "Failed to bind server socket: %s\n", > strerror(err)); > + exit(err); > + } > + > + sockaddr_len = sizeof(*sockaddr); > + if (getsockname(sock, (struct sockaddr *)sockaddr, &sockaddr_len) < > 0) { > + int err = errno; > + fprintf(stderr, "Failed to get socket name: %s\n", strerror(err)); > + exit(err); > + } > + return sock; > +} > + > +/* > + * Checks that the timestamp in the message is not after the reception > timestamp > + * as well as the reception time is within 10 seconds of the message time. > + */ > +void check_timestamp_difference(const struct timeval *msg_tv, > + const struct timeval *pkt_tv) > +{ > + if (pkt_tv->tv_sec < msg_tv->tv_sec || > + (pkt_tv->tv_sec == msg_tv->tv_sec && pkt_tv->tv_usec < > msg_tv->tv_usec)) > + { > + fprintf(stderr, > + "Packet received before sent: %lld.%06lld < > %lld.%06lld\n", > + (long long)pkt_tv->tv_sec, (long long)pkt_tv->tv_usec, > + (long long)msg_tv->tv_sec, (long long)msg_tv->tv_usec); > + exit(-1); > + } > + > + if (pkt_tv->tv_sec > msg_tv->tv_sec + 10 || > + (pkt_tv->tv_sec == msg_tv->tv_sec + 10 && > + pkt_tv->tv_usec > msg_tv->tv_usec)) { > + fprintf(stderr, > + "Packet received more than 10 seconds after sent: " > + "%lld.%06lld > %lld.%06lld + 10\n", > + (long long)pkt_tv->tv_sec, (long long)pkt_tv->tv_usec, > + (long long)msg_tv->tv_sec, (long long)msg_tv->tv_usec); > + exit(-1); > + } > +} > + > +void send_current_time(int sock, struct sockaddr_in server_sockaddr) > +{ > + struct timeval tv = {0, 0}; > + gettimeofday(&tv, NULL); > + sendto(sock, &tv, sizeof(tv), 0, (struct sockaddr *)&server_sockaddr, > + sizeof(server_sockaddr)); > +} > + > +typedef void (*get_timeval_t)(const struct cmsghdr *cmsg, struct timeval > *tv); > + > + > +void receive_packet(int sock, get_timeval_t get_timeval) > +{ > + struct msghdr msg = {0}; > + > + char iobuf[1024]; > + struct iovec iov; > + > + union { > + /* > + * 128 bytes are enough for all existing > + * timeval/timespec/scm_timestamping structures. > + */ > + char cmsg_buf[CMSG_SPACE(128)]; > + struct cmsghdr align; > + } u; > + struct cmsghdr *cmsg; > + struct timeval msg_tv, pkt_tv; > + > + int res; > + > + iov.iov_base = iobuf; > + iov.iov_len = sizeof(iobuf); > + > + msg.msg_iov = &iov; > + msg.msg_iovlen = 1; > + msg.msg_control = (caddr_t)u.cmsg_buf; > + msg.msg_controllen = sizeof(u.cmsg_buf); > + > + res = recvmsg(sock, &msg, 0); > + if (res < 0) { > + int err = errno; > + fprintf(stderr, "Failed to receive packet: %s\n", strerror(err)); > + exit(err); > + } > + > + assert(res == sizeof(struct timeval)); > + assert(iov.iov_base == iobuf); > + memcpy(&msg_tv, iov.iov_base, sizeof(msg_tv)); > + printf("Message timestamp: %lld.%06lld\n", > + (long long)msg_tv.tv_sec, (long long)msg_tv.tv_usec); > + > + cmsg = CMSG_FIRSTHDR(&msg); > + assert(cmsg); > + (*get_timeval)(cmsg, &pkt_tv); > + printf("Packet timestamp: %lld.%06lld\n", > + (long long)pkt_tv.tv_sec, (long long)pkt_tv.tv_usec); > + > + check_timestamp_difference(&msg_tv, &pkt_tv); > +} > + > +void get_timeval_from_so_timestamp(const struct cmsghdr *cmsg, > + struct timeval *tv) > +{ > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SCM_TIMESTAMP); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))); > + memcpy(tv, CMSG_DATA(cmsg), sizeof(*tv)); > +} > + > +#ifdef SO_TIMESTAMP_OLD > +void get_timeval_from_so_timestamp_old(const struct cmsghdr *cmsg, > + struct timeval *tv) > +{ > + struct kernel_old_timeval old_tv; > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SO_TIMESTAMP_OLD); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(old_tv))); > + > + memcpy(&old_tv, CMSG_DATA(cmsg), sizeof(old_tv)); > + tv->tv_sec = old_tv.tv_sec; > + tv->tv_usec = old_tv.tv_usec; > +} > + > +#ifdef SO_TIMESTAMP_NEW > +void get_timeval_from_so_timestamp_new(const struct cmsghdr *cmsg, > + struct timeval *tv) > +{ > + struct kernel_sock_timeval sock_tv; > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SO_TIMESTAMP_NEW); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(sock_tv))); > + > + memcpy(&sock_tv, CMSG_DATA(cmsg), sizeof(sock_tv)); > + tv->tv_sec = sock_tv.tv_sec; > + tv->tv_usec = sock_tv.tv_usec; > +} > +#endif /* defined(SO_TIMESTAMP_NEW) */ > +#endif /* defined(SO_TIMESTAMP_OLD) */ > + > +void set_socket_option(int sock, int sockopt, int on) > +{ > + socklen_t len; > + int val = on; > + if (setsockopt(sock, SOL_SOCKET, sockopt, &val, sizeof(val)) < 0) { > + int err = errno; > + fprintf(stderr, "Failed to setsockopt %d (%s): %s\n", > + sockopt, on ? "on" : "off", strerror(err)); > + exit(err); > + } > + > + len = sizeof(val); > + val = -1; > + if (getsockopt(sock, SOL_SOCKET, sockopt, &val, &len) < 0) { > + int err = errno; > + fprintf(stderr, "Failed to getsockopt (%d): %s\n", sock, > strerror(err)); > + exit(err); > + } > + assert(len == sizeof(val)); > + assert(val == on); > +} > + > +int main(int argc, char **argv) > +{ > + int parent_sock, child_sock; > + struct sockaddr_in parent_sockaddr, child_sockaddr; > + int pid; > + struct timeval tv = {0, 0}; > + gettimeofday(&tv, NULL); > + > + parent_sock = create_udp_socket(&parent_sockaddr); > + child_sock = create_udp_socket(&child_sockaddr); > + > + printf("Parent sock bound to port %d\nChild sock bound to port %d\n", > + parent_sockaddr.sin_port, child_sockaddr.sin_port); > + > + pid = fork(); > + if (pid < 0) { > + fprintf(stderr, "SKIPPED. Failed to fork: %s\n", strerror(errno)); > + } else if (pid == 0) { > + close(child_sock); > + > + /* Test 1: SO_TIMESTAMP */ > + send_current_time(parent_sock, child_sockaddr); > + > + if (tv.tv_sec > 0x7fffff00) { > + /* Too close to y2038 problem, old system may not work. */ > + close(parent_sock); > + return 0; > + } > + > +#ifdef SO_TIMESTAMP_OLD > + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { > + /* Test 2a: SO_TIMESTAMP_OLD */ > + set_socket_option(parent_sock, SO_TIMESTAMP_OLD, 1); > + receive_packet(parent_sock, > &get_timeval_from_so_timestamp_old); > + set_socket_option(parent_sock, SO_TIMESTAMP_OLD, 0); > + } > +#ifdef SO_TIMESTAMP_NEW > + else { > + /* Test 2b: SO_TIMESTAMP_NEW */ > + set_socket_option(parent_sock, SO_TIMESTAMP_NEW, 1); > + receive_packet(parent_sock, > &get_timeval_from_so_timestamp_new); > + set_socket_option(parent_sock, SO_TIMESTAMP_NEW, 0); > + } > +#endif /* defined(SO_TIMESTAMP_NEW) */ > +#endif /* defined(SO_TIMESTAMP_OLD) */ > + > + close(parent_sock); > + } else { > + int child_status; > + close(parent_sock); > + > + /* Test 1: SO_TIMESTAMP */ > + set_socket_option(child_sock, SO_TIMESTAMP, 1); > + receive_packet(child_sock, &get_timeval_from_so_timestamp); > + set_socket_option(child_sock, SO_TIMESTAMP, 0); > + > + if (tv.tv_sec > 0x7fffff00) { > + /* Too close to y2038 problem, old system may not work. */ > + close(child_sock); > + return 0; > + } > + > +#ifdef SO_TIMESTAMP_OLD > + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { > + /* Test 2a: SO_TIMESTAMP_OLD */ > + send_current_time(child_sock, parent_sockaddr); > + } > +#ifdef SO_TIMESTAMP_NEW > + else { > + /* Test 2b: SO_TIMESTAMP_NEW */ > + send_current_time(child_sock, parent_sockaddr); > + } > +#endif /* defined(SO_TIMESTAMP_NEW) */ > +#endif /* defined(SO_TIMESTAMP_OLD) */ > + > + close(child_sock); > + > + if (waitpid(pid, &child_status, 0) < 0) { > + int err = errno; > + fprintf(stderr, "Final wait() failed: %s\n", strerror(err)); > + return err; > + } > + return child_status; > + } > + return 0; > +} > -- > 2.28.0.220.ged08abb693-goog > >
Ping again. This specific patch is here: https://patchew.org/QEMU/cover.1597129029.git.scw@google.com/611db81c87911cb38a35e5f761e11b76e1f0d538.1597129029.git.scw@google.com/ If you want to include the first four patches for now and prefer a separate patch set for the pending changes I can split them off into a new thread. Shu-Chun On Thu, Sep 17, 2020 at 12:29 AM Shu-Chun Weng <scw@google.com> wrote: > Ping -- any comments on the four patches start with this? > https://patchew.org/QEMU/cover.1597129029.git.scw@google.com/ > > On Tue, Aug 11, 2020 at 12:10 AM Shu-Chun Weng <scw@google.com> wrote: > >> Both guest options map to host SO_TIMESTAMP while keeping a global bit to >> remember if the guest expects the old or the new format. Don't support >> programs mixing two formats. >> >> Added a multiarch test to verify. >> >> Signed-off-by: Shu-Chun Weng <scw@google.com> >> --- >> v1 -> v2: >> Only keep track of old or new format globally, remove support for >> different >> sockets mixing different formats. >> Fix style problems. >> >> linux-user/alpha/sockbits.h | 8 +- >> linux-user/generic/sockbits.h | 9 +- >> linux-user/hppa/sockbits.h | 8 +- >> linux-user/mips/sockbits.h | 8 +- >> linux-user/sparc/sockbits.h | 8 +- >> linux-user/strace.c | 7 +- >> linux-user/syscall.c | 91 ++++++-- >> tests/tcg/multiarch/socket_timestamp.c | 296 +++++++++++++++++++++++++ >> 8 files changed, 408 insertions(+), 27 deletions(-) >> create mode 100644 tests/tcg/multiarch/socket_timestamp.c >> >> diff --git a/linux-user/alpha/sockbits.h b/linux-user/alpha/sockbits.h >> index d54dc98c09..40f0644df0 100644 >> --- a/linux-user/alpha/sockbits.h >> +++ b/linux-user/alpha/sockbits.h >> @@ -48,8 +48,6 @@ >> #define TARGET_SO_DETACH_FILTER 27 >> >> #define TARGET_SO_PEERNAME 28 >> -#define TARGET_SO_TIMESTAMP 29 >> -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP >> >> #define TARGET_SO_PEERSEC 30 >> #define TARGET_SO_PASSSEC 34 >> @@ -75,6 +73,12 @@ >> /* Instruct lower device to use last 4-bytes of skb data as FCS */ >> #define TARGET_SO_NOFCS 43 >> >> +#define TARGET_SO_TIMESTAMP_OLD 29 >> +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD >> + >> +#define TARGET_SO_TIMESTAMP_NEW 63 >> +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW >> + >> /* TARGET_O_NONBLOCK clashes with the bits used for socket types. >> Therefore we >> * have to define SOCK_NONBLOCK to a different value here. >> */ >> diff --git a/linux-user/generic/sockbits.h b/linux-user/generic/sockbits.h >> index e44733c601..532cf2d3dc 100644 >> --- a/linux-user/generic/sockbits.h >> +++ b/linux-user/generic/sockbits.h >> @@ -49,10 +49,15 @@ >> #define TARGET_SO_DETACH_FILTER 27 >> >> #define TARGET_SO_PEERNAME 28 >> -#define TARGET_SO_TIMESTAMP 29 >> -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP >> >> #define TARGET_SO_ACCEPTCONN 30 >> >> #define TARGET_SO_PEERSEC 31 >> + >> +#define TARGET_SO_TIMESTAMP_OLD 29 >> +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD >> + >> +#define TARGET_SO_TIMESTAMP_NEW 63 >> +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW >> + >> #endif >> diff --git a/linux-user/hppa/sockbits.h b/linux-user/hppa/sockbits.h >> index 23f69a3293..284a47e74e 100644 >> --- a/linux-user/hppa/sockbits.h >> +++ b/linux-user/hppa/sockbits.h >> @@ -29,8 +29,6 @@ >> #define TARGET_SO_BSDCOMPAT 0x400e >> #define TARGET_SO_PASSCRED 0x4010 >> #define TARGET_SO_PEERCRED 0x4011 >> -#define TARGET_SO_TIMESTAMP 0x4012 >> -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP >> #define TARGET_SO_TIMESTAMPNS 0x4013 >> #define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS >> >> @@ -67,6 +65,12 @@ >> >> #define TARGET_SO_CNX_ADVICE 0x402E >> >> +#define TARGET_SO_TIMESTAMP_OLD 0x4012 >> +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD >> + >> +#define TARGET_SO_TIMESTAMP_NEW 0x4038 >> +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW >> + >> /* TARGET_O_NONBLOCK clashes with the bits used for socket types. >> Therefore we >> * have to define SOCK_NONBLOCK to a different value here. >> */ >> diff --git a/linux-user/mips/sockbits.h b/linux-user/mips/sockbits.h >> index 0f022cd598..b4c39d9588 100644 >> --- a/linux-user/mips/sockbits.h >> +++ b/linux-user/mips/sockbits.h >> @@ -61,14 +61,18 @@ >> #define TARGET_SO_DETACH_FILTER 27 >> >> #define TARGET_SO_PEERNAME 28 >> -#define TARGET_SO_TIMESTAMP 29 >> -#define SCM_TIMESTAMP SO_TIMESTAMP >> >> #define TARGET_SO_PEERSEC 30 >> #define TARGET_SO_SNDBUFFORCE 31 >> #define TARGET_SO_RCVBUFFORCE 33 >> #define TARGET_SO_PASSSEC 34 >> >> +#define TARGET_SO_TIMESTAMP_OLD 29 >> +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD >> + >> +#define TARGET_SO_TIMESTAMP_NEW 63 >> +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW >> + >> /** sock_type - Socket types >> * >> * Please notice that for binary compat reasons MIPS has to >> diff --git a/linux-user/sparc/sockbits.h b/linux-user/sparc/sockbits.h >> index 0a822e3e1f..07440efd14 100644 >> --- a/linux-user/sparc/sockbits.h >> +++ b/linux-user/sparc/sockbits.h >> @@ -48,8 +48,6 @@ >> #define TARGET_SO_GET_FILTER TARGET_SO_ATTACH_FILTER >> >> #define TARGET_SO_PEERNAME 0x001c >> -#define TARGET_SO_TIMESTAMP 0x001d >> -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP >> >> #define TARGET_SO_PEERSEC 0x001e >> #define TARGET_SO_PASSSEC 0x001f >> @@ -104,6 +102,12 @@ >> >> #define TARGET_SO_ZEROCOPY 0x003e >> >> +#define TARGET_SO_TIMESTAMP_OLD 0x001d >> +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD >> + >> +#define TARGET_SO_TIMESTAMP_NEW 0x0046 >> +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW >> + >> /* Security levels - as per NRL IPv6 - don't actually do anything */ >> #define TARGET_SO_SECURITY_AUTHENTICATION 0x5001 >> #define TARGET_SO_SECURITY_ENCRYPTION_TRANSPORT 0x5002 >> diff --git a/linux-user/strace.c b/linux-user/strace.c >> index 089fb3968e..a11a5e9e86 100644 >> --- a/linux-user/strace.c >> +++ b/linux-user/strace.c >> @@ -2257,8 +2257,11 @@ print_optint: >> case TARGET_SO_PASSCRED: >> qemu_log("SO_PASSCRED,"); >> goto print_optint; >> - case TARGET_SO_TIMESTAMP: >> - qemu_log("SO_TIMESTAMP,"); >> + case TARGET_SO_TIMESTAMP_OLD: >> + qemu_log("SO_TIMESTAMP_OLD,"); >> + goto print_optint; >> + case TARGET_SO_TIMESTAMP_NEW: >> + qemu_log("SO_TIMESTAMP_NEW,"); >> goto print_optint; >> case TARGET_SO_RCVLOWAT: >> qemu_log("SO_RCVLOWAT,"); >> diff --git a/linux-user/syscall.c b/linux-user/syscall.c >> index cda194a7cc..e6b1a18cc0 100644 >> --- a/linux-user/syscall.c >> +++ b/linux-user/syscall.c >> @@ -1697,6 +1697,18 @@ static inline abi_long target_to_host_cmsg(struct >> msghdr *msgh, >> return 0; >> } >> >> +/* >> + * Linux kernel actually keeps track of whether the old version >> (potentially >> + * 32-bit time_t) or the new version is used for each socket. Instead of >> + * replicate it will all the complexity, we only keep track of one >> global state, >> + * which is enough for guest programs that don't intentionally mix the >> two >> + * versions. >> + */ >> +static enum TargetTimestampVersion { >> + TARGET_TIMESTAMP_OLD, >> + TARGET_TIMESTAMP_NEW, >> +} target_expected_timestamp_version = TARGET_TIMESTAMP_OLD; >> + >> static inline abi_long host_to_target_cmsg(struct target_msghdr >> *target_msgh, >> struct msghdr *msgh) >> { >> @@ -1747,8 +1759,17 @@ static inline abi_long host_to_target_cmsg(struct >> target_msghdr *target_msgh, >> switch (cmsg->cmsg_level) { >> case SOL_SOCKET: >> switch (cmsg->cmsg_type) { >> - case SO_TIMESTAMP: >> - tgt_len = sizeof(struct target_timeval); >> + case SCM_TIMESTAMP: >> + switch (target_expected_timestamp_version) { >> + case TARGET_TIMESTAMP_OLD: >> + tgt_len = sizeof(struct target_timeval); >> + target_cmsg->cmsg_type = >> tswap32(TARGET_SCM_TIMESTAMP_OLD); >> + break; >> + case TARGET_TIMESTAMP_NEW: >> + tgt_len = sizeof(struct target__kernel_sock_timeval); >> + target_cmsg->cmsg_type = >> tswap32(TARGET_SCM_TIMESTAMP_NEW); >> + break; >> + } >> break; >> default: >> break; >> @@ -1782,20 +1803,39 @@ static inline abi_long host_to_target_cmsg(struct >> target_msghdr *target_msgh, >> } >> break; >> } >> - case SO_TIMESTAMP: >> + case SCM_TIMESTAMP: >> { >> struct timeval *tv = (struct timeval *)data; >> - struct target_timeval *target_tv = >> - (struct target_timeval *)target_data; >> - >> - if (len != sizeof(struct timeval) || >> - tgt_len != sizeof(struct target_timeval)) { >> + if (len != sizeof(struct timeval)) { >> goto unimplemented; >> } >> >> - /* copy struct timeval to target */ >> - __put_user(tv->tv_sec, &target_tv->tv_sec); >> - __put_user(tv->tv_usec, &target_tv->tv_usec); >> + switch (target_expected_timestamp_version) { >> + case TARGET_TIMESTAMP_OLD: >> + { >> + struct target_timeval *target_tv = >> + (struct target_timeval *)target_data; >> + if (tgt_len != sizeof(struct target_timeval)) { >> + goto unimplemented; >> + } >> + >> + __put_user(tv->tv_sec, &target_tv->tv_sec); >> + __put_user(tv->tv_usec, &target_tv->tv_usec); >> + break; >> + } >> + case TARGET_TIMESTAMP_NEW: >> + { >> + struct target__kernel_sock_timeval *target_tv = >> + (struct target__kernel_sock_timeval >> *)target_data; >> + if (tgt_len != sizeof(struct >> target__kernel_sock_timeval)) { >> + goto unimplemented; >> + } >> + >> + __put_user(tv->tv_sec, &target_tv->tv_sec); >> + __put_user(tv->tv_usec, &target_tv->tv_usec); >> + break; >> + } >> + } >> break; >> } >> case SCM_CREDENTIALS: >> @@ -1937,6 +1977,8 @@ static abi_long do_setsockopt(int sockfd, int >> level, int optname, >> int val; >> struct ip_mreqn *ip_mreq; >> struct ip_mreq_source *ip_mreq_source; >> + enum TargetTimestampVersion target_timestamp_version = >> + target_expected_timestamp_version; >> >> switch(level) { >> case SOL_TCP: >> @@ -2331,9 +2373,14 @@ set_timeout: >> case TARGET_SO_PASSSEC: >> optname = SO_PASSSEC; >> break; >> - case TARGET_SO_TIMESTAMP: >> - optname = SO_TIMESTAMP; >> - break; >> + case TARGET_SO_TIMESTAMP_OLD: >> + target_timestamp_version = TARGET_TIMESTAMP_OLD; >> + optname = SO_TIMESTAMP; >> + break; >> + case TARGET_SO_TIMESTAMP_NEW: >> + target_timestamp_version = TARGET_TIMESTAMP_NEW; >> + optname = SO_TIMESTAMP; >> + break; >> case TARGET_SO_RCVLOWAT: >> optname = SO_RCVLOWAT; >> break; >> @@ -2346,6 +2393,9 @@ set_timeout: >> if (get_user_u32(val, optval_addr)) >> return -TARGET_EFAULT; >> ret = get_errno(setsockopt(sockfd, SOL_SOCKET, optname, &val, >> sizeof(val))); >> + if (!is_error(ret) && optname == SO_TIMESTAMP) { >> + target_expected_timestamp_version = target_timestamp_version; >> + } >> break; >> #ifdef SOL_NETLINK >> case SOL_NETLINK: >> @@ -2396,6 +2446,7 @@ static abi_long do_getsockopt(int sockfd, int >> level, int optname, >> abi_long ret; >> int len, val; >> socklen_t lv; >> + int timestamp_format_matches = 0; >> >> switch(level) { >> case TARGET_SOL_SOCKET: >> @@ -2576,7 +2627,14 @@ get_timeout: >> case TARGET_SO_PASSCRED: >> optname = SO_PASSCRED; >> goto int_case; >> - case TARGET_SO_TIMESTAMP: >> + case TARGET_SO_TIMESTAMP_OLD: >> + timestamp_format_matches = >> + (target_expected_timestamp_version == >> TARGET_TIMESTAMP_OLD); >> + optname = SO_TIMESTAMP; >> + goto int_case; >> + case TARGET_SO_TIMESTAMP_NEW: >> + timestamp_format_matches = >> + (target_expected_timestamp_version == >> TARGET_TIMESTAMP_NEW); >> optname = SO_TIMESTAMP; >> goto int_case; >> case TARGET_SO_RCVLOWAT: >> @@ -2604,6 +2662,9 @@ get_timeout: >> if (optname == SO_TYPE) { >> val = host_to_target_sock_type(val); >> } >> + if (optname == SO_TIMESTAMP) { >> + val = val && timestamp_format_matches; >> + } >> if (len > lv) >> len = lv; >> if (len == 4) { >> diff --git a/tests/tcg/multiarch/socket_timestamp.c >> b/tests/tcg/multiarch/socket_timestamp.c >> new file mode 100644 >> index 0000000000..71ab1845de >> --- /dev/null >> +++ b/tests/tcg/multiarch/socket_timestamp.c >> @@ -0,0 +1,296 @@ >> +#include <assert.h> >> +#include <errno.h> >> +#include <linux/types.h> >> +#include <netinet/in.h> >> +#include <stdint.h> >> +#include <stdio.h> >> +#include <stdlib.h> >> +#include <string.h> >> +#include <sys/ioctl.h> >> +#include <sys/socket.h> >> +#include <sys/time.h> >> +#include <sys/types.h> >> +#include <sys/wait.h> >> +#include <unistd.h> >> + >> +#ifdef __kernel_old_timeval >> +#define kernel_old_timeval __kernel_old_timeval >> +#else >> +struct kernel_old_timeval { >> + __kernel_long_t tv_sec; >> + __kernel_long_t tv_usec; >> +}; >> +#endif >> + >> +struct kernel_sock_timeval { >> + int64_t tv_sec; >> + int64_t tv_usec; >> +}; >> + >> +int create_udp_socket(struct sockaddr_in *sockaddr) >> +{ >> + socklen_t sockaddr_len; >> + int sock = socket(AF_INET, SOCK_DGRAM, 0); >> + if (sock < 0) { >> + int err = errno; >> + fprintf(stderr, "Failed to create server socket: %s\n", >> strerror(err)); >> + exit(err); >> + } >> + >> + memset(sockaddr, 0, sizeof(*sockaddr)); >> + sockaddr->sin_family = AF_INET; >> + sockaddr->sin_port = htons(0); /* let kernel select a port for us */ >> + sockaddr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); >> + >> + if (bind(sock, (struct sockaddr *)sockaddr, sizeof(*sockaddr)) < 0) { >> + int err = errno; >> + fprintf(stderr, "Failed to bind server socket: %s\n", >> strerror(err)); >> + exit(err); >> + } >> + >> + sockaddr_len = sizeof(*sockaddr); >> + if (getsockname(sock, (struct sockaddr *)sockaddr, &sockaddr_len) < >> 0) { >> + int err = errno; >> + fprintf(stderr, "Failed to get socket name: %s\n", >> strerror(err)); >> + exit(err); >> + } >> + return sock; >> +} >> + >> +/* >> + * Checks that the timestamp in the message is not after the reception >> timestamp >> + * as well as the reception time is within 10 seconds of the message >> time. >> + */ >> +void check_timestamp_difference(const struct timeval *msg_tv, >> + const struct timeval *pkt_tv) >> +{ >> + if (pkt_tv->tv_sec < msg_tv->tv_sec || >> + (pkt_tv->tv_sec == msg_tv->tv_sec && pkt_tv->tv_usec < >> msg_tv->tv_usec)) >> + { >> + fprintf(stderr, >> + "Packet received before sent: %lld.%06lld < >> %lld.%06lld\n", >> + (long long)pkt_tv->tv_sec, (long long)pkt_tv->tv_usec, >> + (long long)msg_tv->tv_sec, (long long)msg_tv->tv_usec); >> + exit(-1); >> + } >> + >> + if (pkt_tv->tv_sec > msg_tv->tv_sec + 10 || >> + (pkt_tv->tv_sec == msg_tv->tv_sec + 10 && >> + pkt_tv->tv_usec > msg_tv->tv_usec)) { >> + fprintf(stderr, >> + "Packet received more than 10 seconds after sent: " >> + "%lld.%06lld > %lld.%06lld + 10\n", >> + (long long)pkt_tv->tv_sec, (long long)pkt_tv->tv_usec, >> + (long long)msg_tv->tv_sec, (long long)msg_tv->tv_usec); >> + exit(-1); >> + } >> +} >> + >> +void send_current_time(int sock, struct sockaddr_in server_sockaddr) >> +{ >> + struct timeval tv = {0, 0}; >> + gettimeofday(&tv, NULL); >> + sendto(sock, &tv, sizeof(tv), 0, (struct sockaddr *)&server_sockaddr, >> + sizeof(server_sockaddr)); >> +} >> + >> +typedef void (*get_timeval_t)(const struct cmsghdr *cmsg, struct timeval >> *tv); >> + >> + >> +void receive_packet(int sock, get_timeval_t get_timeval) >> +{ >> + struct msghdr msg = {0}; >> + >> + char iobuf[1024]; >> + struct iovec iov; >> + >> + union { >> + /* >> + * 128 bytes are enough for all existing >> + * timeval/timespec/scm_timestamping structures. >> + */ >> + char cmsg_buf[CMSG_SPACE(128)]; >> + struct cmsghdr align; >> + } u; >> + struct cmsghdr *cmsg; >> + struct timeval msg_tv, pkt_tv; >> + >> + int res; >> + >> + iov.iov_base = iobuf; >> + iov.iov_len = sizeof(iobuf); >> + >> + msg.msg_iov = &iov; >> + msg.msg_iovlen = 1; >> + msg.msg_control = (caddr_t)u.cmsg_buf; >> + msg.msg_controllen = sizeof(u.cmsg_buf); >> + >> + res = recvmsg(sock, &msg, 0); >> + if (res < 0) { >> + int err = errno; >> + fprintf(stderr, "Failed to receive packet: %s\n", strerror(err)); >> + exit(err); >> + } >> + >> + assert(res == sizeof(struct timeval)); >> + assert(iov.iov_base == iobuf); >> + memcpy(&msg_tv, iov.iov_base, sizeof(msg_tv)); >> + printf("Message timestamp: %lld.%06lld\n", >> + (long long)msg_tv.tv_sec, (long long)msg_tv.tv_usec); >> + >> + cmsg = CMSG_FIRSTHDR(&msg); >> + assert(cmsg); >> + (*get_timeval)(cmsg, &pkt_tv); >> + printf("Packet timestamp: %lld.%06lld\n", >> + (long long)pkt_tv.tv_sec, (long long)pkt_tv.tv_usec); >> + >> + check_timestamp_difference(&msg_tv, &pkt_tv); >> +} >> + >> +void get_timeval_from_so_timestamp(const struct cmsghdr *cmsg, >> + struct timeval *tv) >> +{ >> + assert(cmsg->cmsg_level == SOL_SOCKET); >> + assert(cmsg->cmsg_type == SCM_TIMESTAMP); >> + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))); >> + memcpy(tv, CMSG_DATA(cmsg), sizeof(*tv)); >> +} >> + >> +#ifdef SO_TIMESTAMP_OLD >> +void get_timeval_from_so_timestamp_old(const struct cmsghdr *cmsg, >> + struct timeval *tv) >> +{ >> + struct kernel_old_timeval old_tv; >> + assert(cmsg->cmsg_level == SOL_SOCKET); >> + assert(cmsg->cmsg_type == SO_TIMESTAMP_OLD); >> + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(old_tv))); >> + >> + memcpy(&old_tv, CMSG_DATA(cmsg), sizeof(old_tv)); >> + tv->tv_sec = old_tv.tv_sec; >> + tv->tv_usec = old_tv.tv_usec; >> +} >> + >> +#ifdef SO_TIMESTAMP_NEW >> +void get_timeval_from_so_timestamp_new(const struct cmsghdr *cmsg, >> + struct timeval *tv) >> +{ >> + struct kernel_sock_timeval sock_tv; >> + assert(cmsg->cmsg_level == SOL_SOCKET); >> + assert(cmsg->cmsg_type == SO_TIMESTAMP_NEW); >> + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(sock_tv))); >> + >> + memcpy(&sock_tv, CMSG_DATA(cmsg), sizeof(sock_tv)); >> + tv->tv_sec = sock_tv.tv_sec; >> + tv->tv_usec = sock_tv.tv_usec; >> +} >> +#endif /* defined(SO_TIMESTAMP_NEW) */ >> +#endif /* defined(SO_TIMESTAMP_OLD) */ >> + >> +void set_socket_option(int sock, int sockopt, int on) >> +{ >> + socklen_t len; >> + int val = on; >> + if (setsockopt(sock, SOL_SOCKET, sockopt, &val, sizeof(val)) < 0) { >> + int err = errno; >> + fprintf(stderr, "Failed to setsockopt %d (%s): %s\n", >> + sockopt, on ? "on" : "off", strerror(err)); >> + exit(err); >> + } >> + >> + len = sizeof(val); >> + val = -1; >> + if (getsockopt(sock, SOL_SOCKET, sockopt, &val, &len) < 0) { >> + int err = errno; >> + fprintf(stderr, "Failed to getsockopt (%d): %s\n", sock, >> strerror(err)); >> + exit(err); >> + } >> + assert(len == sizeof(val)); >> + assert(val == on); >> +} >> + >> +int main(int argc, char **argv) >> +{ >> + int parent_sock, child_sock; >> + struct sockaddr_in parent_sockaddr, child_sockaddr; >> + int pid; >> + struct timeval tv = {0, 0}; >> + gettimeofday(&tv, NULL); >> + >> + parent_sock = create_udp_socket(&parent_sockaddr); >> + child_sock = create_udp_socket(&child_sockaddr); >> + >> + printf("Parent sock bound to port %d\nChild sock bound to port %d\n", >> + parent_sockaddr.sin_port, child_sockaddr.sin_port); >> + >> + pid = fork(); >> + if (pid < 0) { >> + fprintf(stderr, "SKIPPED. Failed to fork: %s\n", >> strerror(errno)); >> + } else if (pid == 0) { >> + close(child_sock); >> + >> + /* Test 1: SO_TIMESTAMP */ >> + send_current_time(parent_sock, child_sockaddr); >> + >> + if (tv.tv_sec > 0x7fffff00) { >> + /* Too close to y2038 problem, old system may not work. */ >> + close(parent_sock); >> + return 0; >> + } >> + >> +#ifdef SO_TIMESTAMP_OLD >> + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { >> + /* Test 2a: SO_TIMESTAMP_OLD */ >> + set_socket_option(parent_sock, SO_TIMESTAMP_OLD, 1); >> + receive_packet(parent_sock, >> &get_timeval_from_so_timestamp_old); >> + set_socket_option(parent_sock, SO_TIMESTAMP_OLD, 0); >> + } >> +#ifdef SO_TIMESTAMP_NEW >> + else { >> + /* Test 2b: SO_TIMESTAMP_NEW */ >> + set_socket_option(parent_sock, SO_TIMESTAMP_NEW, 1); >> + receive_packet(parent_sock, >> &get_timeval_from_so_timestamp_new); >> + set_socket_option(parent_sock, SO_TIMESTAMP_NEW, 0); >> + } >> +#endif /* defined(SO_TIMESTAMP_NEW) */ >> +#endif /* defined(SO_TIMESTAMP_OLD) */ >> + >> + close(parent_sock); >> + } else { >> + int child_status; >> + close(parent_sock); >> + >> + /* Test 1: SO_TIMESTAMP */ >> + set_socket_option(child_sock, SO_TIMESTAMP, 1); >> + receive_packet(child_sock, &get_timeval_from_so_timestamp); >> + set_socket_option(child_sock, SO_TIMESTAMP, 0); >> + >> + if (tv.tv_sec > 0x7fffff00) { >> + /* Too close to y2038 problem, old system may not work. */ >> + close(child_sock); >> + return 0; >> + } >> + >> +#ifdef SO_TIMESTAMP_OLD >> + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { >> + /* Test 2a: SO_TIMESTAMP_OLD */ >> + send_current_time(child_sock, parent_sockaddr); >> + } >> +#ifdef SO_TIMESTAMP_NEW >> + else { >> + /* Test 2b: SO_TIMESTAMP_NEW */ >> + send_current_time(child_sock, parent_sockaddr); >> + } >> +#endif /* defined(SO_TIMESTAMP_NEW) */ >> +#endif /* defined(SO_TIMESTAMP_OLD) */ >> + >> + close(child_sock); >> + >> + if (waitpid(pid, &child_status, 0) < 0) { >> + int err = errno; >> + fprintf(stderr, "Final wait() failed: %s\n", strerror(err)); >> + return err; >> + } >> + return child_status; >> + } >> + return 0; >> +} >> -- >> 2.28.0.220.ged08abb693-goog >> >>
diff --git a/linux-user/alpha/sockbits.h b/linux-user/alpha/sockbits.h index d54dc98c09..40f0644df0 100644 --- a/linux-user/alpha/sockbits.h +++ b/linux-user/alpha/sockbits.h @@ -48,8 +48,6 @@ #define TARGET_SO_DETACH_FILTER 27 #define TARGET_SO_PEERNAME 28 -#define TARGET_SO_TIMESTAMP 29 -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP #define TARGET_SO_PEERSEC 30 #define TARGET_SO_PASSSEC 34 @@ -75,6 +73,12 @@ /* Instruct lower device to use last 4-bytes of skb data as FCS */ #define TARGET_SO_NOFCS 43 +#define TARGET_SO_TIMESTAMP_OLD 29 +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD + +#define TARGET_SO_TIMESTAMP_NEW 63 +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW + /* TARGET_O_NONBLOCK clashes with the bits used for socket types. Therefore we * have to define SOCK_NONBLOCK to a different value here. */ diff --git a/linux-user/generic/sockbits.h b/linux-user/generic/sockbits.h index e44733c601..532cf2d3dc 100644 --- a/linux-user/generic/sockbits.h +++ b/linux-user/generic/sockbits.h @@ -49,10 +49,15 @@ #define TARGET_SO_DETACH_FILTER 27 #define TARGET_SO_PEERNAME 28 -#define TARGET_SO_TIMESTAMP 29 -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP #define TARGET_SO_ACCEPTCONN 30 #define TARGET_SO_PEERSEC 31 + +#define TARGET_SO_TIMESTAMP_OLD 29 +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD + +#define TARGET_SO_TIMESTAMP_NEW 63 +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW + #endif diff --git a/linux-user/hppa/sockbits.h b/linux-user/hppa/sockbits.h index 23f69a3293..284a47e74e 100644 --- a/linux-user/hppa/sockbits.h +++ b/linux-user/hppa/sockbits.h @@ -29,8 +29,6 @@ #define TARGET_SO_BSDCOMPAT 0x400e #define TARGET_SO_PASSCRED 0x4010 #define TARGET_SO_PEERCRED 0x4011 -#define TARGET_SO_TIMESTAMP 0x4012 -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP #define TARGET_SO_TIMESTAMPNS 0x4013 #define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS @@ -67,6 +65,12 @@ #define TARGET_SO_CNX_ADVICE 0x402E +#define TARGET_SO_TIMESTAMP_OLD 0x4012 +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD + +#define TARGET_SO_TIMESTAMP_NEW 0x4038 +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW + /* TARGET_O_NONBLOCK clashes with the bits used for socket types. Therefore we * have to define SOCK_NONBLOCK to a different value here. */ diff --git a/linux-user/mips/sockbits.h b/linux-user/mips/sockbits.h index 0f022cd598..b4c39d9588 100644 --- a/linux-user/mips/sockbits.h +++ b/linux-user/mips/sockbits.h @@ -61,14 +61,18 @@ #define TARGET_SO_DETACH_FILTER 27 #define TARGET_SO_PEERNAME 28 -#define TARGET_SO_TIMESTAMP 29 -#define SCM_TIMESTAMP SO_TIMESTAMP #define TARGET_SO_PEERSEC 30 #define TARGET_SO_SNDBUFFORCE 31 #define TARGET_SO_RCVBUFFORCE 33 #define TARGET_SO_PASSSEC 34 +#define TARGET_SO_TIMESTAMP_OLD 29 +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD + +#define TARGET_SO_TIMESTAMP_NEW 63 +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW + /** sock_type - Socket types * * Please notice that for binary compat reasons MIPS has to diff --git a/linux-user/sparc/sockbits.h b/linux-user/sparc/sockbits.h index 0a822e3e1f..07440efd14 100644 --- a/linux-user/sparc/sockbits.h +++ b/linux-user/sparc/sockbits.h @@ -48,8 +48,6 @@ #define TARGET_SO_GET_FILTER TARGET_SO_ATTACH_FILTER #define TARGET_SO_PEERNAME 0x001c -#define TARGET_SO_TIMESTAMP 0x001d -#define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP #define TARGET_SO_PEERSEC 0x001e #define TARGET_SO_PASSSEC 0x001f @@ -104,6 +102,12 @@ #define TARGET_SO_ZEROCOPY 0x003e +#define TARGET_SO_TIMESTAMP_OLD 0x001d +#define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD + +#define TARGET_SO_TIMESTAMP_NEW 0x0046 +#define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW + /* Security levels - as per NRL IPv6 - don't actually do anything */ #define TARGET_SO_SECURITY_AUTHENTICATION 0x5001 #define TARGET_SO_SECURITY_ENCRYPTION_TRANSPORT 0x5002 diff --git a/linux-user/strace.c b/linux-user/strace.c index 089fb3968e..a11a5e9e86 100644 --- a/linux-user/strace.c +++ b/linux-user/strace.c @@ -2257,8 +2257,11 @@ print_optint: case TARGET_SO_PASSCRED: qemu_log("SO_PASSCRED,"); goto print_optint; - case TARGET_SO_TIMESTAMP: - qemu_log("SO_TIMESTAMP,"); + case TARGET_SO_TIMESTAMP_OLD: + qemu_log("SO_TIMESTAMP_OLD,"); + goto print_optint; + case TARGET_SO_TIMESTAMP_NEW: + qemu_log("SO_TIMESTAMP_NEW,"); goto print_optint; case TARGET_SO_RCVLOWAT: qemu_log("SO_RCVLOWAT,"); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index cda194a7cc..e6b1a18cc0 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -1697,6 +1697,18 @@ static inline abi_long target_to_host_cmsg(struct msghdr *msgh, return 0; } +/* + * Linux kernel actually keeps track of whether the old version (potentially + * 32-bit time_t) or the new version is used for each socket. Instead of + * replicate it will all the complexity, we only keep track of one global state, + * which is enough for guest programs that don't intentionally mix the two + * versions. + */ +static enum TargetTimestampVersion { + TARGET_TIMESTAMP_OLD, + TARGET_TIMESTAMP_NEW, +} target_expected_timestamp_version = TARGET_TIMESTAMP_OLD; + static inline abi_long host_to_target_cmsg(struct target_msghdr *target_msgh, struct msghdr *msgh) { @@ -1747,8 +1759,17 @@ static inline abi_long host_to_target_cmsg(struct target_msghdr *target_msgh, switch (cmsg->cmsg_level) { case SOL_SOCKET: switch (cmsg->cmsg_type) { - case SO_TIMESTAMP: - tgt_len = sizeof(struct target_timeval); + case SCM_TIMESTAMP: + switch (target_expected_timestamp_version) { + case TARGET_TIMESTAMP_OLD: + tgt_len = sizeof(struct target_timeval); + target_cmsg->cmsg_type = tswap32(TARGET_SCM_TIMESTAMP_OLD); + break; + case TARGET_TIMESTAMP_NEW: + tgt_len = sizeof(struct target__kernel_sock_timeval); + target_cmsg->cmsg_type = tswap32(TARGET_SCM_TIMESTAMP_NEW); + break; + } break; default: break; @@ -1782,20 +1803,39 @@ static inline abi_long host_to_target_cmsg(struct target_msghdr *target_msgh, } break; } - case SO_TIMESTAMP: + case SCM_TIMESTAMP: { struct timeval *tv = (struct timeval *)data; - struct target_timeval *target_tv = - (struct target_timeval *)target_data; - - if (len != sizeof(struct timeval) || - tgt_len != sizeof(struct target_timeval)) { + if (len != sizeof(struct timeval)) { goto unimplemented; } - /* copy struct timeval to target */ - __put_user(tv->tv_sec, &target_tv->tv_sec); - __put_user(tv->tv_usec, &target_tv->tv_usec); + switch (target_expected_timestamp_version) { + case TARGET_TIMESTAMP_OLD: + { + struct target_timeval *target_tv = + (struct target_timeval *)target_data; + if (tgt_len != sizeof(struct target_timeval)) { + goto unimplemented; + } + + __put_user(tv->tv_sec, &target_tv->tv_sec); + __put_user(tv->tv_usec, &target_tv->tv_usec); + break; + } + case TARGET_TIMESTAMP_NEW: + { + struct target__kernel_sock_timeval *target_tv = + (struct target__kernel_sock_timeval *)target_data; + if (tgt_len != sizeof(struct target__kernel_sock_timeval)) { + goto unimplemented; + } + + __put_user(tv->tv_sec, &target_tv->tv_sec); + __put_user(tv->tv_usec, &target_tv->tv_usec); + break; + } + } break; } case SCM_CREDENTIALS: @@ -1937,6 +1977,8 @@ static abi_long do_setsockopt(int sockfd, int level, int optname, int val; struct ip_mreqn *ip_mreq; struct ip_mreq_source *ip_mreq_source; + enum TargetTimestampVersion target_timestamp_version = + target_expected_timestamp_version; switch(level) { case SOL_TCP: @@ -2331,9 +2373,14 @@ set_timeout: case TARGET_SO_PASSSEC: optname = SO_PASSSEC; break; - case TARGET_SO_TIMESTAMP: - optname = SO_TIMESTAMP; - break; + case TARGET_SO_TIMESTAMP_OLD: + target_timestamp_version = TARGET_TIMESTAMP_OLD; + optname = SO_TIMESTAMP; + break; + case TARGET_SO_TIMESTAMP_NEW: + target_timestamp_version = TARGET_TIMESTAMP_NEW; + optname = SO_TIMESTAMP; + break; case TARGET_SO_RCVLOWAT: optname = SO_RCVLOWAT; break; @@ -2346,6 +2393,9 @@ set_timeout: if (get_user_u32(val, optval_addr)) return -TARGET_EFAULT; ret = get_errno(setsockopt(sockfd, SOL_SOCKET, optname, &val, sizeof(val))); + if (!is_error(ret) && optname == SO_TIMESTAMP) { + target_expected_timestamp_version = target_timestamp_version; + } break; #ifdef SOL_NETLINK case SOL_NETLINK: @@ -2396,6 +2446,7 @@ static abi_long do_getsockopt(int sockfd, int level, int optname, abi_long ret; int len, val; socklen_t lv; + int timestamp_format_matches = 0; switch(level) { case TARGET_SOL_SOCKET: @@ -2576,7 +2627,14 @@ get_timeout: case TARGET_SO_PASSCRED: optname = SO_PASSCRED; goto int_case; - case TARGET_SO_TIMESTAMP: + case TARGET_SO_TIMESTAMP_OLD: + timestamp_format_matches = + (target_expected_timestamp_version == TARGET_TIMESTAMP_OLD); + optname = SO_TIMESTAMP; + goto int_case; + case TARGET_SO_TIMESTAMP_NEW: + timestamp_format_matches = + (target_expected_timestamp_version == TARGET_TIMESTAMP_NEW); optname = SO_TIMESTAMP; goto int_case; case TARGET_SO_RCVLOWAT: @@ -2604,6 +2662,9 @@ get_timeout: if (optname == SO_TYPE) { val = host_to_target_sock_type(val); } + if (optname == SO_TIMESTAMP) { + val = val && timestamp_format_matches; + } if (len > lv) len = lv; if (len == 4) { diff --git a/tests/tcg/multiarch/socket_timestamp.c b/tests/tcg/multiarch/socket_timestamp.c new file mode 100644 index 0000000000..71ab1845de --- /dev/null +++ b/tests/tcg/multiarch/socket_timestamp.c @@ -0,0 +1,296 @@ +#include <assert.h> +#include <errno.h> +#include <linux/types.h> +#include <netinet/in.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#ifdef __kernel_old_timeval +#define kernel_old_timeval __kernel_old_timeval +#else +struct kernel_old_timeval { + __kernel_long_t tv_sec; + __kernel_long_t tv_usec; +}; +#endif + +struct kernel_sock_timeval { + int64_t tv_sec; + int64_t tv_usec; +}; + +int create_udp_socket(struct sockaddr_in *sockaddr) +{ + socklen_t sockaddr_len; + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + int err = errno; + fprintf(stderr, "Failed to create server socket: %s\n", strerror(err)); + exit(err); + } + + memset(sockaddr, 0, sizeof(*sockaddr)); + sockaddr->sin_family = AF_INET; + sockaddr->sin_port = htons(0); /* let kernel select a port for us */ + sockaddr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(sock, (struct sockaddr *)sockaddr, sizeof(*sockaddr)) < 0) { + int err = errno; + fprintf(stderr, "Failed to bind server socket: %s\n", strerror(err)); + exit(err); + } + + sockaddr_len = sizeof(*sockaddr); + if (getsockname(sock, (struct sockaddr *)sockaddr, &sockaddr_len) < 0) { + int err = errno; + fprintf(stderr, "Failed to get socket name: %s\n", strerror(err)); + exit(err); + } + return sock; +} + +/* + * Checks that the timestamp in the message is not after the reception timestamp + * as well as the reception time is within 10 seconds of the message time. + */ +void check_timestamp_difference(const struct timeval *msg_tv, + const struct timeval *pkt_tv) +{ + if (pkt_tv->tv_sec < msg_tv->tv_sec || + (pkt_tv->tv_sec == msg_tv->tv_sec && pkt_tv->tv_usec < msg_tv->tv_usec)) + { + fprintf(stderr, + "Packet received before sent: %lld.%06lld < %lld.%06lld\n", + (long long)pkt_tv->tv_sec, (long long)pkt_tv->tv_usec, + (long long)msg_tv->tv_sec, (long long)msg_tv->tv_usec); + exit(-1); + } + + if (pkt_tv->tv_sec > msg_tv->tv_sec + 10 || + (pkt_tv->tv_sec == msg_tv->tv_sec + 10 && + pkt_tv->tv_usec > msg_tv->tv_usec)) { + fprintf(stderr, + "Packet received more than 10 seconds after sent: " + "%lld.%06lld > %lld.%06lld + 10\n", + (long long)pkt_tv->tv_sec, (long long)pkt_tv->tv_usec, + (long long)msg_tv->tv_sec, (long long)msg_tv->tv_usec); + exit(-1); + } +} + +void send_current_time(int sock, struct sockaddr_in server_sockaddr) +{ + struct timeval tv = {0, 0}; + gettimeofday(&tv, NULL); + sendto(sock, &tv, sizeof(tv), 0, (struct sockaddr *)&server_sockaddr, + sizeof(server_sockaddr)); +} + +typedef void (*get_timeval_t)(const struct cmsghdr *cmsg, struct timeval *tv); + + +void receive_packet(int sock, get_timeval_t get_timeval) +{ + struct msghdr msg = {0}; + + char iobuf[1024]; + struct iovec iov; + + union { + /* + * 128 bytes are enough for all existing + * timeval/timespec/scm_timestamping structures. + */ + char cmsg_buf[CMSG_SPACE(128)]; + struct cmsghdr align; + } u; + struct cmsghdr *cmsg; + struct timeval msg_tv, pkt_tv; + + int res; + + iov.iov_base = iobuf; + iov.iov_len = sizeof(iobuf); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = (caddr_t)u.cmsg_buf; + msg.msg_controllen = sizeof(u.cmsg_buf); + + res = recvmsg(sock, &msg, 0); + if (res < 0) { + int err = errno; + fprintf(stderr, "Failed to receive packet: %s\n", strerror(err)); + exit(err); + } + + assert(res == sizeof(struct timeval)); + assert(iov.iov_base == iobuf); + memcpy(&msg_tv, iov.iov_base, sizeof(msg_tv)); + printf("Message timestamp: %lld.%06lld\n", + (long long)msg_tv.tv_sec, (long long)msg_tv.tv_usec); + + cmsg = CMSG_FIRSTHDR(&msg); + assert(cmsg); + (*get_timeval)(cmsg, &pkt_tv); + printf("Packet timestamp: %lld.%06lld\n", + (long long)pkt_tv.tv_sec, (long long)pkt_tv.tv_usec); + + check_timestamp_difference(&msg_tv, &pkt_tv); +} + +void get_timeval_from_so_timestamp(const struct cmsghdr *cmsg, + struct timeval *tv) +{ + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SCM_TIMESTAMP); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))); + memcpy(tv, CMSG_DATA(cmsg), sizeof(*tv)); +} + +#ifdef SO_TIMESTAMP_OLD +void get_timeval_from_so_timestamp_old(const struct cmsghdr *cmsg, + struct timeval *tv) +{ + struct kernel_old_timeval old_tv; + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SO_TIMESTAMP_OLD); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(old_tv))); + + memcpy(&old_tv, CMSG_DATA(cmsg), sizeof(old_tv)); + tv->tv_sec = old_tv.tv_sec; + tv->tv_usec = old_tv.tv_usec; +} + +#ifdef SO_TIMESTAMP_NEW +void get_timeval_from_so_timestamp_new(const struct cmsghdr *cmsg, + struct timeval *tv) +{ + struct kernel_sock_timeval sock_tv; + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SO_TIMESTAMP_NEW); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(sock_tv))); + + memcpy(&sock_tv, CMSG_DATA(cmsg), sizeof(sock_tv)); + tv->tv_sec = sock_tv.tv_sec; + tv->tv_usec = sock_tv.tv_usec; +} +#endif /* defined(SO_TIMESTAMP_NEW) */ +#endif /* defined(SO_TIMESTAMP_OLD) */ + +void set_socket_option(int sock, int sockopt, int on) +{ + socklen_t len; + int val = on; + if (setsockopt(sock, SOL_SOCKET, sockopt, &val, sizeof(val)) < 0) { + int err = errno; + fprintf(stderr, "Failed to setsockopt %d (%s): %s\n", + sockopt, on ? "on" : "off", strerror(err)); + exit(err); + } + + len = sizeof(val); + val = -1; + if (getsockopt(sock, SOL_SOCKET, sockopt, &val, &len) < 0) { + int err = errno; + fprintf(stderr, "Failed to getsockopt (%d): %s\n", sock, strerror(err)); + exit(err); + } + assert(len == sizeof(val)); + assert(val == on); +} + +int main(int argc, char **argv) +{ + int parent_sock, child_sock; + struct sockaddr_in parent_sockaddr, child_sockaddr; + int pid; + struct timeval tv = {0, 0}; + gettimeofday(&tv, NULL); + + parent_sock = create_udp_socket(&parent_sockaddr); + child_sock = create_udp_socket(&child_sockaddr); + + printf("Parent sock bound to port %d\nChild sock bound to port %d\n", + parent_sockaddr.sin_port, child_sockaddr.sin_port); + + pid = fork(); + if (pid < 0) { + fprintf(stderr, "SKIPPED. Failed to fork: %s\n", strerror(errno)); + } else if (pid == 0) { + close(child_sock); + + /* Test 1: SO_TIMESTAMP */ + send_current_time(parent_sock, child_sockaddr); + + if (tv.tv_sec > 0x7fffff00) { + /* Too close to y2038 problem, old system may not work. */ + close(parent_sock); + return 0; + } + +#ifdef SO_TIMESTAMP_OLD + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { + /* Test 2a: SO_TIMESTAMP_OLD */ + set_socket_option(parent_sock, SO_TIMESTAMP_OLD, 1); + receive_packet(parent_sock, &get_timeval_from_so_timestamp_old); + set_socket_option(parent_sock, SO_TIMESTAMP_OLD, 0); + } +#ifdef SO_TIMESTAMP_NEW + else { + /* Test 2b: SO_TIMESTAMP_NEW */ + set_socket_option(parent_sock, SO_TIMESTAMP_NEW, 1); + receive_packet(parent_sock, &get_timeval_from_so_timestamp_new); + set_socket_option(parent_sock, SO_TIMESTAMP_NEW, 0); + } +#endif /* defined(SO_TIMESTAMP_NEW) */ +#endif /* defined(SO_TIMESTAMP_OLD) */ + + close(parent_sock); + } else { + int child_status; + close(parent_sock); + + /* Test 1: SO_TIMESTAMP */ + set_socket_option(child_sock, SO_TIMESTAMP, 1); + receive_packet(child_sock, &get_timeval_from_so_timestamp); + set_socket_option(child_sock, SO_TIMESTAMP, 0); + + if (tv.tv_sec > 0x7fffff00) { + /* Too close to y2038 problem, old system may not work. */ + close(child_sock); + return 0; + } + +#ifdef SO_TIMESTAMP_OLD + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { + /* Test 2a: SO_TIMESTAMP_OLD */ + send_current_time(child_sock, parent_sockaddr); + } +#ifdef SO_TIMESTAMP_NEW + else { + /* Test 2b: SO_TIMESTAMP_NEW */ + send_current_time(child_sock, parent_sockaddr); + } +#endif /* defined(SO_TIMESTAMP_NEW) */ +#endif /* defined(SO_TIMESTAMP_OLD) */ + + close(child_sock); + + if (waitpid(pid, &child_status, 0) < 0) { + int err = errno; + fprintf(stderr, "Final wait() failed: %s\n", strerror(err)); + return err; + } + return child_status; + } + return 0; +}
Both guest options map to host SO_TIMESTAMP while keeping a global bit to remember if the guest expects the old or the new format. Don't support programs mixing two formats. Added a multiarch test to verify. Signed-off-by: Shu-Chun Weng <scw@google.com> --- v1 -> v2: Only keep track of old or new format globally, remove support for different sockets mixing different formats. Fix style problems. linux-user/alpha/sockbits.h | 8 +- linux-user/generic/sockbits.h | 9 +- linux-user/hppa/sockbits.h | 8 +- linux-user/mips/sockbits.h | 8 +- linux-user/sparc/sockbits.h | 8 +- linux-user/strace.c | 7 +- linux-user/syscall.c | 91 ++++++-- tests/tcg/multiarch/socket_timestamp.c | 296 +++++++++++++++++++++++++ 8 files changed, 408 insertions(+), 27 deletions(-) create mode 100644 tests/tcg/multiarch/socket_timestamp.c