* [PATCH v2 0/8] fcntl, sockopt, and ioctl options @ 2020-08-11 7:09 Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls Shu-Chun Weng ` (8 more replies) 0 siblings, 9 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent Hi Laurent, This is a series of 8 patches in 4 groups, putting into a single thread for easier tracking. [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls An incidental follow up on https://lists.nongnu.org/archive/html/qemu-devel/2019-09/msg01925.html [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option [PATCH v2 3/8] linux-user: add missing IPv6 get/setsockopt option [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() Updated https://lists.nongnu.org/archive/html/qemu-devel/2019-09/msg01317.html to consistently add them in get/setsockopt [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW [PATCH v2 6/8] linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING Updated https://lists.nongnu.org/archive/html/qemu-devel/2019-09/msg01319.html to only use TARGET_SO_*_OLD/NEW [PATCH v2 7/8] thunk: supports flexible arrays [PATCH v2 8/8] linux-user: Add support for SIOCETHTOOL ioctl Updated https://lists.nongnu.org/archive/html/qemu-devel/2019-08/msg05090.html v1 -> v2: Address comments on the first 5 (was 3) patches. Fix style problems. Shu-Chun Weng (8): linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls linux-user: add missing UDP get/setsockopt option linux-user: add missing IPv6 get/setsockopt option linux-user: Add IPv6 options to do_print_sockopt() linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING thunk: supports flexible arrays linux-user: Add support for SIOCETHTOOL ioctl include/exec/user/thunk.h | 24 + linux-user/Makefile.objs | 3 +- linux-user/alpha/sockbits.h | 21 +- linux-user/ethtool.c | 840 +++++++++++++++++++++++++ linux-user/ethtool.h | 20 + linux-user/ethtool_entries.h | 107 ++++ linux-user/generic/sockbits.h | 17 +- linux-user/hppa/sockbits.h | 20 +- linux-user/ioctls.h | 2 + linux-user/mips/sockbits.h | 16 +- linux-user/qemu.h | 1 + linux-user/sparc/sockbits.h | 21 +- linux-user/strace.c | 188 +++++- linux-user/syscall.c | 286 ++++++++- linux-user/syscall_defs.h | 26 +- linux-user/syscall_types.h | 280 +++++++++ tests/tcg/multiarch/ethtool.c | 423 +++++++++++++ tests/tcg/multiarch/socket_timestamp.c | 540 ++++++++++++++++ thunk.c | 152 ++++- 19 files changed, 2916 insertions(+), 71 deletions(-) create mode 100644 linux-user/ethtool.c create mode 100644 linux-user/ethtool.h create mode 100644 linux-user/ethtool_entries.h create mode 100644 tests/tcg/multiarch/ethtool.c create mode 100644 tests/tcg/multiarch/socket_timestamp.c -- 2.28.0.220.ged08abb693-goog ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng @ 2020-08-11 7:09 ` Shu-Chun Weng 2020-08-11 14:09 ` Laurent Vivier 2020-08-11 7:09 ` [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option Shu-Chun Weng ` (7 subsequent siblings) 8 siblings, 1 reply; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent Also reorder blocks so that they are all in the same order everywhere. Signed-off-by: Shu-Chun Weng <scw@google.com> --- v1 -> v2: Updated print_fcntl(). linux-user/strace.c | 55 ++++++++++++++++++++++++++++++++------- linux-user/syscall.c | 10 +++++++ linux-user/syscall_defs.h | 14 +++++----- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/linux-user/strace.c b/linux-user/strace.c index 13981341b3..4fff24b880 100644 --- a/linux-user/strace.c +++ b/linux-user/strace.c @@ -1684,6 +1684,18 @@ print_fcntl(const struct syscallname *name, qemu_log("F_SETFL,"); print_open_flags(arg2, 1); break; + case TARGET_F_OFD_GETLK: + qemu_log("F_OFD_GETLK,"); + print_pointer(arg2, 1); + break; + case TARGET_F_OFD_SETLK: + qemu_log("F_OFD_SETLK,"); + print_pointer(arg2, 1); + break; + case TARGET_F_OFD_SETLKW: + qemu_log("F_OFD_SETLKW,"); + print_pointer(arg2, 1); + break; case TARGET_F_GETLK: qemu_log("F_GETLK,"); print_pointer(arg2, 1); @@ -1726,26 +1738,51 @@ print_fcntl(const struct syscallname *name, #endif case TARGET_F_SETLEASE: qemu_log("F_SETLEASE,"); - print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); break; case TARGET_F_GETLEASE: qemu_log("F_GETLEASE"); break; - case TARGET_F_SETPIPE_SZ: - qemu_log("F_SETPIPE_SZ,"); - print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); - break; - case TARGET_F_GETPIPE_SZ: - qemu_log("F_GETPIPE_SZ"); - break; +#ifdef F_DUPFD_CLOEXEC case TARGET_F_DUPFD_CLOEXEC: qemu_log("F_DUPFD_CLOEXEC,"); print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); break; +#endif case TARGET_F_NOTIFY: qemu_log("F_NOTIFY,"); - print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); break; +#ifdef F_GETOWN_EX + case TARGET_F_GETOWN_EX: + qemu_log("F_GETOWN_EX,"); + print_pointer(arg2, 1); + break; +#endif +#ifdef F_SETOWN_EX + case TARGET_F_SETOWN_EX: + qemu_log("F_SETOWN_EX,"); + print_pointer(arg2, 1); + break; +#endif +#ifdef F_SETPIPE_SZ + case TARGET_F_SETPIPE_SZ: + qemu_log("F_SETPIPE_SZ,"); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); + break; + case TARGET_F_GETPIPE_SZ: + qemu_log("F_GETPIPE_SZ"); + break; +#endif +#ifdef F_ADD_SEALS + case TARGET_F_ADD_SEALS: + qemu_log("F_ADD_SEALS,"); + print_raw_param("0x"TARGET_ABI_FMT_lx, arg2, 1); + break; + case TARGET_F_GET_SEALS: + qemu_log("F_GET_SEALS"); + break; +#endif default: print_raw_param(TARGET_ABI_FMT_ld, arg1, 0); print_pointer(arg2, 1); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 945fc25279..5645862798 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -6305,6 +6305,14 @@ static int target_to_host_fcntl_cmd(int cmd) case TARGET_F_GETPIPE_SZ: ret = F_GETPIPE_SZ; break; +#endif +#ifdef F_ADD_SEALS + case TARGET_F_ADD_SEALS: + ret = F_ADD_SEALS; + break; + case TARGET_F_GET_SEALS: + ret = F_GET_SEALS; + break; #endif default: ret = -TARGET_EINVAL; @@ -6591,6 +6599,8 @@ static abi_long do_fcntl(int fd, int cmd, abi_ulong arg) case TARGET_F_GETLEASE: case TARGET_F_SETPIPE_SZ: case TARGET_F_GETPIPE_SZ: + case TARGET_F_ADD_SEALS: + case TARGET_F_GET_SEALS: ret = get_errno(safe_fcntl(fd, host_cmd, arg)); break; diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index 3c261cff0e..70df1a94fb 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -2292,12 +2292,14 @@ struct target_statfs64 { #endif #define TARGET_F_LINUX_SPECIFIC_BASE 1024 -#define TARGET_F_SETLEASE (TARGET_F_LINUX_SPECIFIC_BASE + 0) -#define TARGET_F_GETLEASE (TARGET_F_LINUX_SPECIFIC_BASE + 1) -#define TARGET_F_DUPFD_CLOEXEC (TARGET_F_LINUX_SPECIFIC_BASE + 6) -#define TARGET_F_SETPIPE_SZ (TARGET_F_LINUX_SPECIFIC_BASE + 7) -#define TARGET_F_GETPIPE_SZ (TARGET_F_LINUX_SPECIFIC_BASE + 8) -#define TARGET_F_NOTIFY (TARGET_F_LINUX_SPECIFIC_BASE+2) +#define TARGET_F_SETLEASE (TARGET_F_LINUX_SPECIFIC_BASE + 0) +#define TARGET_F_GETLEASE (TARGET_F_LINUX_SPECIFIC_BASE + 1) +#define TARGET_F_DUPFD_CLOEXEC (TARGET_F_LINUX_SPECIFIC_BASE + 6) +#define TARGET_F_NOTIFY (TARGET_F_LINUX_SPECIFIC_BASE + 2) +#define TARGET_F_SETPIPE_SZ (TARGET_F_LINUX_SPECIFIC_BASE + 7) +#define TARGET_F_GETPIPE_SZ (TARGET_F_LINUX_SPECIFIC_BASE + 8) +#define TARGET_F_ADD_SEALS (TARGET_F_LINUX_SPECIFIC_BASE + 9) +#define TARGET_F_GET_SEALS (TARGET_F_LINUX_SPECIFIC_BASE + 10) #include "target_fcntl.h" -- 2.28.0.220.ged08abb693-goog ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls 2020-08-11 7:09 ` [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls Shu-Chun Weng @ 2020-08-11 14:09 ` Laurent Vivier 0 siblings, 0 replies; 22+ messages in thread From: Laurent Vivier @ 2020-08-11 14:09 UTC (permalink / raw) To: Shu-Chun Weng, qemu-devel Le 11/08/2020 à 09:09, Shu-Chun Weng a écrit : > Also reorder blocks so that they are all in the same order everywhere. > > Signed-off-by: Shu-Chun Weng <scw@google.com> > --- > v1 -> v2: > Updated print_fcntl(). > > linux-user/strace.c | 55 ++++++++++++++++++++++++++++++++------- > linux-user/syscall.c | 10 +++++++ > linux-user/syscall_defs.h | 14 +++++----- > 3 files changed, 64 insertions(+), 15 deletions(-) > > diff --git a/linux-user/strace.c b/linux-user/strace.c > index 13981341b3..4fff24b880 100644 > --- a/linux-user/strace.c > +++ b/linux-user/strace.c > @@ -1684,6 +1684,18 @@ print_fcntl(const struct syscallname *name, > qemu_log("F_SETFL,"); > print_open_flags(arg2, 1); > break; > + case TARGET_F_OFD_GETLK: > + qemu_log("F_OFD_GETLK,"); > + print_pointer(arg2, 1); > + break; > + case TARGET_F_OFD_SETLK: > + qemu_log("F_OFD_SETLK,"); > + print_pointer(arg2, 1); > + break; > + case TARGET_F_OFD_SETLKW: > + qemu_log("F_OFD_SETLKW,"); > + print_pointer(arg2, 1); > + break; > case TARGET_F_GETLK: > qemu_log("F_GETLK,"); > print_pointer(arg2, 1); > @@ -1726,26 +1738,51 @@ print_fcntl(const struct syscallname *name, > #endif > case TARGET_F_SETLEASE: > qemu_log("F_SETLEASE,"); > - print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); > + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); > break; > case TARGET_F_GETLEASE: > qemu_log("F_GETLEASE"); > break; > - case TARGET_F_SETPIPE_SZ: > - qemu_log("F_SETPIPE_SZ,"); > - print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); > - break; > - case TARGET_F_GETPIPE_SZ: > - qemu_log("F_GETPIPE_SZ"); > - break; > +#ifdef F_DUPFD_CLOEXEC > case TARGET_F_DUPFD_CLOEXEC: > qemu_log("F_DUPFD_CLOEXEC,"); > print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); > break; > +#endif > case TARGET_F_NOTIFY: > qemu_log("F_NOTIFY,"); > - print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); > + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); > break; > +#ifdef F_GETOWN_EX > + case TARGET_F_GETOWN_EX: > + qemu_log("F_GETOWN_EX,"); > + print_pointer(arg2, 1); > + break; > +#endif > +#ifdef F_SETOWN_EX > + case TARGET_F_SETOWN_EX: > + qemu_log("F_SETOWN_EX,"); > + print_pointer(arg2, 1); > + break; > +#endif > +#ifdef F_SETPIPE_SZ > + case TARGET_F_SETPIPE_SZ: > + qemu_log("F_SETPIPE_SZ,"); > + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); > + break; > + case TARGET_F_GETPIPE_SZ: > + qemu_log("F_GETPIPE_SZ"); > + break; > +#endif > +#ifdef F_ADD_SEALS > + case TARGET_F_ADD_SEALS: > + qemu_log("F_ADD_SEALS,"); > + print_raw_param("0x"TARGET_ABI_FMT_lx, arg2, 1); > + break; > + case TARGET_F_GET_SEALS: > + qemu_log("F_GET_SEALS"); > + break; > +#endif > default: > print_raw_param(TARGET_ABI_FMT_ld, arg1, 0); > print_pointer(arg2, 1); > diff --git a/linux-user/syscall.c b/linux-user/syscall.c > index 945fc25279..5645862798 100644 > --- a/linux-user/syscall.c > +++ b/linux-user/syscall.c > @@ -6305,6 +6305,14 @@ static int target_to_host_fcntl_cmd(int cmd) > case TARGET_F_GETPIPE_SZ: > ret = F_GETPIPE_SZ; > break; > +#endif > +#ifdef F_ADD_SEALS > + case TARGET_F_ADD_SEALS: > + ret = F_ADD_SEALS; > + break; > + case TARGET_F_GET_SEALS: > + ret = F_GET_SEALS; > + break; > #endif > default: > ret = -TARGET_EINVAL; > @@ -6591,6 +6599,8 @@ static abi_long do_fcntl(int fd, int cmd, abi_ulong arg) > case TARGET_F_GETLEASE: > case TARGET_F_SETPIPE_SZ: > case TARGET_F_GETPIPE_SZ: > + case TARGET_F_ADD_SEALS: > + case TARGET_F_GET_SEALS: > ret = get_errno(safe_fcntl(fd, host_cmd, arg)); > break; > > diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h > index 3c261cff0e..70df1a94fb 100644 > --- a/linux-user/syscall_defs.h > +++ b/linux-user/syscall_defs.h > @@ -2292,12 +2292,14 @@ struct target_statfs64 { > #endif > > #define TARGET_F_LINUX_SPECIFIC_BASE 1024 > -#define TARGET_F_SETLEASE (TARGET_F_LINUX_SPECIFIC_BASE + 0) > -#define TARGET_F_GETLEASE (TARGET_F_LINUX_SPECIFIC_BASE + 1) > -#define TARGET_F_DUPFD_CLOEXEC (TARGET_F_LINUX_SPECIFIC_BASE + 6) > -#define TARGET_F_SETPIPE_SZ (TARGET_F_LINUX_SPECIFIC_BASE + 7) > -#define TARGET_F_GETPIPE_SZ (TARGET_F_LINUX_SPECIFIC_BASE + 8) > -#define TARGET_F_NOTIFY (TARGET_F_LINUX_SPECIFIC_BASE+2) > +#define TARGET_F_SETLEASE (TARGET_F_LINUX_SPECIFIC_BASE + 0) > +#define TARGET_F_GETLEASE (TARGET_F_LINUX_SPECIFIC_BASE + 1) > +#define TARGET_F_DUPFD_CLOEXEC (TARGET_F_LINUX_SPECIFIC_BASE + 6) > +#define TARGET_F_NOTIFY (TARGET_F_LINUX_SPECIFIC_BASE + 2) > +#define TARGET_F_SETPIPE_SZ (TARGET_F_LINUX_SPECIFIC_BASE + 7) > +#define TARGET_F_GETPIPE_SZ (TARGET_F_LINUX_SPECIFIC_BASE + 8) > +#define TARGET_F_ADD_SEALS (TARGET_F_LINUX_SPECIFIC_BASE + 9) > +#define TARGET_F_GET_SEALS (TARGET_F_LINUX_SPECIFIC_BASE + 10) > > #include "target_fcntl.h" > > Reviewed-by: Laurent Vivier <laurent@vivier.eu> ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls Shu-Chun Weng @ 2020-08-11 7:09 ` Shu-Chun Weng 2020-08-11 14:21 ` Laurent Vivier 2020-08-11 7:09 ` [PATCH v2 3/8] linux-user: add missing IPv6 " Shu-Chun Weng ` (6 subsequent siblings) 8 siblings, 1 reply; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent SOL_UDP manipulate options at UDP level. All six options currently defined in linux source include/uapi/linux/udp.h take integer values. Signed-off-by: Shu-Chun Weng <scw@google.com> Reviewed-by: Laurent Vivier <laurent@vivier.eu> --- v1 -> v2: Split out SOL_UDP into own patch. Updated do_print_sockopt(). linux-user/strace.c | 6 ++++++ linux-user/syscall.c | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/linux-user/strace.c b/linux-user/strace.c index 4fff24b880..854b54a2ad 100644 --- a/linux-user/strace.c +++ b/linux-user/strace.c @@ -7,6 +7,7 @@ #include <sys/mount.h> #include <arpa/inet.h> #include <netinet/tcp.h> +#include <netinet/udp.h> #include <linux/if_packet.h> #include <linux/netlink.h> #include <sched.h> @@ -2190,6 +2191,11 @@ static void do_print_sockopt(const char *name, abi_long arg1) print_raw_param(TARGET_ABI_FMT_ld, optname, 0); print_pointer(optval, 0); break; + case SOL_UDP: + qemu_log("SOL_UDP,"); + print_raw_param(TARGET_ABI_FMT_ld, optname, 0); + print_pointer(optval, 0); + break; case SOL_IP: qemu_log("SOL_IP,"); print_raw_param(TARGET_ABI_FMT_ld, optname, 0); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 5645862798..177eec5201 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -53,6 +53,7 @@ //#include <sys/user.h> #include <netinet/ip.h> #include <netinet/tcp.h> +#include <netinet/udp.h> #include <linux/wireless.h> #include <linux/icmp.h> #include <linux/icmpv6.h> @@ -1938,7 +1939,8 @@ static abi_long do_setsockopt(int sockfd, int level, int optname, switch(level) { case SOL_TCP: - /* TCP options all take an 'int' value. */ + case SOL_UDP: + /* TCP and UDP options all take an 'int' value. */ if (optlen < sizeof(uint32_t)) return -TARGET_EINVAL; @@ -2586,7 +2588,8 @@ get_timeout: } break; case SOL_TCP: - /* TCP options all take an 'int' value. */ + case SOL_UDP: + /* TCP and UDP options all take an 'int' value. */ int_case: if (get_user_u32(len, optlen)) return -TARGET_EFAULT; -- 2.28.0.220.ged08abb693-goog ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option 2020-08-11 7:09 ` [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option Shu-Chun Weng @ 2020-08-11 14:21 ` Laurent Vivier 2020-08-11 20:04 ` Shu-Chun Weng 0 siblings, 1 reply; 22+ messages in thread From: Laurent Vivier @ 2020-08-11 14:21 UTC (permalink / raw) To: Shu-Chun Weng, qemu-devel Le 11/08/2020 à 09:09, Shu-Chun Weng a écrit : > SOL_UDP manipulate options at UDP level. All six options currently defined > in linux source include/uapi/linux/udp.h take integer values. > > Signed-off-by: Shu-Chun Weng <scw@google.com> > Reviewed-by: Laurent Vivier <laurent@vivier.eu> > --- > v1 -> v2: > Split out SOL_UDP into own patch. > Updated do_print_sockopt(). > > linux-user/strace.c | 6 ++++++ > linux-user/syscall.c | 7 +++++-- > 2 files changed, 11 insertions(+), 2 deletions(-) > > diff --git a/linux-user/strace.c b/linux-user/strace.c > index 4fff24b880..854b54a2ad 100644 > --- a/linux-user/strace.c > +++ b/linux-user/strace.c > @@ -7,6 +7,7 @@ > #include <sys/mount.h> > #include <arpa/inet.h> > #include <netinet/tcp.h> > +#include <netinet/udp.h> > #include <linux/if_packet.h> > #include <linux/netlink.h> > #include <sched.h> > @@ -2190,6 +2191,11 @@ static void do_print_sockopt(const char *name, abi_long arg1) > print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > print_pointer(optval, 0); > break; > + case SOL_UDP: > + qemu_log("SOL_UDP,"); > + print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > + print_pointer(optval, 0); > + break; > case SOL_IP: > qemu_log("SOL_IP,"); > print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > diff --git a/linux-user/syscall.c b/linux-user/syscall.c > index 5645862798..177eec5201 100644 > --- a/linux-user/syscall.c > +++ b/linux-user/syscall.c > @@ -53,6 +53,7 @@ > //#include <sys/user.h> > #include <netinet/ip.h> > #include <netinet/tcp.h> > +#include <netinet/udp.h> > #include <linux/wireless.h> > #include <linux/icmp.h> > #include <linux/icmpv6.h> > @@ -1938,7 +1939,8 @@ static abi_long do_setsockopt(int sockfd, int level, int optname, > > switch(level) { > case SOL_TCP: > - /* TCP options all take an 'int' value. */ > + case SOL_UDP: > + /* TCP and UDP options all take an 'int' value. */ > if (optlen < sizeof(uint32_t)) > return -TARGET_EINVAL; > > @@ -2586,7 +2588,8 @@ get_timeout: > } > break; > case SOL_TCP: > - /* TCP options all take an 'int' value. */ > + case SOL_UDP: > + /* TCP and UDP options all take an 'int' value. */ > int_case: > if (get_user_u32(len, optlen)) > return -TARGET_EFAULT; > Reviewed-by: Laurent Vivier <laurent@vivier.eu> I'm wondering if the int_case of getsockopt() manages correctly the length: length can be between 0 and sizeof(int), but the int_case only uses a put_user_u32() or a put_user_u8(). Do we need the put_user_u16()? Thanks, Laurent ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option 2020-08-11 14:21 ` Laurent Vivier @ 2020-08-11 20:04 ` Shu-Chun Weng 0 siblings, 0 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 20:04 UTC (permalink / raw) To: Laurent Vivier; +Cc: qemu-devel [-- Attachment #1.1: Type: text/plain, Size: 3396 bytes --] It does look like something that can be improved. The lines have been there for 14 years though: https://github.com/qemu/qemu/commit/53a5960aadd542dd27b8705ac30df154557d5ffc The potential bug is triggered when the user passes in a 2-byte integer in getsockopt(), which seems uncommon -- do we have guest architectures that use 16-bit int type? Shu-Chun On Tue, Aug 11, 2020 at 7:21 AM Laurent Vivier <laurent@vivier.eu> wrote: > Le 11/08/2020 à 09:09, Shu-Chun Weng a écrit : > > SOL_UDP manipulate options at UDP level. All six options currently > defined > > in linux source include/uapi/linux/udp.h take integer values. > > > > Signed-off-by: Shu-Chun Weng <scw@google.com> > > Reviewed-by: Laurent Vivier <laurent@vivier.eu> > > --- > > v1 -> v2: > > Split out SOL_UDP into own patch. > > Updated do_print_sockopt(). > > > > linux-user/strace.c | 6 ++++++ > > linux-user/syscall.c | 7 +++++-- > > 2 files changed, 11 insertions(+), 2 deletions(-) > > > > diff --git a/linux-user/strace.c b/linux-user/strace.c > > index 4fff24b880..854b54a2ad 100644 > > --- a/linux-user/strace.c > > +++ b/linux-user/strace.c > > @@ -7,6 +7,7 @@ > > #include <sys/mount.h> > > #include <arpa/inet.h> > > #include <netinet/tcp.h> > > +#include <netinet/udp.h> > > #include <linux/if_packet.h> > > #include <linux/netlink.h> > > #include <sched.h> > > @@ -2190,6 +2191,11 @@ static void do_print_sockopt(const char *name, > abi_long arg1) > > print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > > print_pointer(optval, 0); > > break; > > + case SOL_UDP: > > + qemu_log("SOL_UDP,"); > > + print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > > + print_pointer(optval, 0); > > + break; > > case SOL_IP: > > qemu_log("SOL_IP,"); > > print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > > diff --git a/linux-user/syscall.c b/linux-user/syscall.c > > index 5645862798 <(564)%20586-2798>..177eec5201 100644 > > --- a/linux-user/syscall.c > > +++ b/linux-user/syscall.c > > @@ -53,6 +53,7 @@ > > //#include <sys/user.h> > > #include <netinet/ip.h> > > #include <netinet/tcp.h> > > +#include <netinet/udp.h> > > #include <linux/wireless.h> > > #include <linux/icmp.h> > > #include <linux/icmpv6.h> > > @@ -1938,7 +1939,8 @@ static abi_long do_setsockopt(int sockfd, int > level, int optname, > > > > switch(level) { > > case SOL_TCP: > > - /* TCP options all take an 'int' value. */ > > + case SOL_UDP: > > + /* TCP and UDP options all take an 'int' value. */ > > if (optlen < sizeof(uint32_t)) > > return -TARGET_EINVAL; > > > > @@ -2586,7 +2588,8 @@ get_timeout: > > } > > break; > > case SOL_TCP: > > - /* TCP options all take an 'int' value. */ > > + case SOL_UDP: > > + /* TCP and UDP options all take an 'int' value. */ > > int_case: > > if (get_user_u32(len, optlen)) > > return -TARGET_EFAULT; > > > > Reviewed-by: Laurent Vivier <laurent@vivier.eu> > > I'm wondering if the int_case of getsockopt() manages correctly the > length: length can be between 0 and sizeof(int), but the int_case only > uses a put_user_u32() or a put_user_u8(). Do we need the put_user_u16()? > > Thanks, > Laurent > [-- Attachment #1.2: Type: text/html, Size: 4708 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3844 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 3/8] linux-user: add missing IPv6 get/setsockopt option 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option Shu-Chun Weng @ 2020-08-11 7:09 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() Shu-Chun Weng ` (5 subsequent siblings) 8 siblings, 0 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent IPV6_ADDR_PREFERENCES (RFC5014: Source address selection) was not supported. Signed-off-by: Shu-Chun Weng <scw@google.com> Reviewed-by: Laurent Vivier <laurent@vivier.eu> --- v1 -> v2: Split out IPV6 options into own patch. do_print_sockopt() changes added in a separate patch since a large number of unrelated changes are involved. linux-user/syscall.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 177eec5201..cda194a7cc 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -51,6 +51,7 @@ #include <sys/sysinfo.h> #include <sys/signalfd.h> //#include <sys/user.h> +#include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <netinet/udp.h> @@ -2026,6 +2027,7 @@ static abi_long do_setsockopt(int sockfd, int level, int optname, case IPV6_RECVDSTOPTS: case IPV6_2292DSTOPTS: case IPV6_TCLASS: + case IPV6_ADDR_PREFERENCES: #ifdef IPV6_RECVPATHMTU case IPV6_RECVPATHMTU: #endif @@ -2680,6 +2682,7 @@ get_timeout: case IPV6_RECVDSTOPTS: case IPV6_2292DSTOPTS: case IPV6_TCLASS: + case IPV6_ADDR_PREFERENCES: #ifdef IPV6_RECVPATHMTU case IPV6_RECVPATHMTU: #endif -- 2.28.0.220.ged08abb693-goog ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng ` (2 preceding siblings ...) 2020-08-11 7:09 ` [PATCH v2 3/8] linux-user: add missing IPv6 " Shu-Chun Weng @ 2020-08-11 7:09 ` Shu-Chun Weng 2020-09-17 7:26 ` Shu-Chun Weng 2020-09-29 23:29 ` Laurent Vivier 2020-08-11 7:09 ` [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW Shu-Chun Weng ` (4 subsequent siblings) 8 siblings, 2 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent Signed-off-by: Shu-Chun Weng <scw@google.com> --- v1 -> v2: New: Add all IPV6 options to do_print_sockopt(), including the newly supported IPV6_ADDR_PREFERENCES. linux-user/strace.c | 108 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/linux-user/strace.c b/linux-user/strace.c index 854b54a2ad..089fb3968e 100644 --- a/linux-user/strace.c +++ b/linux-user/strace.c @@ -6,6 +6,7 @@ #include <sys/select.h> #include <sys/mount.h> #include <arpa/inet.h> +#include <netinet/in.h> #include <netinet/tcp.h> #include <netinet/udp.h> #include <linux/if_packet.h> @@ -2307,6 +2308,113 @@ print_optint: break; } break; + case SOL_IPV6: + qemu_log("SOL_IPV6,"); + switch (optname) { + case IPV6_MTU_DISCOVER: + qemu_log("IPV6_MTU_DISCOVER,"); + goto print_optint; + case IPV6_MTU: + qemu_log("IPV6_MTU,"); + goto print_optint; + case IPV6_V6ONLY: + qemu_log("IPV6_V6ONLY,"); + goto print_optint; + case IPV6_RECVPKTINFO: + qemu_log("IPV6_RECVPKTINFO,"); + goto print_optint; + case IPV6_UNICAST_HOPS: + qemu_log("IPV6_UNICAST_HOPS,"); + goto print_optint; + case IPV6_MULTICAST_HOPS: + qemu_log("IPV6_MULTICAST_HOPS,"); + goto print_optint; + case IPV6_MULTICAST_LOOP: + qemu_log("IPV6_MULTICAST_LOOP,"); + goto print_optint; + case IPV6_RECVERR: + qemu_log("IPV6_RECVERR,"); + goto print_optint; + case IPV6_RECVHOPLIMIT: + qemu_log("IPV6_RECVHOPLIMIT,"); + goto print_optint; + case IPV6_2292HOPLIMIT: + qemu_log("IPV6_2292HOPLIMIT,"); + goto print_optint; + case IPV6_CHECKSUM: + qemu_log("IPV6_CHECKSUM,"); + goto print_optint; + case IPV6_ADDRFORM: + qemu_log("IPV6_ADDRFORM,"); + goto print_optint; + case IPV6_2292PKTINFO: + qemu_log("IPV6_2292PKTINFO,"); + goto print_optint; + case IPV6_RECVTCLASS: + qemu_log("IPV6_RECVTCLASS,"); + goto print_optint; + case IPV6_RECVRTHDR: + qemu_log("IPV6_RECVRTHDR,"); + goto print_optint; + case IPV6_2292RTHDR: + qemu_log("IPV6_2292RTHDR,"); + goto print_optint; + case IPV6_RECVHOPOPTS: + qemu_log("IPV6_RECVHOPOPTS,"); + goto print_optint; + case IPV6_2292HOPOPTS: + qemu_log("IPV6_2292HOPOPTS,"); + goto print_optint; + case IPV6_RECVDSTOPTS: + qemu_log("IPV6_RECVDSTOPTS,"); + goto print_optint; + case IPV6_2292DSTOPTS: + qemu_log("IPV6_2292DSTOPTS,"); + goto print_optint; + case IPV6_TCLASS: + qemu_log("IPV6_TCLASS,"); + goto print_optint; + case IPV6_ADDR_PREFERENCES: + qemu_log("IPV6_ADDR_PREFERENCES,"); + goto print_optint; +#ifdef IPV6_RECVPATHMTU + case IPV6_RECVPATHMTU: + qemu_log("IPV6_RECVPATHMTU,"); + goto print_optint; +#endif +#ifdef IPV6_TRANSPARENT + case IPV6_TRANSPARENT: + qemu_log("IPV6_TRANSPARENT,"); + goto print_optint; +#endif +#ifdef IPV6_FREEBIND + case IPV6_FREEBIND: + qemu_log("IPV6_FREEBIND,"); + goto print_optint; +#endif +#ifdef IPV6_RECVORIGDSTADDR + case IPV6_RECVORIGDSTADDR: + qemu_log("IPV6_RECVORIGDSTADDR,"); + goto print_optint; +#endif + case IPV6_PKTINFO: + qemu_log("IPV6_PKTINFO,"); + print_pointer(optval, 0); + break; + case IPV6_ADD_MEMBERSHIP: + qemu_log("IPV6_ADD_MEMBERSHIP,"); + print_pointer(optval, 0); + break; + case IPV6_DROP_MEMBERSHIP: + qemu_log("IPV6_DROP_MEMBERSHIP,"); + print_pointer(optval, 0); + break; + default: + print_raw_param(TARGET_ABI_FMT_ld, optname, 0); + print_pointer(optval, 0); + break; + } + break; default: print_raw_param(TARGET_ABI_FMT_ld, level, 0); print_raw_param(TARGET_ABI_FMT_ld, optname, 0); -- 2.28.0.220.ged08abb693-goog ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() 2020-08-11 7:09 ` [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() Shu-Chun Weng @ 2020-09-17 7:26 ` Shu-Chun Weng 2020-09-29 23:29 ` Laurent Vivier 1 sibling, 0 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-09-17 7:26 UTC (permalink / raw) To: qemu-devel; +Cc: Laurent Vivier [-- Attachment #1.1: Type: text/plain, Size: 4891 bytes --] Ping -- this one was broken off into its own patch from v1 due to the amount of addition. Hence I did not add Review-by. On Tue, Aug 11, 2020 at 12:10 AM Shu-Chun Weng <scw@google.com> wrote: > Signed-off-by: Shu-Chun Weng <scw@google.com> > --- > v1 -> v2: > New: Add all IPV6 options to do_print_sockopt(), including the newly > supported > IPV6_ADDR_PREFERENCES. > > linux-user/strace.c | 108 ++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 108 insertions(+) > > diff --git a/linux-user/strace.c b/linux-user/strace.c > index 854b54a2ad..089fb3968e 100644 > --- a/linux-user/strace.c > +++ b/linux-user/strace.c > @@ -6,6 +6,7 @@ > #include <sys/select.h> > #include <sys/mount.h> > #include <arpa/inet.h> > +#include <netinet/in.h> > #include <netinet/tcp.h> > #include <netinet/udp.h> > #include <linux/if_packet.h> > @@ -2307,6 +2308,113 @@ print_optint: > break; > } > break; > + case SOL_IPV6: > + qemu_log("SOL_IPV6,"); > + switch (optname) { > + case IPV6_MTU_DISCOVER: > + qemu_log("IPV6_MTU_DISCOVER,"); > + goto print_optint; > + case IPV6_MTU: > + qemu_log("IPV6_MTU,"); > + goto print_optint; > + case IPV6_V6ONLY: > + qemu_log("IPV6_V6ONLY,"); > + goto print_optint; > + case IPV6_RECVPKTINFO: > + qemu_log("IPV6_RECVPKTINFO,"); > + goto print_optint; > + case IPV6_UNICAST_HOPS: > + qemu_log("IPV6_UNICAST_HOPS,"); > + goto print_optint; > + case IPV6_MULTICAST_HOPS: > + qemu_log("IPV6_MULTICAST_HOPS,"); > + goto print_optint; > + case IPV6_MULTICAST_LOOP: > + qemu_log("IPV6_MULTICAST_LOOP,"); > + goto print_optint; > + case IPV6_RECVERR: > + qemu_log("IPV6_RECVERR,"); > + goto print_optint; > + case IPV6_RECVHOPLIMIT: > + qemu_log("IPV6_RECVHOPLIMIT,"); > + goto print_optint; > + case IPV6_2292HOPLIMIT: > + qemu_log("IPV6_2292HOPLIMIT,"); > + goto print_optint; > + case IPV6_CHECKSUM: > + qemu_log("IPV6_CHECKSUM,"); > + goto print_optint; > + case IPV6_ADDRFORM: > + qemu_log("IPV6_ADDRFORM,"); > + goto print_optint; > + case IPV6_2292PKTINFO: > + qemu_log("IPV6_2292PKTINFO,"); > + goto print_optint; > + case IPV6_RECVTCLASS: > + qemu_log("IPV6_RECVTCLASS,"); > + goto print_optint; > + case IPV6_RECVRTHDR: > + qemu_log("IPV6_RECVRTHDR,"); > + goto print_optint; > + case IPV6_2292RTHDR: > + qemu_log("IPV6_2292RTHDR,"); > + goto print_optint; > + case IPV6_RECVHOPOPTS: > + qemu_log("IPV6_RECVHOPOPTS,"); > + goto print_optint; > + case IPV6_2292HOPOPTS: > + qemu_log("IPV6_2292HOPOPTS,"); > + goto print_optint; > + case IPV6_RECVDSTOPTS: > + qemu_log("IPV6_RECVDSTOPTS,"); > + goto print_optint; > + case IPV6_2292DSTOPTS: > + qemu_log("IPV6_2292DSTOPTS,"); > + goto print_optint; > + case IPV6_TCLASS: > + qemu_log("IPV6_TCLASS,"); > + goto print_optint; > + case IPV6_ADDR_PREFERENCES: > + qemu_log("IPV6_ADDR_PREFERENCES,"); > + goto print_optint; > +#ifdef IPV6_RECVPATHMTU > + case IPV6_RECVPATHMTU: > + qemu_log("IPV6_RECVPATHMTU,"); > + goto print_optint; > +#endif > +#ifdef IPV6_TRANSPARENT > + case IPV6_TRANSPARENT: > + qemu_log("IPV6_TRANSPARENT,"); > + goto print_optint; > +#endif > +#ifdef IPV6_FREEBIND > + case IPV6_FREEBIND: > + qemu_log("IPV6_FREEBIND,"); > + goto print_optint; > +#endif > +#ifdef IPV6_RECVORIGDSTADDR > + case IPV6_RECVORIGDSTADDR: > + qemu_log("IPV6_RECVORIGDSTADDR,"); > + goto print_optint; > +#endif > + case IPV6_PKTINFO: > + qemu_log("IPV6_PKTINFO,"); > + print_pointer(optval, 0); > + break; > + case IPV6_ADD_MEMBERSHIP: > + qemu_log("IPV6_ADD_MEMBERSHIP,"); > + print_pointer(optval, 0); > + break; > + case IPV6_DROP_MEMBERSHIP: > + qemu_log("IPV6_DROP_MEMBERSHIP,"); > + print_pointer(optval, 0); > + break; > + default: > + print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > + print_pointer(optval, 0); > + break; > + } > + break; > default: > print_raw_param(TARGET_ABI_FMT_ld, level, 0); > print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > -- > 2.28.0.220.ged08abb693-goog > > [-- Attachment #1.2: Type: text/html, Size: 6544 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3990 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() 2020-08-11 7:09 ` [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() Shu-Chun Weng 2020-09-17 7:26 ` Shu-Chun Weng @ 2020-09-29 23:29 ` Laurent Vivier 2020-12-18 3:58 ` Shu-Chun Weng 1 sibling, 1 reply; 22+ messages in thread From: Laurent Vivier @ 2020-09-29 23:29 UTC (permalink / raw) To: Shu-Chun Weng, qemu-devel Le 11/08/2020 à 09:09, Shu-Chun Weng a écrit : > Signed-off-by: Shu-Chun Weng <scw@google.com> > --- > v1 -> v2: > New: Add all IPV6 options to do_print_sockopt(), including the newly supported > IPV6_ADDR_PREFERENCES. > > linux-user/strace.c | 108 ++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 108 insertions(+) > > diff --git a/linux-user/strace.c b/linux-user/strace.c > index 854b54a2ad..089fb3968e 100644 > --- a/linux-user/strace.c > +++ b/linux-user/strace.c > @@ -6,6 +6,7 @@ > #include <sys/select.h> > #include <sys/mount.h> > #include <arpa/inet.h> > +#include <netinet/in.h> > #include <netinet/tcp.h> > #include <netinet/udp.h> > #include <linux/if_packet.h> > @@ -2307,6 +2308,113 @@ print_optint: > break; > } > break; > + case SOL_IPV6: > + qemu_log("SOL_IPV6,"); > + switch (optname) { > + case IPV6_MTU_DISCOVER: > + qemu_log("IPV6_MTU_DISCOVER,"); > + goto print_optint; > + case IPV6_MTU: > + qemu_log("IPV6_MTU,"); > + goto print_optint; > + case IPV6_V6ONLY: > + qemu_log("IPV6_V6ONLY,"); > + goto print_optint; > + case IPV6_RECVPKTINFO: > + qemu_log("IPV6_RECVPKTINFO,"); > + goto print_optint; > + case IPV6_UNICAST_HOPS: > + qemu_log("IPV6_UNICAST_HOPS,"); > + goto print_optint; > + case IPV6_MULTICAST_HOPS: > + qemu_log("IPV6_MULTICAST_HOPS,"); > + goto print_optint; > + case IPV6_MULTICAST_LOOP: > + qemu_log("IPV6_MULTICAST_LOOP,"); > + goto print_optint; > + case IPV6_RECVERR: > + qemu_log("IPV6_RECVERR,"); > + goto print_optint; > + case IPV6_RECVHOPLIMIT: > + qemu_log("IPV6_RECVHOPLIMIT,"); > + goto print_optint; > + case IPV6_2292HOPLIMIT: > + qemu_log("IPV6_2292HOPLIMIT,"); > + goto print_optint; > + case IPV6_CHECKSUM: > + qemu_log("IPV6_CHECKSUM,"); > + goto print_optint; > + case IPV6_ADDRFORM: > + qemu_log("IPV6_ADDRFORM,"); > + goto print_optint; > + case IPV6_2292PKTINFO: > + qemu_log("IPV6_2292PKTINFO,"); > + goto print_optint; > + case IPV6_RECVTCLASS: > + qemu_log("IPV6_RECVTCLASS,"); > + goto print_optint; > + case IPV6_RECVRTHDR: > + qemu_log("IPV6_RECVRTHDR,"); > + goto print_optint; > + case IPV6_2292RTHDR: > + qemu_log("IPV6_2292RTHDR,"); > + goto print_optint; > + case IPV6_RECVHOPOPTS: > + qemu_log("IPV6_RECVHOPOPTS,"); > + goto print_optint; > + case IPV6_2292HOPOPTS: > + qemu_log("IPV6_2292HOPOPTS,"); > + goto print_optint; > + case IPV6_RECVDSTOPTS: > + qemu_log("IPV6_RECVDSTOPTS,"); > + goto print_optint; > + case IPV6_2292DSTOPTS: > + qemu_log("IPV6_2292DSTOPTS,"); > + goto print_optint; > + case IPV6_TCLASS: > + qemu_log("IPV6_TCLASS,"); > + goto print_optint; > + case IPV6_ADDR_PREFERENCES: > + qemu_log("IPV6_ADDR_PREFERENCES,"); > + goto print_optint; > +#ifdef IPV6_RECVPATHMTU > + case IPV6_RECVPATHMTU: > + qemu_log("IPV6_RECVPATHMTU,"); > + goto print_optint; > +#endif > +#ifdef IPV6_TRANSPARENT > + case IPV6_TRANSPARENT: > + qemu_log("IPV6_TRANSPARENT,"); > + goto print_optint; > +#endif > +#ifdef IPV6_FREEBIND > + case IPV6_FREEBIND: > + qemu_log("IPV6_FREEBIND,"); > + goto print_optint; > +#endif > +#ifdef IPV6_RECVORIGDSTADDR > + case IPV6_RECVORIGDSTADDR: > + qemu_log("IPV6_RECVORIGDSTADDR,"); > + goto print_optint; > +#endif > + case IPV6_PKTINFO: > + qemu_log("IPV6_PKTINFO,"); > + print_pointer(optval, 0); > + break; > + case IPV6_ADD_MEMBERSHIP: > + qemu_log("IPV6_ADD_MEMBERSHIP,"); > + print_pointer(optval, 0); > + break; > + case IPV6_DROP_MEMBERSHIP: > + qemu_log("IPV6_DROP_MEMBERSHIP,"); > + print_pointer(optval, 0); > + break; > + default: > + print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > + print_pointer(optval, 0); > + break; > + } > + break; > default: > print_raw_param(TARGET_ABI_FMT_ld, level, 0); > print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > Reviewed-by: Laurent Vivier <laurent@vivier.eu> ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() 2020-09-29 23:29 ` Laurent Vivier @ 2020-12-18 3:58 ` Shu-Chun Weng 0 siblings, 0 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-12-18 3:58 UTC (permalink / raw) To: Laurent Vivier; +Cc: qemu-devel [-- Attachment #1.1: Type: text/plain, Size: 5744 bytes --] Hi Laurent, The first 4 patches in the set (up to this) are self-contained. Is it possible to include them in your dev branch while the others are still waiting for review? (I'll ping the other threads separately). The first three patches are: https://lists.nongnu.org/archive/html/qemu-devel/2020-08/msg02044.html https://lists.nongnu.org/archive/html/qemu-devel/2020-08/msg02051.html https://lists.nongnu.org/archive/html/qemu-devel/2020-08/msg01946.html Shu-Chun On Tue, Sep 29, 2020 at 4:29 PM Laurent Vivier <laurent@vivier.eu> wrote: > Le 11/08/2020 à 09:09, Shu-Chun Weng a écrit : > > Signed-off-by: Shu-Chun Weng <scw@google.com> > > --- > > v1 -> v2: > > New: Add all IPV6 options to do_print_sockopt(), including the newly > supported > > IPV6_ADDR_PREFERENCES. > > > > linux-user/strace.c | 108 ++++++++++++++++++++++++++++++++++++++++++++ > > 1 file changed, 108 insertions(+) > > > > diff --git a/linux-user/strace.c b/linux-user/strace.c > > index 854b54a2ad..089fb3968e 100644 > > --- a/linux-user/strace.c > > +++ b/linux-user/strace.c > > @@ -6,6 +6,7 @@ > > #include <sys/select.h> > > #include <sys/mount.h> > > #include <arpa/inet.h> > > +#include <netinet/in.h> > > #include <netinet/tcp.h> > > #include <netinet/udp.h> > > #include <linux/if_packet.h> > > @@ -2307,6 +2308,113 @@ print_optint: > > break; > > } > > break; > > + case SOL_IPV6: > > + qemu_log("SOL_IPV6,"); > > + switch (optname) { > > + case IPV6_MTU_DISCOVER: > > + qemu_log("IPV6_MTU_DISCOVER,"); > > + goto print_optint; > > + case IPV6_MTU: > > + qemu_log("IPV6_MTU,"); > > + goto print_optint; > > + case IPV6_V6ONLY: > > + qemu_log("IPV6_V6ONLY,"); > > + goto print_optint; > > + case IPV6_RECVPKTINFO: > > + qemu_log("IPV6_RECVPKTINFO,"); > > + goto print_optint; > > + case IPV6_UNICAST_HOPS: > > + qemu_log("IPV6_UNICAST_HOPS,"); > > + goto print_optint; > > + case IPV6_MULTICAST_HOPS: > > + qemu_log("IPV6_MULTICAST_HOPS,"); > > + goto print_optint; > > + case IPV6_MULTICAST_LOOP: > > + qemu_log("IPV6_MULTICAST_LOOP,"); > > + goto print_optint; > > + case IPV6_RECVERR: > > + qemu_log("IPV6_RECVERR,"); > > + goto print_optint; > > + case IPV6_RECVHOPLIMIT: > > + qemu_log("IPV6_RECVHOPLIMIT,"); > > + goto print_optint; > > + case IPV6_2292HOPLIMIT: > > + qemu_log("IPV6_2292HOPLIMIT,"); > > + goto print_optint; > > + case IPV6_CHECKSUM: > > + qemu_log("IPV6_CHECKSUM,"); > > + goto print_optint; > > + case IPV6_ADDRFORM: > > + qemu_log("IPV6_ADDRFORM,"); > > + goto print_optint; > > + case IPV6_2292PKTINFO: > > + qemu_log("IPV6_2292PKTINFO,"); > > + goto print_optint; > > + case IPV6_RECVTCLASS: > > + qemu_log("IPV6_RECVTCLASS,"); > > + goto print_optint; > > + case IPV6_RECVRTHDR: > > + qemu_log("IPV6_RECVRTHDR,"); > > + goto print_optint; > > + case IPV6_2292RTHDR: > > + qemu_log("IPV6_2292RTHDR,"); > > + goto print_optint; > > + case IPV6_RECVHOPOPTS: > > + qemu_log("IPV6_RECVHOPOPTS,"); > > + goto print_optint; > > + case IPV6_2292HOPOPTS: > > + qemu_log("IPV6_2292HOPOPTS,"); > > + goto print_optint; > > + case IPV6_RECVDSTOPTS: > > + qemu_log("IPV6_RECVDSTOPTS,"); > > + goto print_optint; > > + case IPV6_2292DSTOPTS: > > + qemu_log("IPV6_2292DSTOPTS,"); > > + goto print_optint; > > + case IPV6_TCLASS: > > + qemu_log("IPV6_TCLASS,"); > > + goto print_optint; > > + case IPV6_ADDR_PREFERENCES: > > + qemu_log("IPV6_ADDR_PREFERENCES,"); > > + goto print_optint; > > +#ifdef IPV6_RECVPATHMTU > > + case IPV6_RECVPATHMTU: > > + qemu_log("IPV6_RECVPATHMTU,"); > > + goto print_optint; > > +#endif > > +#ifdef IPV6_TRANSPARENT > > + case IPV6_TRANSPARENT: > > + qemu_log("IPV6_TRANSPARENT,"); > > + goto print_optint; > > +#endif > > +#ifdef IPV6_FREEBIND > > + case IPV6_FREEBIND: > > + qemu_log("IPV6_FREEBIND,"); > > + goto print_optint; > > +#endif > > +#ifdef IPV6_RECVORIGDSTADDR > > + case IPV6_RECVORIGDSTADDR: > > + qemu_log("IPV6_RECVORIGDSTADDR,"); > > + goto print_optint; > > +#endif > > + case IPV6_PKTINFO: > > + qemu_log("IPV6_PKTINFO,"); > > + print_pointer(optval, 0); > > + break; > > + case IPV6_ADD_MEMBERSHIP: > > + qemu_log("IPV6_ADD_MEMBERSHIP,"); > > + print_pointer(optval, 0); > > + break; > > + case IPV6_DROP_MEMBERSHIP: > > + qemu_log("IPV6_DROP_MEMBERSHIP,"); > > + print_pointer(optval, 0); > > + break; > > + default: > > + print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > > + print_pointer(optval, 0); > > + break; > > + } > > + break; > > default: > > print_raw_param(TARGET_ABI_FMT_ld, level, 0); > > print_raw_param(TARGET_ABI_FMT_ld, optname, 0); > > > > Reviewed-by: Laurent Vivier <laurent@vivier.eu> > [-- Attachment #1.2: Type: text/html, Size: 8098 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3990 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng ` (3 preceding siblings ...) 2020-08-11 7:09 ` [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() Shu-Chun Weng @ 2020-08-11 7:09 ` Shu-Chun Weng 2020-09-17 7:29 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 6/8] linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING Shu-Chun Weng ` (3 subsequent siblings) 8 siblings, 1 reply; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent 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 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW 2020-08-11 7:09 ` [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW Shu-Chun Weng @ 2020-09-17 7:29 ` Shu-Chun Weng 2020-12-18 4:01 ` Shu-Chun Weng 0 siblings, 1 reply; 22+ messages in thread From: Shu-Chun Weng @ 2020-09-17 7:29 UTC (permalink / raw) To: qemu-devel; +Cc: Laurent Vivier [-- Attachment #1.1: Type: text/plain, Size: 23467 bytes --] 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 > > [-- Attachment #1.2: Type: text/html, Size: 28520 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3990 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW 2020-09-17 7:29 ` Shu-Chun Weng @ 2020-12-18 4:01 ` Shu-Chun Weng 0 siblings, 0 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-12-18 4:01 UTC (permalink / raw) To: qemu-devel; +Cc: Laurent Vivier [-- Attachment #1.1: Type: text/plain, Size: 24562 bytes --] 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 >> >> [-- Attachment #1.2: Type: text/html, Size: 29464 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3990 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 6/8] linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng ` (4 preceding siblings ...) 2020-08-11 7:09 ` [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW Shu-Chun Weng @ 2020-08-11 7:09 ` Shu-Chun Weng 2020-12-18 4:02 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 7/8] thunk: supports flexible arrays Shu-Chun Weng ` (2 subsequent siblings) 8 siblings, 1 reply; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent This change supports SO_TIMESTAMPNS_OLD/NEW and SO_TIMESTAMPING_OLD/NEW for setsocketopt() with SOL_SOCKET. Based on the SO_TIMESTAMP_OLD/NEW framework. The three pairs share the same flag `SOCK_TSTAMP_NEW` in linux kernel for deciding if the old or the new format is used. Signed-off-by: Shu-Chun Weng <scw@google.com> --- v1 -> v2: Only keep track of old/new format in a global state. Fix style problems. linux-user/alpha/sockbits.h | 13 +- linux-user/generic/sockbits.h | 8 + linux-user/hppa/sockbits.h | 12 +- linux-user/mips/sockbits.h | 8 + linux-user/sparc/sockbits.h | 13 +- linux-user/strace.c | 12 + linux-user/syscall.c | 149 ++++++++- tests/tcg/multiarch/socket_timestamp.c | 442 +++++++++++++++++++------ 8 files changed, 540 insertions(+), 117 deletions(-) diff --git a/linux-user/alpha/sockbits.h b/linux-user/alpha/sockbits.h index 40f0644df0..c2c88f432b 100644 --- a/linux-user/alpha/sockbits.h +++ b/linux-user/alpha/sockbits.h @@ -51,8 +51,6 @@ #define TARGET_SO_PEERSEC 30 #define TARGET_SO_PASSSEC 34 -#define TARGET_SO_TIMESTAMPNS 35 -#define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS /* Security levels - as per NRL IPv6 - don't actually do anything */ #define TARGET_SO_SECURITY_AUTHENTICATION 19 @@ -61,9 +59,6 @@ #define TARGET_SO_MARK 36 -#define TARGET_SO_TIMESTAMPING 37 -#define TARGET_SCM_TIMESTAMPING TARGET_SO_TIMESTAMPING - #define TARGET_SO_RXQ_OVFL 40 #define TARGET_SO_WIFI_STATUS 41 @@ -75,9 +70,17 @@ #define TARGET_SO_TIMESTAMP_OLD 29 #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD +#define TARGET_SO_TIMESTAMPNS_OLD 35 +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD +#define TARGET_SO_TIMESTAMPING_OLD 37 +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD #define TARGET_SO_TIMESTAMP_NEW 63 #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW +#define TARGET_SO_TIMESTAMPNS_NEW 64 +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW +#define TARGET_SO_TIMESTAMPING_NEW 65 +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_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 532cf2d3dc..a0496d8751 100644 --- a/linux-user/generic/sockbits.h +++ b/linux-user/generic/sockbits.h @@ -56,8 +56,16 @@ #define TARGET_SO_TIMESTAMP_OLD 29 #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD +#define TARGET_SO_TIMESTAMPNS_OLD 35 +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD +#define TARGET_SO_TIMESTAMPING_OLD 37 +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD #define TARGET_SO_TIMESTAMP_NEW 63 #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW +#define TARGET_SO_TIMESTAMPNS_NEW 64 +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW +#define TARGET_SO_TIMESTAMPING_NEW 65 +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_NEW #endif diff --git a/linux-user/hppa/sockbits.h b/linux-user/hppa/sockbits.h index 284a47e74e..d7e9aa340d 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_TIMESTAMPNS 0x4013 -#define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS #define TARGET_SO_SECURITY_AUTHENTICATION 0x4016 #define TARGET_SO_SECURITY_ENCRYPTION_TRANSPORT 0x4017 @@ -44,8 +42,6 @@ #define TARGET_SO_PEERSEC 0x401d #define TARGET_SO_PASSSEC 0x401e #define TARGET_SO_MARK 0x401f -#define TARGET_SO_TIMESTAMPING 0x4020 -#define TARGET_SCM_TIMESTAMPING TARGET_SO_TIMESTAMPING #define TARGET_SO_RXQ_OVFL 0x4021 #define TARGET_SO_WIFI_STATUS 0x4022 #define TARGET_SCM_WIFI_STATUS TARGET_SO_WIFI_STATUS @@ -67,9 +63,17 @@ #define TARGET_SO_TIMESTAMP_OLD 0x4012 #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD +#define TARGET_SO_TIMESTAMPNS_OLD 0x4013 +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD +#define TARGET_SO_TIMESTAMPING_OLD 0x4020 +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD #define TARGET_SO_TIMESTAMP_NEW 0x4038 #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW +#define TARGET_SO_TIMESTAMPNS_NEW 0x4039 +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW +#define TARGET_SO_TIMESTAMPING_NEW 0x403A +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_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 b4c39d9588..49524e23ac 100644 --- a/linux-user/mips/sockbits.h +++ b/linux-user/mips/sockbits.h @@ -69,9 +69,17 @@ #define TARGET_SO_TIMESTAMP_OLD 29 #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD +#define TARGET_SO_TIMESTAMPNS_OLD 35 +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD +#define TARGET_SO_TIMESTAMPING_OLD 37 +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD #define TARGET_SO_TIMESTAMP_NEW 63 #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW +#define TARGET_SO_TIMESTAMPNS_NEW 64 +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW +#define TARGET_SO_TIMESTAMPING_NEW 65 +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_NEW /** sock_type - Socket types * diff --git a/linux-user/sparc/sockbits.h b/linux-user/sparc/sockbits.h index 07440efd14..c5fade3ad1 100644 --- a/linux-user/sparc/sockbits.h +++ b/linux-user/sparc/sockbits.h @@ -51,14 +51,9 @@ #define TARGET_SO_PEERSEC 0x001e #define TARGET_SO_PASSSEC 0x001f -#define TARGET_SO_TIMESTAMPNS 0x0021 -#define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS #define TARGET_SO_MARK 0x0022 -#define TARGET_SO_TIMESTAMPING 0x0023 -#define TARGET_SCM_TIMESTAMPING TARGET_SO_TIMESTAMPING - #define TARGET_SO_RXQ_OVFL 0x0024 #define TARGET_SO_WIFI_STATUS 0x0025 @@ -104,9 +99,17 @@ #define TARGET_SO_TIMESTAMP_OLD 0x001d #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD +#define TARGET_SO_TIMESTAMPNS_OLD 0x0021 +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD +#define TARGET_SO_TIMESTAMPING_OLD 0x0023 +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD #define TARGET_SO_TIMESTAMP_NEW 0x0046 #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW +#define TARGET_SO_TIMESTAMPNS_NEW 0x0042 +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW +#define TARGET_SO_TIMESTAMPING_NEW 0x0043 +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_NEW /* Security levels - as per NRL IPv6 - don't actually do anything */ #define TARGET_SO_SECURITY_AUTHENTICATION 0x5001 diff --git a/linux-user/strace.c b/linux-user/strace.c index a11a5e9e86..7aabb3c972 100644 --- a/linux-user/strace.c +++ b/linux-user/strace.c @@ -2260,9 +2260,21 @@ print_optint: case TARGET_SO_TIMESTAMP_OLD: qemu_log("SO_TIMESTAMP_OLD,"); goto print_optint; + case TARGET_SO_TIMESTAMPNS_OLD: + qemu_log("SO_TIMESTAMPNS_OLD,"); + goto print_optint; + case TARGET_SO_TIMESTAMPING_OLD: + qemu_log("SO_TIMESTAMPING_OLD,"); + goto print_optint; case TARGET_SO_TIMESTAMP_NEW: qemu_log("SO_TIMESTAMP_NEW,"); goto print_optint; + case TARGET_SO_TIMESTAMPNS_NEW: + qemu_log("SO_TIMESTAMPNS_NEW,"); + goto print_optint; + case TARGET_SO_TIMESTAMPING_NEW: + qemu_log("SO_TIMESTAMPING_NEW,"); + goto print_optint; case TARGET_SO_RCVLOWAT: qemu_log("SO_RCVLOWAT,"); goto print_optint; diff --git a/linux-user/syscall.c b/linux-user/syscall.c index e6b1a18cc0..bfc4219104 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -1771,6 +1771,34 @@ static inline abi_long host_to_target_cmsg(struct target_msghdr *target_msgh, break; } break; + case SCM_TIMESTAMPNS: + switch (target_expected_timestamp_version) { + case TARGET_TIMESTAMP_OLD: + tgt_len = sizeof(struct target_timespec); + target_cmsg->cmsg_type = + tswap32(TARGET_SCM_TIMESTAMPNS_OLD); + break; + case TARGET_TIMESTAMP_NEW: + tgt_len = sizeof(struct target__kernel_timespec); + target_cmsg->cmsg_type = + tswap32(TARGET_SCM_TIMESTAMPNS_NEW); + break; + } + break; + case SCM_TIMESTAMPING: + switch (target_expected_timestamp_version) { + case TARGET_TIMESTAMP_OLD: + tgt_len = sizeof(struct target_timespec[3]); + target_cmsg->cmsg_type = + tswap32(TARGET_SCM_TIMESTAMPING_OLD); + break; + case TARGET_TIMESTAMP_NEW: + tgt_len = sizeof(struct target__kernel_timespec[3]); + target_cmsg->cmsg_type = + tswap32(TARGET_SCM_TIMESTAMPING_NEW); + break; + } + break; default: break; } @@ -1838,6 +1866,81 @@ static inline abi_long host_to_target_cmsg(struct target_msghdr *target_msgh, } break; } + case SCM_TIMESTAMPNS: + { + struct timespec *ts = (struct timespec *)data; + if (len != sizeof(struct timespec)) { + goto unimplemented; + } + + switch (target_expected_timestamp_version) { + case TARGET_TIMESTAMP_OLD: + { + struct target_timespec *target_ts = + (struct target_timespec *)target_data; + if (tgt_len != sizeof(struct target_timespec)) { + goto unimplemented; + } + + __put_user(ts->tv_sec, &target_ts->tv_sec); + __put_user(ts->tv_nsec, &target_ts->tv_nsec); + break; + } + case TARGET_TIMESTAMP_NEW: + { + struct target__kernel_timespec *target_ts = + (struct target__kernel_timespec *)target_data; + if (tgt_len != sizeof(struct target__kernel_timespec)) { + goto unimplemented; + } + + __put_user(ts->tv_sec, &target_ts->tv_sec); + __put_user(ts->tv_nsec, &target_ts->tv_nsec); + break; + } + } + break; + } + case SCM_TIMESTAMPING: + { + int i; + struct timespec *ts = (struct timespec *)data; + if (len != sizeof(struct timespec[3])) { + goto unimplemented; + } + + switch (target_expected_timestamp_version) { + case TARGET_TIMESTAMP_OLD: + { + struct target_timespec *target_ts = + (struct target_timespec *)target_data; + if (tgt_len != sizeof(struct target_timespec[3])) { + goto unimplemented; + } + + for (i = 0; i < 3; ++i) { + __put_user(ts[i].tv_sec, &target_ts[i].tv_sec); + __put_user(ts[i].tv_nsec, &target_ts[i].tv_nsec); + } + break; + } + case TARGET_TIMESTAMP_NEW: + { + struct target__kernel_timespec *target_ts = + (struct target__kernel_timespec *)target_data; + if (tgt_len != sizeof(struct target__kernel_timespec[3])) { + goto unimplemented; + } + + for (i = 0; i < 3; ++i) { + __put_user(ts[i].tv_sec, &target_ts[i].tv_sec); + __put_user(ts[i].tv_nsec, &target_ts[i].tv_nsec); + } + break; + } + } + break; + } case SCM_CREDENTIALS: { struct ucred *cred = (struct ucred *)data; @@ -2381,6 +2484,22 @@ set_timeout: target_timestamp_version = TARGET_TIMESTAMP_NEW; optname = SO_TIMESTAMP; break; + case TARGET_SO_TIMESTAMPNS_OLD: + target_timestamp_version = TARGET_TIMESTAMP_OLD; + optname = SO_TIMESTAMPNS; + break; + case TARGET_SO_TIMESTAMPNS_NEW: + target_timestamp_version = TARGET_TIMESTAMP_NEW; + optname = SO_TIMESTAMPNS; + break; + case TARGET_SO_TIMESTAMPING_OLD: + target_timestamp_version = TARGET_TIMESTAMP_OLD; + optname = SO_TIMESTAMPING; + break; + case TARGET_SO_TIMESTAMPING_NEW: + target_timestamp_version = TARGET_TIMESTAMP_NEW; + optname = SO_TIMESTAMPING; + break; case TARGET_SO_RCVLOWAT: optname = SO_RCVLOWAT; break; @@ -2393,7 +2512,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) { + if (!is_error(ret) && + (optname == SO_TIMESTAMP || optname == SO_TIMESTAMPNS || + optname == SO_TIMESTAMPING)) { target_expected_timestamp_version = target_timestamp_version; } break; @@ -2637,6 +2758,26 @@ get_timeout: (target_expected_timestamp_version == TARGET_TIMESTAMP_NEW); optname = SO_TIMESTAMP; goto int_case; + case TARGET_SO_TIMESTAMPNS_OLD: + timestamp_format_matches = + (target_expected_timestamp_version == TARGET_TIMESTAMP_OLD); + optname = SO_TIMESTAMPNS; + goto int_case; + case TARGET_SO_TIMESTAMPNS_NEW: + timestamp_format_matches = + (target_expected_timestamp_version == TARGET_TIMESTAMP_NEW); + optname = SO_TIMESTAMPNS; + goto int_case; + case TARGET_SO_TIMESTAMPING_OLD: + timestamp_format_matches = + (target_expected_timestamp_version == TARGET_TIMESTAMP_OLD); + optname = SO_TIMESTAMPING; + goto int_case; + case TARGET_SO_TIMESTAMPING_NEW: + timestamp_format_matches = + (target_expected_timestamp_version == TARGET_TIMESTAMP_NEW); + optname = SO_TIMESTAMPING; + goto int_case; case TARGET_SO_RCVLOWAT: optname = SO_RCVLOWAT; goto int_case; @@ -2661,9 +2802,9 @@ get_timeout: return ret; if (optname == SO_TYPE) { val = host_to_target_sock_type(val); - } - if (optname == SO_TIMESTAMP) { - val = val && timestamp_format_matches; + } else if ((optname == SO_TIMESTAMP || optname == SO_TIMESTAMPNS || + optname == SO_TIMESTAMPING) && !timestamp_format_matches) { + val = 0; } if (len > lv) len = lv; diff --git a/tests/tcg/multiarch/socket_timestamp.c b/tests/tcg/multiarch/socket_timestamp.c index 71ab1845de..3ae833ad44 100644 --- a/tests/tcg/multiarch/socket_timestamp.c +++ b/tests/tcg/multiarch/socket_timestamp.c @@ -1,5 +1,6 @@ #include <assert.h> #include <errno.h> +#include <linux/net_tstamp.h> #include <linux/types.h> #include <netinet/in.h> #include <stdint.h> @@ -11,6 +12,7 @@ #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> +#include <time.h> #include <unistd.h> #ifdef __kernel_old_timeval @@ -27,6 +29,33 @@ struct kernel_sock_timeval { int64_t tv_usec; }; +struct kernel_old_timespec { + __kernel_long_t tv_sec; + long tv_nsec; +}; + +struct kernel_timespec { + int64_t tv_sec; + long long tv_nsec; +}; + +struct scm_timestamping { + struct timespec ts[3]; +}; + +struct scm_old_timestamping { + struct kernel_old_timespec ts[3]; +}; + +struct scm_timestamping64 { + struct kernel_timespec ts[3]; +}; + +const int so_timestamping_flags = + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RX_SOFTWARE | + SOF_TIMESTAMPING_SOFTWARE; + int create_udp_socket(struct sockaddr_in *sockaddr) { socklen_t sockaddr_len; @@ -61,43 +90,47 @@ int create_udp_socket(struct sockaddr_in *sockaddr) * 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) +void check_timestamp_difference(const struct timespec *msg_ts, + const struct timespec *pkt_ts) { - 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)) + if (pkt_ts->tv_sec < msg_ts->tv_sec || + (pkt_ts->tv_sec == msg_ts->tv_sec && pkt_ts->tv_nsec < msg_ts->tv_nsec)) { 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); + "Packet received before sent: %lld.%06lld < %lld.%09lld\n", + (long long)pkt_ts->tv_sec, (long long)pkt_ts->tv_nsec, + (long long)msg_ts->tv_sec, (long long)msg_ts->tv_nsec); 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)) { + if (pkt_ts->tv_sec > msg_ts->tv_sec + 10 || + (pkt_ts->tv_sec == msg_ts->tv_sec + 10 && + pkt_ts->tv_nsec > msg_ts->tv_nsec)) { 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); + "%lld.%06lld > %lld.%09lld + 10\n", + (long long)pkt_ts->tv_sec, (long long)pkt_ts->tv_nsec, + (long long)msg_ts->tv_sec, (long long)msg_ts->tv_nsec); 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, + struct timespec ts = {0, 0}; + clock_gettime(CLOCK_REALTIME, &ts); +#ifdef MSG_CONFIRM + const int flags = MSG_CONFIRM; +#else + const int flags = 0; +#endif + sendto(sock, &ts, sizeof(ts), flags, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)); } -typedef void (*get_timeval_t)(const struct cmsghdr *cmsg, struct timeval *tv); +typedef void (*get_timespec_t)(const struct cmsghdr *cmsg, struct timespec *tv); - -void receive_packet(int sock, get_timeval_t get_timeval) +void receive_packet(int sock, get_timespec_t get_timespec) { struct msghdr msg = {0}; @@ -113,7 +146,7 @@ void receive_packet(int sock, get_timeval_t get_timeval) struct cmsghdr align; } u; struct cmsghdr *cmsg; - struct timeval msg_tv, pkt_tv; + struct timespec msg_ts, pkt_ts; int res; @@ -134,31 +167,35 @@ void receive_packet(int sock, get_timeval_t get_timeval) 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); + memcpy(&msg_ts, iov.iov_base, sizeof(msg_ts)); + printf("Message timestamp: %lld.%09lld\n", + (long long)msg_ts.tv_sec, (long long)msg_ts.tv_nsec); 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); + (*get_timespec)(cmsg, &pkt_ts); + printf("Packet timestamp: %lld.%09lld\n", + (long long)pkt_ts.tv_sec, (long long)pkt_ts.tv_nsec); - check_timestamp_difference(&msg_tv, &pkt_tv); + check_timestamp_difference(&msg_ts, &pkt_ts); } -void get_timeval_from_so_timestamp(const struct cmsghdr *cmsg, - struct timeval *tv) +void get_timespec_from_so_timestamp(const struct cmsghdr *cmsg, + struct timespec *ts) { + 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)); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(tv))); + + memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv)); + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000LL; } #ifdef SO_TIMESTAMP_OLD -void get_timeval_from_so_timestamp_old(const struct cmsghdr *cmsg, - struct timeval *tv) +void get_timespec_from_so_timestamp_old(const struct cmsghdr *cmsg, + struct timespec *ts) { struct kernel_old_timeval old_tv; assert(cmsg->cmsg_level == SOL_SOCKET); @@ -166,13 +203,13 @@ void get_timeval_from_so_timestamp_old(const struct cmsghdr *cmsg, 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; + ts->tv_sec = old_tv.tv_sec; + ts->tv_nsec = old_tv.tv_usec * 1000LL; } #ifdef SO_TIMESTAMP_NEW -void get_timeval_from_so_timestamp_new(const struct cmsghdr *cmsg, - struct timeval *tv) +void get_timespec_from_so_timestamp_new(const struct cmsghdr *cmsg, + struct timespec *ts) { struct kernel_sock_timeval sock_tv; assert(cmsg->cmsg_level == SOL_SOCKET); @@ -180,42 +217,298 @@ void get_timeval_from_so_timestamp_new(const struct cmsghdr *cmsg, 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; + ts->tv_sec = sock_tv.tv_sec; + ts->tv_nsec = sock_tv.tv_usec * 1000LL; } #endif /* defined(SO_TIMESTAMP_NEW) */ #endif /* defined(SO_TIMESTAMP_OLD) */ -void set_socket_option(int sock, int sockopt, int on) +void get_timespec_from_so_timestampns(const struct cmsghdr *cmsg, + struct timespec *ts) +{ + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SCM_TIMESTAMPNS); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(*ts))); + + memcpy(ts, CMSG_DATA(cmsg), sizeof(*ts)); +} + +#ifdef SO_TIMESTAMPNS_OLD +void get_timespec_from_so_timestampns_old(const struct cmsghdr *cmsg, + struct timespec *ts) +{ + struct kernel_old_timespec old_ts; + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SO_TIMESTAMPNS_OLD); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(old_ts))); + + memcpy(&old_ts, CMSG_DATA(cmsg), sizeof(old_ts)); + ts->tv_sec = old_ts.tv_sec; + ts->tv_nsec = old_ts.tv_nsec; +} + +#ifdef SO_TIMESTAMPNS_NEW +void get_timespec_from_so_timestampns_new(const struct cmsghdr *cmsg, + struct timespec *ts) +{ + struct kernel_timespec sock_ts; + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SO_TIMESTAMPNS_NEW); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(sock_ts))); + + memcpy(&sock_ts, CMSG_DATA(cmsg), sizeof(sock_ts)); + ts->tv_sec = sock_ts.tv_sec; + ts->tv_nsec = sock_ts.tv_nsec; +} +#endif /* defined(SO_TIMESTAMPNS_NEW) */ +#endif /* defined(SO_TIMESTAMPNS_OLD) */ + +void get_timespec_from_so_timestamping(const struct cmsghdr *cmsg, + struct timespec *ts) +{ + int i; + struct scm_timestamping tss; + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SCM_TIMESTAMPING); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(tss))); + + memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); + + for (i = 0; i < 3; ++i) { + if (tss.ts[i].tv_sec || tss.ts[i].tv_nsec) { + *ts = tss.ts[i]; + return; + } + } + assert(!"All three entries in scm_timestamping are empty"); +} + +#ifdef SO_TIMESTAMPING_OLD +void get_timespec_from_so_timestamping_old(const struct cmsghdr *cmsg, + struct timespec *ts) +{ + int i; + struct scm_old_timestamping tss; + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SO_TIMESTAMPING_OLD); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(tss))); + + memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); + + for (i = 0; i < 3; ++i) { + if (tss.ts[i].tv_sec || tss.ts[i].tv_nsec) { + ts->tv_sec = tss.ts[i].tv_sec; + ts->tv_nsec = tss.ts[i].tv_nsec; + return; + } + } + assert(!"All three entries in scm_old_timestamping are empty"); +} + +#ifdef SO_TIMESTAMPING_NEW +void get_timespec_from_so_timestamping_new(const struct cmsghdr *cmsg, + struct timespec *ts) +{ + int i; + struct scm_timestamping64 tss; + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SO_TIMESTAMPING_NEW); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(tss))); + + memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); + for (i = 0; i < 3; ++i) { + if (tss.ts[i].tv_sec || tss.ts[i].tv_nsec) { + ts->tv_sec = tss.ts[i].tv_sec; + ts->tv_nsec = tss.ts[i].tv_nsec; + return; + } + } + assert(!"All three entries in scm_timestamp64 are empty"); +} +#endif /* defined(SO_TIMESTAMPING_NEW) */ +#endif /* defined(SO_TIMESTAMPING_OLD) */ + +void set_socket_option(int sock, int sockopt, int set_to) { socklen_t len; - int val = on; + int val = set_to; 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)); + fprintf(stderr, "Failed at setsockopt(%d, SOL_SOCKET, %d, %d): %s\n", + sock, sockopt, set_to, strerror(err)); exit(err); } +#ifdef SO_TIMESTAMPING_NEW + if (sockopt == SO_TIMESTAMPING_NEW) { + /* + * `getsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING_NEW)` not implemented + * as of linux kernel v5.8-rc4. + */ + return; + } +#endif + 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)); + fprintf(stderr, "Failed at getsockopt(%d, SOL_SOCKET, %d): %s\n", + sock, sockopt, strerror(err)); exit(err); } assert(len == sizeof(val)); - assert(val == on); + assert(val == set_to); +} + +void child_steps(int sock, struct sockaddr_in addr, int run_old) +{ + /* Test 1: SO_TIMESTAMP */ + send_current_time(sock, addr); + + /* Test 2: SO_TIMESTAMPNS */ + printf("Test 2: SO_TIMESTAMPNS\n"); + set_socket_option(sock, SO_TIMESTAMPNS, 1); + receive_packet(sock, &get_timespec_from_so_timestampns); + set_socket_option(sock, SO_TIMESTAMPNS, 0); + + /* Test 3: SO_TIMESTAMPING */ + send_current_time(sock, addr); + + if (!run_old) { + return; + } + +#ifdef SO_TIMESTAMP_OLD + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { + /* Test 4a: SO_TIMESTAMP_OLD */ + printf("Test 4a: SO_TIMESTAMP_OLD\n"); + set_socket_option(sock, SO_TIMESTAMP_OLD, 1); + receive_packet(sock, &get_timespec_from_so_timestamp_old); + set_socket_option(sock, SO_TIMESTAMP_OLD, 0); + } +#ifdef SO_TIMESTAMP_NEW + else { + /* Test 4b: SO_TIMESTAMP_NEW */ + printf("Test 4b: SO_TIMESTAMP_NEW\n"); + set_socket_option(sock, SO_TIMESTAMP_NEW, 1); + receive_packet(sock, &get_timespec_from_so_timestamp_new); + set_socket_option(sock, SO_TIMESTAMP_NEW, 0); + } +#endif /* defined(SO_TIMESTAMP_NEW) */ +#endif /* defined(SO_TIMESTAMP_OLD) */ + +#ifdef SO_TIMESTAMPNS_OLD + if (SO_TIMESTAMPNS_OLD != SO_TIMESTAMPNS) { + /* Test 5a: SO_TIMESTAMPNS_OLD */ + send_current_time(sock, addr); + } +#ifdef SO_TIMESTAMPNS_NEW + else { + /* Test 5b: SO_TIMESTAMPNS_NEW */ + send_current_time(sock, addr); + } +#endif /* defined(SO_TIMESTAMPNS_NEW) */ +#endif /* defined(SO_TIMESTAMPNS_OLD) */ + +#ifdef SO_TIMESTAMPING_OLD + if (SO_TIMESTAMPING_OLD != SO_TIMESTAMPING) { + /* Test 6a: SO_TIMESTAMPING_OLD */ + printf("Test 6a: SO_TIMESTAMPING_OLD\n"); + set_socket_option(sock, SO_TIMESTAMPING_OLD, so_timestamping_flags); + receive_packet(sock, &get_timespec_from_so_timestamping_old); + set_socket_option(sock, SO_TIMESTAMPING_OLD, 0); + } +#ifdef SO_TIMESTAMPING_NEW + else { + /* Test 6b: SO_TIMESTAMPING_NEW */ + printf("Test 6b: SO_TIMESTAMPING_NEW\n"); + set_socket_option(sock, SO_TIMESTAMPING_NEW, so_timestamping_flags); + receive_packet(sock, &get_timespec_from_so_timestamping_new); + set_socket_option(sock, SO_TIMESTAMPING_NEW, 0); + } +#endif /* defined(SO_TIMESTAMPING_NEW) */ +#endif /* defined(SO_TIMESTAMPING_OLD) */ +} + +void parent_steps(int sock, struct sockaddr_in addr, int run_old) +{ + /* Test 1: SO_TIMESTAMP */ + printf("Test 1: SO_TIMESTAMP\n"); + set_socket_option(sock, SO_TIMESTAMP, 1); + receive_packet(sock, &get_timespec_from_so_timestamp); + set_socket_option(sock, SO_TIMESTAMP, 0); + + /* Test 2: SO_TIMESTAMPNS */ + send_current_time(sock, addr); + + /* Test 3: SO_TIMESTAMPING */ + printf("Test 3: SO_TIMESTAMPING\n"); + set_socket_option(sock, SO_TIMESTAMPING, so_timestamping_flags); + receive_packet(sock, &get_timespec_from_so_timestamping); + set_socket_option(sock, SO_TIMESTAMPING, 0); + + if (!run_old) { + return; + } + +#ifdef SO_TIMESTAMP_OLD + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { + /* Test 4a: SO_TIMESTAMP_OLD */ + send_current_time(sock, addr); + } +#ifdef SO_TIMESTAMP_NEW + else { + /* Test 4b: SO_TIMESTAMP_NEW */ + send_current_time(sock, addr); + } +#endif /* defined(SO_TIMESTAMP_NEW) */ +#endif /* defined(SO_TIMESTAMP_OLD) */ + +#ifdef SO_TIMESTAMPNS_OLD + if (SO_TIMESTAMPNS_OLD != SO_TIMESTAMPNS) { + /* Test 5a: SO_TIMESTAMPNS_OLD */ + printf("Test 5a: SO_TIMESTAMPNS_OLD\n"); + set_socket_option(sock, SO_TIMESTAMPNS_OLD, 1); + receive_packet(sock, &get_timespec_from_so_timestampns_old); + set_socket_option(sock, SO_TIMESTAMPNS_OLD, 0); + } +#ifdef SO_TIMESTAMPNS_NEW + else { + /* Test 5b: SO_TIMESTAMPNS_NEW */ + printf("Test 5b: SO_TIMESTAMPNS_NEW\n"); + set_socket_option(sock, SO_TIMESTAMPNS_NEW, 1); + receive_packet(sock, &get_timespec_from_so_timestampns_new); + set_socket_option(sock, SO_TIMESTAMPNS_NEW, 0); + } +#endif /* defined(SO_TIMESTAMPNS_NEW) */ +#endif /* defined(SO_TIMESTAMPNS_OLD) */ + +#ifdef SO_TIMESTAMPING_OLD + if (SO_TIMESTAMPING_OLD != SO_TIMESTAMPING) { + /* Test 6a: SO_TIMESTAMPING_OLD */ + send_current_time(sock, addr); + } +#ifdef SO_TIMESTAMPING_NEW + else { + /* Test 6b: SO_TIMESTAMPING_NEW */ + send_current_time(sock, addr); + } +#endif /* defined(SO_TIMESTAMPING_NEW) */ +#endif /* defined(SO_TIMESTAMPING_OLD) */ } int main(int argc, char **argv) { int parent_sock, child_sock; struct sockaddr_in parent_sockaddr, child_sockaddr; - int pid; + int pid, run_old; struct timeval tv = {0, 0}; gettimeofday(&tv, NULL); + /* Too close to y2038 old systems may not work. */ + run_old = tv.tv_sec < 0x7fffff00; + parent_sock = create_udp_socket(&parent_sockaddr); child_sock = create_udp_socket(&child_sockaddr); @@ -226,64 +519,15 @@ int main(int argc, char **argv) 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); + child_steps(child_sock, parent_sockaddr, run_old); + close(child_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); + parent_steps(parent_sock, child_sockaddr, run_old); + close(parent_sock); if (waitpid(pid, &child_status, 0) < 0) { int err = errno; -- 2.28.0.220.ged08abb693-goog ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 6/8] linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING 2020-08-11 7:09 ` [PATCH v2 6/8] linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING Shu-Chun Weng @ 2020-12-18 4:02 ` Shu-Chun Weng 0 siblings, 0 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-12-18 4:02 UTC (permalink / raw) To: qemu-devel; +Cc: Laurent Vivier [-- Attachment #1.1: Type: text/plain, Size: 38462 bytes --] Ping -- any comments on https://patchew.org/QEMU/cover.1597129029.git.scw@google.com/c1fdce46c35527ea9da34ca26eab4efcdac407db.1597129029.git.scw@google.com/ On Tue, Aug 11, 2020 at 12:10 AM Shu-Chun Weng <scw@google.com> wrote: > This change supports SO_TIMESTAMPNS_OLD/NEW and SO_TIMESTAMPING_OLD/NEW > for setsocketopt() with SOL_SOCKET. Based on the SO_TIMESTAMP_OLD/NEW > framework. The three pairs share the same flag `SOCK_TSTAMP_NEW` in > linux kernel for deciding if the old or the new format is used. > > Signed-off-by: Shu-Chun Weng <scw@google.com> > --- > v1 -> v2: > Only keep track of old/new format in a global state. > Fix style problems. > > linux-user/alpha/sockbits.h | 13 +- > linux-user/generic/sockbits.h | 8 + > linux-user/hppa/sockbits.h | 12 +- > linux-user/mips/sockbits.h | 8 + > linux-user/sparc/sockbits.h | 13 +- > linux-user/strace.c | 12 + > linux-user/syscall.c | 149 ++++++++- > tests/tcg/multiarch/socket_timestamp.c | 442 +++++++++++++++++++------ > 8 files changed, 540 insertions(+), 117 deletions(-) > > diff --git a/linux-user/alpha/sockbits.h b/linux-user/alpha/sockbits.h > index 40f0644df0..c2c88f432b 100644 > --- a/linux-user/alpha/sockbits.h > +++ b/linux-user/alpha/sockbits.h > @@ -51,8 +51,6 @@ > > #define TARGET_SO_PEERSEC 30 > #define TARGET_SO_PASSSEC 34 > -#define TARGET_SO_TIMESTAMPNS 35 > -#define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS > > /* Security levels - as per NRL IPv6 - don't actually do anything */ > #define TARGET_SO_SECURITY_AUTHENTICATION 19 > @@ -61,9 +59,6 @@ > > #define TARGET_SO_MARK 36 > > -#define TARGET_SO_TIMESTAMPING 37 > -#define TARGET_SCM_TIMESTAMPING TARGET_SO_TIMESTAMPING > - > #define TARGET_SO_RXQ_OVFL 40 > > #define TARGET_SO_WIFI_STATUS 41 > @@ -75,9 +70,17 @@ > > #define TARGET_SO_TIMESTAMP_OLD 29 > #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > +#define TARGET_SO_TIMESTAMPNS_OLD 35 > +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD > +#define TARGET_SO_TIMESTAMPING_OLD 37 > +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD > > #define TARGET_SO_TIMESTAMP_NEW 63 > #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > +#define TARGET_SO_TIMESTAMPNS_NEW 64 > +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW > +#define TARGET_SO_TIMESTAMPING_NEW 65 > +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_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 532cf2d3dc..a0496d8751 100644 > --- a/linux-user/generic/sockbits.h > +++ b/linux-user/generic/sockbits.h > @@ -56,8 +56,16 @@ > > #define TARGET_SO_TIMESTAMP_OLD 29 > #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > +#define TARGET_SO_TIMESTAMPNS_OLD 35 > +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD > +#define TARGET_SO_TIMESTAMPING_OLD 37 > +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD > > #define TARGET_SO_TIMESTAMP_NEW 63 > #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > +#define TARGET_SO_TIMESTAMPNS_NEW 64 > +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW > +#define TARGET_SO_TIMESTAMPING_NEW 65 > +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_NEW > > #endif > diff --git a/linux-user/hppa/sockbits.h b/linux-user/hppa/sockbits.h > index 284a47e74e..d7e9aa340d 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_TIMESTAMPNS 0x4013 > -#define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS > > #define TARGET_SO_SECURITY_AUTHENTICATION 0x4016 > #define TARGET_SO_SECURITY_ENCRYPTION_TRANSPORT 0x4017 > @@ -44,8 +42,6 @@ > #define TARGET_SO_PEERSEC 0x401d > #define TARGET_SO_PASSSEC 0x401e > #define TARGET_SO_MARK 0x401f > -#define TARGET_SO_TIMESTAMPING 0x4020 > -#define TARGET_SCM_TIMESTAMPING TARGET_SO_TIMESTAMPING > #define TARGET_SO_RXQ_OVFL 0x4021 > #define TARGET_SO_WIFI_STATUS 0x4022 > #define TARGET_SCM_WIFI_STATUS TARGET_SO_WIFI_STATUS > @@ -67,9 +63,17 @@ > > #define TARGET_SO_TIMESTAMP_OLD 0x4012 > #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > +#define TARGET_SO_TIMESTAMPNS_OLD 0x4013 > +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD > +#define TARGET_SO_TIMESTAMPING_OLD 0x4020 > +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD > > #define TARGET_SO_TIMESTAMP_NEW 0x4038 > #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > +#define TARGET_SO_TIMESTAMPNS_NEW 0x4039 > +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW > +#define TARGET_SO_TIMESTAMPING_NEW 0x403A > +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_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 b4c39d9588..49524e23ac 100644 > --- a/linux-user/mips/sockbits.h > +++ b/linux-user/mips/sockbits.h > @@ -69,9 +69,17 @@ > > #define TARGET_SO_TIMESTAMP_OLD 29 > #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > +#define TARGET_SO_TIMESTAMPNS_OLD 35 > +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD > +#define TARGET_SO_TIMESTAMPING_OLD 37 > +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD > > #define TARGET_SO_TIMESTAMP_NEW 63 > #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > +#define TARGET_SO_TIMESTAMPNS_NEW 64 > +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW > +#define TARGET_SO_TIMESTAMPING_NEW 65 > +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_NEW > > /** sock_type - Socket types > * > diff --git a/linux-user/sparc/sockbits.h b/linux-user/sparc/sockbits.h > index 07440efd14..c5fade3ad1 100644 > --- a/linux-user/sparc/sockbits.h > +++ b/linux-user/sparc/sockbits.h > @@ -51,14 +51,9 @@ > > #define TARGET_SO_PEERSEC 0x001e > #define TARGET_SO_PASSSEC 0x001f > -#define TARGET_SO_TIMESTAMPNS 0x0021 > -#define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS > > #define TARGET_SO_MARK 0x0022 > > -#define TARGET_SO_TIMESTAMPING 0x0023 > -#define TARGET_SCM_TIMESTAMPING TARGET_SO_TIMESTAMPING > - > #define TARGET_SO_RXQ_OVFL 0x0024 > > #define TARGET_SO_WIFI_STATUS 0x0025 > @@ -104,9 +99,17 @@ > > #define TARGET_SO_TIMESTAMP_OLD 0x001d > #define TARGET_SCM_TIMESTAMP_OLD TARGET_SO_TIMESTAMP_OLD > +#define TARGET_SO_TIMESTAMPNS_OLD 0x0021 > +#define TARGET_SCM_TIMESTAMPNS_OLD TARGET_SO_TIMESTAMPNS_OLD > +#define TARGET_SO_TIMESTAMPING_OLD 0x0023 > +#define TARGET_SCM_TIMESTAMPING_OLD TARGET_SO_TIMESTAMPING_OLD > > #define TARGET_SO_TIMESTAMP_NEW 0x0046 > #define TARGET_SCM_TIMESTAMP_NEW TARGET_SO_TIMESTAMP_NEW > +#define TARGET_SO_TIMESTAMPNS_NEW 0x0042 > +#define TARGET_SCM_TIMESTAMPNS_NEW TARGET_SO_TIMESTAMPNS_NEW > +#define TARGET_SO_TIMESTAMPING_NEW 0x0043 > +#define TARGET_SCM_TIMESTAMPING_NEW TARGET_SO_TIMESTAMPING_NEW > > /* Security levels - as per NRL IPv6 - don't actually do anything */ > #define TARGET_SO_SECURITY_AUTHENTICATION 0x5001 > diff --git a/linux-user/strace.c b/linux-user/strace.c > index a11a5e9e86..7aabb3c972 100644 > --- a/linux-user/strace.c > +++ b/linux-user/strace.c > @@ -2260,9 +2260,21 @@ print_optint: > case TARGET_SO_TIMESTAMP_OLD: > qemu_log("SO_TIMESTAMP_OLD,"); > goto print_optint; > + case TARGET_SO_TIMESTAMPNS_OLD: > + qemu_log("SO_TIMESTAMPNS_OLD,"); > + goto print_optint; > + case TARGET_SO_TIMESTAMPING_OLD: > + qemu_log("SO_TIMESTAMPING_OLD,"); > + goto print_optint; > case TARGET_SO_TIMESTAMP_NEW: > qemu_log("SO_TIMESTAMP_NEW,"); > goto print_optint; > + case TARGET_SO_TIMESTAMPNS_NEW: > + qemu_log("SO_TIMESTAMPNS_NEW,"); > + goto print_optint; > + case TARGET_SO_TIMESTAMPING_NEW: > + qemu_log("SO_TIMESTAMPING_NEW,"); > + goto print_optint; > case TARGET_SO_RCVLOWAT: > qemu_log("SO_RCVLOWAT,"); > goto print_optint; > diff --git a/linux-user/syscall.c b/linux-user/syscall.c > index e6b1a18cc0..bfc4219104 100644 > --- a/linux-user/syscall.c > +++ b/linux-user/syscall.c > @@ -1771,6 +1771,34 @@ static inline abi_long host_to_target_cmsg(struct > target_msghdr *target_msgh, > break; > } > break; > + case SCM_TIMESTAMPNS: > + switch (target_expected_timestamp_version) { > + case TARGET_TIMESTAMP_OLD: > + tgt_len = sizeof(struct target_timespec); > + target_cmsg->cmsg_type = > + tswap32(TARGET_SCM_TIMESTAMPNS_OLD); > + break; > + case TARGET_TIMESTAMP_NEW: > + tgt_len = sizeof(struct target__kernel_timespec); > + target_cmsg->cmsg_type = > + tswap32(TARGET_SCM_TIMESTAMPNS_NEW); > + break; > + } > + break; > + case SCM_TIMESTAMPING: > + switch (target_expected_timestamp_version) { > + case TARGET_TIMESTAMP_OLD: > + tgt_len = sizeof(struct target_timespec[3]); > + target_cmsg->cmsg_type = > + tswap32(TARGET_SCM_TIMESTAMPING_OLD); > + break; > + case TARGET_TIMESTAMP_NEW: > + tgt_len = sizeof(struct target__kernel_timespec[3]); > + target_cmsg->cmsg_type = > + tswap32(TARGET_SCM_TIMESTAMPING_NEW); > + break; > + } > + break; > default: > break; > } > @@ -1838,6 +1866,81 @@ static inline abi_long host_to_target_cmsg(struct > target_msghdr *target_msgh, > } > break; > } > + case SCM_TIMESTAMPNS: > + { > + struct timespec *ts = (struct timespec *)data; > + if (len != sizeof(struct timespec)) { > + goto unimplemented; > + } > + > + switch (target_expected_timestamp_version) { > + case TARGET_TIMESTAMP_OLD: > + { > + struct target_timespec *target_ts = > + (struct target_timespec *)target_data; > + if (tgt_len != sizeof(struct target_timespec)) { > + goto unimplemented; > + } > + > + __put_user(ts->tv_sec, &target_ts->tv_sec); > + __put_user(ts->tv_nsec, &target_ts->tv_nsec); > + break; > + } > + case TARGET_TIMESTAMP_NEW: > + { > + struct target__kernel_timespec *target_ts = > + (struct target__kernel_timespec *)target_data; > + if (tgt_len != sizeof(struct > target__kernel_timespec)) { > + goto unimplemented; > + } > + > + __put_user(ts->tv_sec, &target_ts->tv_sec); > + __put_user(ts->tv_nsec, &target_ts->tv_nsec); > + break; > + } > + } > + break; > + } > + case SCM_TIMESTAMPING: > + { > + int i; > + struct timespec *ts = (struct timespec *)data; > + if (len != sizeof(struct timespec[3])) { > + goto unimplemented; > + } > + > + switch (target_expected_timestamp_version) { > + case TARGET_TIMESTAMP_OLD: > + { > + struct target_timespec *target_ts = > + (struct target_timespec *)target_data; > + if (tgt_len != sizeof(struct target_timespec[3])) { > + goto unimplemented; > + } > + > + for (i = 0; i < 3; ++i) { > + __put_user(ts[i].tv_sec, &target_ts[i].tv_sec); > + __put_user(ts[i].tv_nsec, &target_ts[i].tv_nsec); > + } > + break; > + } > + case TARGET_TIMESTAMP_NEW: > + { > + struct target__kernel_timespec *target_ts = > + (struct target__kernel_timespec *)target_data; > + if (tgt_len != sizeof(struct > target__kernel_timespec[3])) { > + goto unimplemented; > + } > + > + for (i = 0; i < 3; ++i) { > + __put_user(ts[i].tv_sec, &target_ts[i].tv_sec); > + __put_user(ts[i].tv_nsec, &target_ts[i].tv_nsec); > + } > + break; > + } > + } > + break; > + } > case SCM_CREDENTIALS: > { > struct ucred *cred = (struct ucred *)data; > @@ -2381,6 +2484,22 @@ set_timeout: > target_timestamp_version = TARGET_TIMESTAMP_NEW; > optname = SO_TIMESTAMP; > break; > + case TARGET_SO_TIMESTAMPNS_OLD: > + target_timestamp_version = TARGET_TIMESTAMP_OLD; > + optname = SO_TIMESTAMPNS; > + break; > + case TARGET_SO_TIMESTAMPNS_NEW: > + target_timestamp_version = TARGET_TIMESTAMP_NEW; > + optname = SO_TIMESTAMPNS; > + break; > + case TARGET_SO_TIMESTAMPING_OLD: > + target_timestamp_version = TARGET_TIMESTAMP_OLD; > + optname = SO_TIMESTAMPING; > + break; > + case TARGET_SO_TIMESTAMPING_NEW: > + target_timestamp_version = TARGET_TIMESTAMP_NEW; > + optname = SO_TIMESTAMPING; > + break; > case TARGET_SO_RCVLOWAT: > optname = SO_RCVLOWAT; > break; > @@ -2393,7 +2512,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) { > + if (!is_error(ret) && > + (optname == SO_TIMESTAMP || optname == SO_TIMESTAMPNS || > + optname == SO_TIMESTAMPING)) { > target_expected_timestamp_version = target_timestamp_version; > } > break; > @@ -2637,6 +2758,26 @@ get_timeout: > (target_expected_timestamp_version == > TARGET_TIMESTAMP_NEW); > optname = SO_TIMESTAMP; > goto int_case; > + case TARGET_SO_TIMESTAMPNS_OLD: > + timestamp_format_matches = > + (target_expected_timestamp_version == > TARGET_TIMESTAMP_OLD); > + optname = SO_TIMESTAMPNS; > + goto int_case; > + case TARGET_SO_TIMESTAMPNS_NEW: > + timestamp_format_matches = > + (target_expected_timestamp_version == > TARGET_TIMESTAMP_NEW); > + optname = SO_TIMESTAMPNS; > + goto int_case; > + case TARGET_SO_TIMESTAMPING_OLD: > + timestamp_format_matches = > + (target_expected_timestamp_version == > TARGET_TIMESTAMP_OLD); > + optname = SO_TIMESTAMPING; > + goto int_case; > + case TARGET_SO_TIMESTAMPING_NEW: > + timestamp_format_matches = > + (target_expected_timestamp_version == > TARGET_TIMESTAMP_NEW); > + optname = SO_TIMESTAMPING; > + goto int_case; > case TARGET_SO_RCVLOWAT: > optname = SO_RCVLOWAT; > goto int_case; > @@ -2661,9 +2802,9 @@ get_timeout: > return ret; > if (optname == SO_TYPE) { > val = host_to_target_sock_type(val); > - } > - if (optname == SO_TIMESTAMP) { > - val = val && timestamp_format_matches; > + } else if ((optname == SO_TIMESTAMP || optname == SO_TIMESTAMPNS > || > + optname == SO_TIMESTAMPING) && > !timestamp_format_matches) { > + val = 0; > } > if (len > lv) > len = lv; > diff --git a/tests/tcg/multiarch/socket_timestamp.c > b/tests/tcg/multiarch/socket_timestamp.c > index 71ab1845de..3ae833ad44 100644 > --- a/tests/tcg/multiarch/socket_timestamp.c > +++ b/tests/tcg/multiarch/socket_timestamp.c > @@ -1,5 +1,6 @@ > #include <assert.h> > #include <errno.h> > +#include <linux/net_tstamp.h> > #include <linux/types.h> > #include <netinet/in.h> > #include <stdint.h> > @@ -11,6 +12,7 @@ > #include <sys/time.h> > #include <sys/types.h> > #include <sys/wait.h> > +#include <time.h> > #include <unistd.h> > > #ifdef __kernel_old_timeval > @@ -27,6 +29,33 @@ struct kernel_sock_timeval { > int64_t tv_usec; > }; > > +struct kernel_old_timespec { > + __kernel_long_t tv_sec; > + long tv_nsec; > +}; > + > +struct kernel_timespec { > + int64_t tv_sec; > + long long tv_nsec; > +}; > + > +struct scm_timestamping { > + struct timespec ts[3]; > +}; > + > +struct scm_old_timestamping { > + struct kernel_old_timespec ts[3]; > +}; > + > +struct scm_timestamping64 { > + struct kernel_timespec ts[3]; > +}; > + > +const int so_timestamping_flags = > + SOF_TIMESTAMPING_RX_HARDWARE | > + SOF_TIMESTAMPING_RX_SOFTWARE | > + SOF_TIMESTAMPING_SOFTWARE; > + > int create_udp_socket(struct sockaddr_in *sockaddr) > { > socklen_t sockaddr_len; > @@ -61,43 +90,47 @@ int create_udp_socket(struct sockaddr_in *sockaddr) > * 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) > +void check_timestamp_difference(const struct timespec *msg_ts, > + const struct timespec *pkt_ts) > { > - 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)) > + if (pkt_ts->tv_sec < msg_ts->tv_sec || > + (pkt_ts->tv_sec == msg_ts->tv_sec && pkt_ts->tv_nsec < > msg_ts->tv_nsec)) > { > 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); > + "Packet received before sent: %lld.%06lld < > %lld.%09lld\n", > + (long long)pkt_ts->tv_sec, (long long)pkt_ts->tv_nsec, > + (long long)msg_ts->tv_sec, (long long)msg_ts->tv_nsec); > 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)) { > + if (pkt_ts->tv_sec > msg_ts->tv_sec + 10 || > + (pkt_ts->tv_sec == msg_ts->tv_sec + 10 && > + pkt_ts->tv_nsec > msg_ts->tv_nsec)) { > 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); > + "%lld.%06lld > %lld.%09lld + 10\n", > + (long long)pkt_ts->tv_sec, (long long)pkt_ts->tv_nsec, > + (long long)msg_ts->tv_sec, (long long)msg_ts->tv_nsec); > 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, > + struct timespec ts = {0, 0}; > + clock_gettime(CLOCK_REALTIME, &ts); > +#ifdef MSG_CONFIRM > + const int flags = MSG_CONFIRM; > +#else > + const int flags = 0; > +#endif > + sendto(sock, &ts, sizeof(ts), flags, (struct sockaddr > *)&server_sockaddr, > sizeof(server_sockaddr)); > } > > -typedef void (*get_timeval_t)(const struct cmsghdr *cmsg, struct timeval > *tv); > +typedef void (*get_timespec_t)(const struct cmsghdr *cmsg, struct > timespec *tv); > > - > -void receive_packet(int sock, get_timeval_t get_timeval) > +void receive_packet(int sock, get_timespec_t get_timespec) > { > struct msghdr msg = {0}; > > @@ -113,7 +146,7 @@ void receive_packet(int sock, get_timeval_t > get_timeval) > struct cmsghdr align; > } u; > struct cmsghdr *cmsg; > - struct timeval msg_tv, pkt_tv; > + struct timespec msg_ts, pkt_ts; > > int res; > > @@ -134,31 +167,35 @@ void receive_packet(int sock, get_timeval_t > get_timeval) > > 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); > + memcpy(&msg_ts, iov.iov_base, sizeof(msg_ts)); > + printf("Message timestamp: %lld.%09lld\n", > + (long long)msg_ts.tv_sec, (long long)msg_ts.tv_nsec); > > 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); > + (*get_timespec)(cmsg, &pkt_ts); > + printf("Packet timestamp: %lld.%09lld\n", > + (long long)pkt_ts.tv_sec, (long long)pkt_ts.tv_nsec); > > - check_timestamp_difference(&msg_tv, &pkt_tv); > + check_timestamp_difference(&msg_ts, &pkt_ts); > } > > -void get_timeval_from_so_timestamp(const struct cmsghdr *cmsg, > - struct timeval *tv) > +void get_timespec_from_so_timestamp(const struct cmsghdr *cmsg, > + struct timespec *ts) > { > + 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)); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(tv))); > + > + memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv)); > + ts->tv_sec = tv.tv_sec; > + ts->tv_nsec = tv.tv_usec * 1000LL; > } > > #ifdef SO_TIMESTAMP_OLD > -void get_timeval_from_so_timestamp_old(const struct cmsghdr *cmsg, > - struct timeval *tv) > +void get_timespec_from_so_timestamp_old(const struct cmsghdr *cmsg, > + struct timespec *ts) > { > struct kernel_old_timeval old_tv; > assert(cmsg->cmsg_level == SOL_SOCKET); > @@ -166,13 +203,13 @@ void get_timeval_from_so_timestamp_old(const struct > cmsghdr *cmsg, > 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; > + ts->tv_sec = old_tv.tv_sec; > + ts->tv_nsec = old_tv.tv_usec * 1000LL; > } > > #ifdef SO_TIMESTAMP_NEW > -void get_timeval_from_so_timestamp_new(const struct cmsghdr *cmsg, > - struct timeval *tv) > +void get_timespec_from_so_timestamp_new(const struct cmsghdr *cmsg, > + struct timespec *ts) > { > struct kernel_sock_timeval sock_tv; > assert(cmsg->cmsg_level == SOL_SOCKET); > @@ -180,42 +217,298 @@ void get_timeval_from_so_timestamp_new(const struct > cmsghdr *cmsg, > 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; > + ts->tv_sec = sock_tv.tv_sec; > + ts->tv_nsec = sock_tv.tv_usec * 1000LL; > } > #endif /* defined(SO_TIMESTAMP_NEW) */ > #endif /* defined(SO_TIMESTAMP_OLD) */ > > -void set_socket_option(int sock, int sockopt, int on) > +void get_timespec_from_so_timestampns(const struct cmsghdr *cmsg, > + struct timespec *ts) > +{ > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SCM_TIMESTAMPNS); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(*ts))); > + > + memcpy(ts, CMSG_DATA(cmsg), sizeof(*ts)); > +} > + > +#ifdef SO_TIMESTAMPNS_OLD > +void get_timespec_from_so_timestampns_old(const struct cmsghdr *cmsg, > + struct timespec *ts) > +{ > + struct kernel_old_timespec old_ts; > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SO_TIMESTAMPNS_OLD); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(old_ts))); > + > + memcpy(&old_ts, CMSG_DATA(cmsg), sizeof(old_ts)); > + ts->tv_sec = old_ts.tv_sec; > + ts->tv_nsec = old_ts.tv_nsec; > +} > + > +#ifdef SO_TIMESTAMPNS_NEW > +void get_timespec_from_so_timestampns_new(const struct cmsghdr *cmsg, > + struct timespec *ts) > +{ > + struct kernel_timespec sock_ts; > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SO_TIMESTAMPNS_NEW); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(sock_ts))); > + > + memcpy(&sock_ts, CMSG_DATA(cmsg), sizeof(sock_ts)); > + ts->tv_sec = sock_ts.tv_sec; > + ts->tv_nsec = sock_ts.tv_nsec; > +} > +#endif /* defined(SO_TIMESTAMPNS_NEW) */ > +#endif /* defined(SO_TIMESTAMPNS_OLD) */ > + > +void get_timespec_from_so_timestamping(const struct cmsghdr *cmsg, > + struct timespec *ts) > +{ > + int i; > + struct scm_timestamping tss; > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SCM_TIMESTAMPING); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(tss))); > + > + memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); > + > + for (i = 0; i < 3; ++i) { > + if (tss.ts[i].tv_sec || tss.ts[i].tv_nsec) { > + *ts = tss.ts[i]; > + return; > + } > + } > + assert(!"All three entries in scm_timestamping are empty"); > +} > + > +#ifdef SO_TIMESTAMPING_OLD > +void get_timespec_from_so_timestamping_old(const struct cmsghdr *cmsg, > + struct timespec *ts) > +{ > + int i; > + struct scm_old_timestamping tss; > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SO_TIMESTAMPING_OLD); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(tss))); > + > + memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); > + > + for (i = 0; i < 3; ++i) { > + if (tss.ts[i].tv_sec || tss.ts[i].tv_nsec) { > + ts->tv_sec = tss.ts[i].tv_sec; > + ts->tv_nsec = tss.ts[i].tv_nsec; > + return; > + } > + } > + assert(!"All three entries in scm_old_timestamping are empty"); > +} > + > +#ifdef SO_TIMESTAMPING_NEW > +void get_timespec_from_so_timestamping_new(const struct cmsghdr *cmsg, > + struct timespec *ts) > +{ > + int i; > + struct scm_timestamping64 tss; > + assert(cmsg->cmsg_level == SOL_SOCKET); > + assert(cmsg->cmsg_type == SO_TIMESTAMPING_NEW); > + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(tss))); > + > + memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); > + for (i = 0; i < 3; ++i) { > + if (tss.ts[i].tv_sec || tss.ts[i].tv_nsec) { > + ts->tv_sec = tss.ts[i].tv_sec; > + ts->tv_nsec = tss.ts[i].tv_nsec; > + return; > + } > + } > + assert(!"All three entries in scm_timestamp64 are empty"); > +} > +#endif /* defined(SO_TIMESTAMPING_NEW) */ > +#endif /* defined(SO_TIMESTAMPING_OLD) */ > + > +void set_socket_option(int sock, int sockopt, int set_to) > { > socklen_t len; > - int val = on; > + int val = set_to; > 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)); > + fprintf(stderr, "Failed at setsockopt(%d, SOL_SOCKET, %d, %d): > %s\n", > + sock, sockopt, set_to, strerror(err)); > exit(err); > } > > +#ifdef SO_TIMESTAMPING_NEW > + if (sockopt == SO_TIMESTAMPING_NEW) { > + /* > + * `getsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING_NEW)` not > implemented > + * as of linux kernel v5.8-rc4. > + */ > + return; > + } > +#endif > + > 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)); > + fprintf(stderr, "Failed at getsockopt(%d, SOL_SOCKET, %d): %s\n", > + sock, sockopt, strerror(err)); > exit(err); > } > assert(len == sizeof(val)); > - assert(val == on); > + assert(val == set_to); > +} > + > +void child_steps(int sock, struct sockaddr_in addr, int run_old) > +{ > + /* Test 1: SO_TIMESTAMP */ > + send_current_time(sock, addr); > + > + /* Test 2: SO_TIMESTAMPNS */ > + printf("Test 2: SO_TIMESTAMPNS\n"); > + set_socket_option(sock, SO_TIMESTAMPNS, 1); > + receive_packet(sock, &get_timespec_from_so_timestampns); > + set_socket_option(sock, SO_TIMESTAMPNS, 0); > + > + /* Test 3: SO_TIMESTAMPING */ > + send_current_time(sock, addr); > + > + if (!run_old) { > + return; > + } > + > +#ifdef SO_TIMESTAMP_OLD > + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { > + /* Test 4a: SO_TIMESTAMP_OLD */ > + printf("Test 4a: SO_TIMESTAMP_OLD\n"); > + set_socket_option(sock, SO_TIMESTAMP_OLD, 1); > + receive_packet(sock, &get_timespec_from_so_timestamp_old); > + set_socket_option(sock, SO_TIMESTAMP_OLD, 0); > + } > +#ifdef SO_TIMESTAMP_NEW > + else { > + /* Test 4b: SO_TIMESTAMP_NEW */ > + printf("Test 4b: SO_TIMESTAMP_NEW\n"); > + set_socket_option(sock, SO_TIMESTAMP_NEW, 1); > + receive_packet(sock, &get_timespec_from_so_timestamp_new); > + set_socket_option(sock, SO_TIMESTAMP_NEW, 0); > + } > +#endif /* defined(SO_TIMESTAMP_NEW) */ > +#endif /* defined(SO_TIMESTAMP_OLD) */ > + > +#ifdef SO_TIMESTAMPNS_OLD > + if (SO_TIMESTAMPNS_OLD != SO_TIMESTAMPNS) { > + /* Test 5a: SO_TIMESTAMPNS_OLD */ > + send_current_time(sock, addr); > + } > +#ifdef SO_TIMESTAMPNS_NEW > + else { > + /* Test 5b: SO_TIMESTAMPNS_NEW */ > + send_current_time(sock, addr); > + } > +#endif /* defined(SO_TIMESTAMPNS_NEW) */ > +#endif /* defined(SO_TIMESTAMPNS_OLD) */ > + > +#ifdef SO_TIMESTAMPING_OLD > + if (SO_TIMESTAMPING_OLD != SO_TIMESTAMPING) { > + /* Test 6a: SO_TIMESTAMPING_OLD */ > + printf("Test 6a: SO_TIMESTAMPING_OLD\n"); > + set_socket_option(sock, SO_TIMESTAMPING_OLD, > so_timestamping_flags); > + receive_packet(sock, &get_timespec_from_so_timestamping_old); > + set_socket_option(sock, SO_TIMESTAMPING_OLD, 0); > + } > +#ifdef SO_TIMESTAMPING_NEW > + else { > + /* Test 6b: SO_TIMESTAMPING_NEW */ > + printf("Test 6b: SO_TIMESTAMPING_NEW\n"); > + set_socket_option(sock, SO_TIMESTAMPING_NEW, > so_timestamping_flags); > + receive_packet(sock, &get_timespec_from_so_timestamping_new); > + set_socket_option(sock, SO_TIMESTAMPING_NEW, 0); > + } > +#endif /* defined(SO_TIMESTAMPING_NEW) */ > +#endif /* defined(SO_TIMESTAMPING_OLD) */ > +} > + > +void parent_steps(int sock, struct sockaddr_in addr, int run_old) > +{ > + /* Test 1: SO_TIMESTAMP */ > + printf("Test 1: SO_TIMESTAMP\n"); > + set_socket_option(sock, SO_TIMESTAMP, 1); > + receive_packet(sock, &get_timespec_from_so_timestamp); > + set_socket_option(sock, SO_TIMESTAMP, 0); > + > + /* Test 2: SO_TIMESTAMPNS */ > + send_current_time(sock, addr); > + > + /* Test 3: SO_TIMESTAMPING */ > + printf("Test 3: SO_TIMESTAMPING\n"); > + set_socket_option(sock, SO_TIMESTAMPING, so_timestamping_flags); > + receive_packet(sock, &get_timespec_from_so_timestamping); > + set_socket_option(sock, SO_TIMESTAMPING, 0); > + > + if (!run_old) { > + return; > + } > + > +#ifdef SO_TIMESTAMP_OLD > + if (SO_TIMESTAMP_OLD != SO_TIMESTAMP) { > + /* Test 4a: SO_TIMESTAMP_OLD */ > + send_current_time(sock, addr); > + } > +#ifdef SO_TIMESTAMP_NEW > + else { > + /* Test 4b: SO_TIMESTAMP_NEW */ > + send_current_time(sock, addr); > + } > +#endif /* defined(SO_TIMESTAMP_NEW) */ > +#endif /* defined(SO_TIMESTAMP_OLD) */ > + > +#ifdef SO_TIMESTAMPNS_OLD > + if (SO_TIMESTAMPNS_OLD != SO_TIMESTAMPNS) { > + /* Test 5a: SO_TIMESTAMPNS_OLD */ > + printf("Test 5a: SO_TIMESTAMPNS_OLD\n"); > + set_socket_option(sock, SO_TIMESTAMPNS_OLD, 1); > + receive_packet(sock, &get_timespec_from_so_timestampns_old); > + set_socket_option(sock, SO_TIMESTAMPNS_OLD, 0); > + } > +#ifdef SO_TIMESTAMPNS_NEW > + else { > + /* Test 5b: SO_TIMESTAMPNS_NEW */ > + printf("Test 5b: SO_TIMESTAMPNS_NEW\n"); > + set_socket_option(sock, SO_TIMESTAMPNS_NEW, 1); > + receive_packet(sock, &get_timespec_from_so_timestampns_new); > + set_socket_option(sock, SO_TIMESTAMPNS_NEW, 0); > + } > +#endif /* defined(SO_TIMESTAMPNS_NEW) */ > +#endif /* defined(SO_TIMESTAMPNS_OLD) */ > + > +#ifdef SO_TIMESTAMPING_OLD > + if (SO_TIMESTAMPING_OLD != SO_TIMESTAMPING) { > + /* Test 6a: SO_TIMESTAMPING_OLD */ > + send_current_time(sock, addr); > + } > +#ifdef SO_TIMESTAMPING_NEW > + else { > + /* Test 6b: SO_TIMESTAMPING_NEW */ > + send_current_time(sock, addr); > + } > +#endif /* defined(SO_TIMESTAMPING_NEW) */ > +#endif /* defined(SO_TIMESTAMPING_OLD) */ > } > > int main(int argc, char **argv) > { > int parent_sock, child_sock; > struct sockaddr_in parent_sockaddr, child_sockaddr; > - int pid; > + int pid, run_old; > struct timeval tv = {0, 0}; > gettimeofday(&tv, NULL); > > + /* Too close to y2038 old systems may not work. */ > + run_old = tv.tv_sec < 0x7fffff00; > + > parent_sock = create_udp_socket(&parent_sockaddr); > child_sock = create_udp_socket(&child_sockaddr); > > @@ -226,64 +519,15 @@ int main(int argc, char **argv) > 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); > + child_steps(child_sock, parent_sockaddr, run_old); > + close(child_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); > + parent_steps(parent_sock, child_sockaddr, run_old); > + close(parent_sock); > > if (waitpid(pid, &child_status, 0) < 0) { > int err = errno; > -- > 2.28.0.220.ged08abb693-goog > > [-- Attachment #1.2: Type: text/html, Size: 46467 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3990 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 7/8] thunk: supports flexible arrays 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng ` (5 preceding siblings ...) 2020-08-11 7:09 ` [PATCH v2 6/8] linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING Shu-Chun Weng @ 2020-08-11 7:09 ` Shu-Chun Weng 2020-08-11 21:39 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 8/8] linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng 2020-12-18 8:24 ` [PATCH v2 0/8] fcntl, sockopt, and ioctl options Laurent Vivier 8 siblings, 1 reply; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent Flexible arrays may appear in the last field of a struct and are heavily used in the ioctl(SIOCETHTOOL) system call on Linux. E.g. struct ethtool_regs { __u32 cmd; __u32 version; /* driver-specific, indicates different chips/revs */ __u32 len; /* bytes */ __u8 data[0]; }; where number of elements in `data` is specified in `len`. It is translated into: STRUCT(ethtool_regs, TYPE_INT, /* cmd */ TYPE_INT, /* version */ TYPE_INT, /* len */ MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */ where the "2" passed to `MK_FLEXIBLE_ARRAY` means the number of element is specified by field number 2 (0-index). Signed-off-by: Shu-Chun Weng <scw@google.com> --- v1 -> v2: Fix style problems. include/exec/user/thunk.h | 24 ++++++ thunk.c | 152 +++++++++++++++++++++++++++++++++++++- 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/include/exec/user/thunk.h b/include/exec/user/thunk.h index 7992475c9f..d0d7c83f1f 100644 --- a/include/exec/user/thunk.h +++ b/include/exec/user/thunk.h @@ -39,12 +39,21 @@ typedef enum argtype { TYPE_ARRAY, TYPE_STRUCT, TYPE_OLDDEVT, + TYPE_FLEXIBLE_ARRAY, } argtype; #define MK_PTR(type) TYPE_PTR, type #define MK_ARRAY(type, size) TYPE_ARRAY, size, type #define MK_STRUCT(id) TYPE_STRUCT, id +/* + * Should only appear as the last element of a TYPE_STRUCT. `len_field_idx` is + * the index into the fields in the enclosing struct that specify the length of + * the flexibly array. The length field MUST be a TYPE_INT field. + */ +#define MK_FLEXIBLE_ARRAY(type, len_field_idx) \ + TYPE_FLEXIBLE_ARRAY, (len_field_idx), type + #define THUNK_TARGET 0 #define THUNK_HOST 1 @@ -55,6 +64,8 @@ typedef struct { int *field_offsets[2]; /* special handling */ void (*convert[2])(void *dst, const void *src); + int (*thunk_size[2])(const void *src); + int size[2]; int align[2]; const char *name; @@ -75,6 +86,11 @@ const argtype *thunk_convert(void *dst, const void *src, const argtype *type_ptr, int to_host); const argtype *thunk_print(void *arg, const argtype *type_ptr); +bool thunk_type_has_flexible_array(const argtype *type_ptr); +/* thunk_type_size but can handle TYPE_FLEXIBLE_ARRAY */ +int thunk_type_size_with_src(const void *src, const argtype *type_ptr, + int is_host); + extern StructEntry *struct_entries; int thunk_type_size_array(const argtype *type_ptr, int is_host); @@ -137,6 +153,12 @@ static inline int thunk_type_size(const argtype *type_ptr, int is_host) case TYPE_STRUCT: se = struct_entries + type_ptr[1]; return se->size[is_host]; + case TYPE_FLEXIBLE_ARRAY: + /* + * Flexible arrays do not count toward sizeof(). Users of structures + * containing them need to calculate it themselves. + */ + return 0; default: g_assert_not_reached(); } @@ -187,6 +209,8 @@ static inline int thunk_type_align(const argtype *type_ptr, int is_host) case TYPE_STRUCT: se = struct_entries + type_ptr[1]; return se->align[is_host]; + case TYPE_FLEXIBLE_ARRAY: + return thunk_type_align_array(type_ptr + 2, is_host); default: g_assert_not_reached(); } diff --git a/thunk.c b/thunk.c index c5d9719747..d9c6cba3bd 100644 --- a/thunk.c +++ b/thunk.c @@ -50,6 +50,8 @@ static inline const argtype *thunk_type_next(const argtype *type_ptr) return thunk_type_next_ptr(type_ptr + 1); case TYPE_STRUCT: return type_ptr + 1; + case TYPE_FLEXIBLE_ARRAY: + return thunk_type_next_ptr(type_ptr + 1); default: return NULL; } @@ -122,6 +124,34 @@ void thunk_register_struct_direct(int id, const char *name, se->name = name; } +static const argtype * +thunk_convert_flexible_array(void *dst, const void *src, + const uint8_t *dst_struct, + const uint8_t *src_struct, const argtype *type_ptr, + const StructEntry *se, int to_host) { + int len_field_idx, dst_size, src_size, i; + uint32_t array_length; + uint8_t *d; + const uint8_t *s; + + assert(*type_ptr == TYPE_FLEXIBLE_ARRAY); + type_ptr++; + len_field_idx = *type_ptr++; + array_length = + *(const uint32_t *)(to_host ? + dst_struct + se->field_offsets[1][len_field_idx] : + src_struct + se->field_offsets[0][len_field_idx]); + dst_size = thunk_type_size(type_ptr, to_host); + src_size = thunk_type_size(type_ptr, to_host); + d = dst; + s = src; + for (i = 0; i < array_length; i++) { + thunk_convert(d, s, type_ptr, to_host); + d += dst_size; + s += src_size; + } + return thunk_type_next(type_ptr); +} /* now we can define the main conversion functions */ const argtype *thunk_convert(void *dst, const void *src, @@ -246,7 +276,7 @@ const argtype *thunk_convert(void *dst, const void *src, assert(*type_ptr < max_struct_entries); se = struct_entries + *type_ptr++; - if (se->convert[0] != NULL) { + if (se->convert[to_host] != NULL) { /* specific conversion is needed */ (*se->convert[to_host])(dst, src); } else { @@ -256,7 +286,18 @@ const argtype *thunk_convert(void *dst, const void *src, src_offsets = se->field_offsets[1 - to_host]; d = dst; s = src; - for(i = 0;i < se->nb_fields; i++) { + for (i = 0; i < se->nb_fields; i++) { + if (*field_types == TYPE_FLEXIBLE_ARRAY) { + field_types = thunk_convert_flexible_array( + d + dst_offsets[i], + s + src_offsets[i], + d, + s, + field_types, + se, + to_host); + continue; + } field_types = thunk_convert(d + dst_offsets[i], s + src_offsets[i], field_types, to_host); @@ -264,6 +305,11 @@ const argtype *thunk_convert(void *dst, const void *src, } } break; + case TYPE_FLEXIBLE_ARRAY: + fprintf(stderr, + "Invalid flexible array (type 0x%x) outside of a structure\n", + type); + break; default: fprintf(stderr, "Invalid type 0x%x\n", type); break; @@ -271,6 +317,45 @@ const argtype *thunk_convert(void *dst, const void *src, return type_ptr; } +static const argtype * +thunk_print_flexible_array(void *arg, const uint8_t *arg_struct, + const argtype *type_ptr, const StructEntry *se) { + int array_length, len_field_idx, arg_size, i; + uint8_t *a; + int is_string = 0; + + assert(*type_ptr == TYPE_FLEXIBLE_ARRAY); + type_ptr++; + len_field_idx = *type_ptr++; + + array_length = tswap32( + *(const uint32_t *)(arg_struct + se->field_offsets[0][len_field_idx])); + arg_size = thunk_type_size(type_ptr, 0); + a = arg; + + if (*type_ptr == TYPE_CHAR) { + qemu_log("\""); + is_string = 1; + } else { + qemu_log("["); + } + + for (i = 0; i < array_length; i++) { + if (i > 0 && !is_string) { + qemu_log(","); + } + thunk_print(a, type_ptr); + a += arg_size; + } + + if (is_string) { + qemu_log("\""); + } else { + qemu_log("]"); + } + return thunk_type_next(type_ptr); +} + const argtype *thunk_print(void *arg, const argtype *type_ptr) { int type; @@ -414,17 +499,80 @@ const argtype *thunk_print(void *arg, const argtype *type_ptr) if (i > 0) { qemu_log(","); } + if (*field_types == TYPE_FLEXIBLE_ARRAY) { + field_types = thunk_print_flexible_array( + a + arg_offsets[i], a, field_types, se); + continue; + } field_types = thunk_print(a + arg_offsets[i], field_types); } qemu_log("}"); } break; + case TYPE_FLEXIBLE_ARRAY: + fprintf(stderr, + "Invalid flexible array (type 0x%x) outside of a structure\n", + type); + break; default: g_assert_not_reached(); } return type_ptr; } +bool thunk_type_has_flexible_array(const argtype *type_ptr) +{ + int i; + const StructEntry *se; + const argtype *field_types; + if (*type_ptr != TYPE_STRUCT) { + return false; + } + se = struct_entries + type_ptr[1]; + field_types = se->field_types; + for (i = 0; i < se->nb_fields; i++) { + if (*field_types == TYPE_FLEXIBLE_ARRAY) { + return true; + } + field_types = thunk_type_next(type_ptr); + } + return false; +} + +int thunk_type_size_with_src(const void *src, const argtype *type_ptr, + int is_host) +{ + switch (*type_ptr) { + case TYPE_STRUCT: { + int i; + const StructEntry *se = struct_entries + type_ptr[1]; + const argtype *field_types; + if (se->thunk_size[is_host] != NULL) { + return (*se->thunk_size[is_host])(src); + } + + field_types = se->field_types; + for (i = 0; i < se->nb_fields; i++) { + if (*field_types == TYPE_FLEXIBLE_ARRAY) { + uint32_t array_length = *(const uint32_t *)( + (const uint8_t *)src + + se->field_offsets[is_host][field_types[1]]); + if (!is_host) { + array_length = tswap32(array_length); + } + return se->size[is_host] + + array_length * + thunk_type_size(field_types + 2, is_host); + } + field_types = thunk_type_next(type_ptr); + } + return se->size[is_host]; + } + default: + return thunk_type_size(type_ptr, is_host); + } +} + /* from em86 */ /* Utility function: Table-driven functions to translate bitmasks -- 2.28.0.220.ged08abb693-goog ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 7/8] thunk: supports flexible arrays 2020-08-11 7:09 ` [PATCH v2 7/8] thunk: supports flexible arrays Shu-Chun Weng @ 2020-08-11 21:39 ` Shu-Chun Weng 2020-12-18 4:03 ` Shu-Chun Weng 0 siblings, 1 reply; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 21:39 UTC (permalink / raw) To: qemu-devel, Riku Voipio; +Cc: Laurent Vivier [-- Attachment #1.1: Type: text/plain, Size: 11338 bytes --] Forgot to +riku.voipio@iki.fi when generating v2. On Tue, Aug 11, 2020 at 12:10 AM Shu-Chun Weng <scw@google.com> wrote: > Flexible arrays may appear in the last field of a struct and are heavily > used in the ioctl(SIOCETHTOOL) system call on Linux. E.g. > > struct ethtool_regs { > __u32 cmd; > __u32 version; /* driver-specific, indicates different chips/revs > */ > __u32 len; /* bytes */ > __u8 data[0]; > }; > > where number of elements in `data` is specified in `len`. It is translated > into: > > STRUCT(ethtool_regs, > TYPE_INT, /* cmd */ > TYPE_INT, /* version */ > TYPE_INT, /* len */ > MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */ > > where the "2" passed to `MK_FLEXIBLE_ARRAY` means the number of element > is specified by field number 2 (0-index). > > Signed-off-by: Shu-Chun Weng <scw@google.com> > --- > v1 -> v2: > Fix style problems. > > include/exec/user/thunk.h | 24 ++++++ > thunk.c | 152 +++++++++++++++++++++++++++++++++++++- > 2 files changed, 174 insertions(+), 2 deletions(-) > > diff --git a/include/exec/user/thunk.h b/include/exec/user/thunk.h > index 7992475c9f..d0d7c83f1f 100644 > --- a/include/exec/user/thunk.h > +++ b/include/exec/user/thunk.h > @@ -39,12 +39,21 @@ typedef enum argtype { > TYPE_ARRAY, > TYPE_STRUCT, > TYPE_OLDDEVT, > + TYPE_FLEXIBLE_ARRAY, > } argtype; > > #define MK_PTR(type) TYPE_PTR, type > #define MK_ARRAY(type, size) TYPE_ARRAY, size, type > #define MK_STRUCT(id) TYPE_STRUCT, id > > +/* > + * Should only appear as the last element of a TYPE_STRUCT. > `len_field_idx` is > + * the index into the fields in the enclosing struct that specify the > length of > + * the flexibly array. The length field MUST be a TYPE_INT field. > + */ > +#define MK_FLEXIBLE_ARRAY(type, len_field_idx) \ > + TYPE_FLEXIBLE_ARRAY, (len_field_idx), type > + > #define THUNK_TARGET 0 > #define THUNK_HOST 1 > > @@ -55,6 +64,8 @@ typedef struct { > int *field_offsets[2]; > /* special handling */ > void (*convert[2])(void *dst, const void *src); > + int (*thunk_size[2])(const void *src); > + > int size[2]; > int align[2]; > const char *name; > @@ -75,6 +86,11 @@ const argtype *thunk_convert(void *dst, const void *src, > const argtype *type_ptr, int to_host); > const argtype *thunk_print(void *arg, const argtype *type_ptr); > > +bool thunk_type_has_flexible_array(const argtype *type_ptr); > +/* thunk_type_size but can handle TYPE_FLEXIBLE_ARRAY */ > +int thunk_type_size_with_src(const void *src, const argtype *type_ptr, > + int is_host); > + > extern StructEntry *struct_entries; > > int thunk_type_size_array(const argtype *type_ptr, int is_host); > @@ -137,6 +153,12 @@ static inline int thunk_type_size(const argtype > *type_ptr, int is_host) > case TYPE_STRUCT: > se = struct_entries + type_ptr[1]; > return se->size[is_host]; > + case TYPE_FLEXIBLE_ARRAY: > + /* > + * Flexible arrays do not count toward sizeof(). Users of > structures > + * containing them need to calculate it themselves. > + */ > + return 0; > default: > g_assert_not_reached(); > } > @@ -187,6 +209,8 @@ static inline int thunk_type_align(const argtype > *type_ptr, int is_host) > case TYPE_STRUCT: > se = struct_entries + type_ptr[1]; > return se->align[is_host]; > + case TYPE_FLEXIBLE_ARRAY: > + return thunk_type_align_array(type_ptr + 2, is_host); > default: > g_assert_not_reached(); > } > diff --git a/thunk.c b/thunk.c > index c5d9719747..d9c6cba3bd 100644 > --- a/thunk.c > +++ b/thunk.c > @@ -50,6 +50,8 @@ static inline const argtype *thunk_type_next(const > argtype *type_ptr) > return thunk_type_next_ptr(type_ptr + 1); > case TYPE_STRUCT: > return type_ptr + 1; > + case TYPE_FLEXIBLE_ARRAY: > + return thunk_type_next_ptr(type_ptr + 1); > default: > return NULL; > } > @@ -122,6 +124,34 @@ void thunk_register_struct_direct(int id, const char > *name, > se->name = name; > } > > +static const argtype * > +thunk_convert_flexible_array(void *dst, const void *src, > + const uint8_t *dst_struct, > + const uint8_t *src_struct, const argtype > *type_ptr, > + const StructEntry *se, int to_host) { > + int len_field_idx, dst_size, src_size, i; > + uint32_t array_length; > + uint8_t *d; > + const uint8_t *s; > + > + assert(*type_ptr == TYPE_FLEXIBLE_ARRAY); > + type_ptr++; > + len_field_idx = *type_ptr++; > + array_length = > + *(const uint32_t *)(to_host ? > + dst_struct + > se->field_offsets[1][len_field_idx] : > + src_struct + > se->field_offsets[0][len_field_idx]); > + dst_size = thunk_type_size(type_ptr, to_host); > + src_size = thunk_type_size(type_ptr, to_host); > + d = dst; > + s = src; > + for (i = 0; i < array_length; i++) { > + thunk_convert(d, s, type_ptr, to_host); > + d += dst_size; > + s += src_size; > + } > + return thunk_type_next(type_ptr); > +} > > /* now we can define the main conversion functions */ > const argtype *thunk_convert(void *dst, const void *src, > @@ -246,7 +276,7 @@ const argtype *thunk_convert(void *dst, const void > *src, > > assert(*type_ptr < max_struct_entries); > se = struct_entries + *type_ptr++; > - if (se->convert[0] != NULL) { > + if (se->convert[to_host] != NULL) { > /* specific conversion is needed */ > (*se->convert[to_host])(dst, src); > } else { > @@ -256,7 +286,18 @@ const argtype *thunk_convert(void *dst, const void > *src, > src_offsets = se->field_offsets[1 - to_host]; > d = dst; > s = src; > - for(i = 0;i < se->nb_fields; i++) { > + for (i = 0; i < se->nb_fields; i++) { > + if (*field_types == TYPE_FLEXIBLE_ARRAY) { > + field_types = thunk_convert_flexible_array( > + d + dst_offsets[i], > + s + src_offsets[i], > + d, > + s, > + field_types, > + se, > + to_host); > + continue; > + } > field_types = thunk_convert(d + dst_offsets[i], > s + src_offsets[i], > field_types, to_host); > @@ -264,6 +305,11 @@ const argtype *thunk_convert(void *dst, const void > *src, > } > } > break; > + case TYPE_FLEXIBLE_ARRAY: > + fprintf(stderr, > + "Invalid flexible array (type 0x%x) outside of a > structure\n", > + type); > + break; > default: > fprintf(stderr, "Invalid type 0x%x\n", type); > break; > @@ -271,6 +317,45 @@ const argtype *thunk_convert(void *dst, const void > *src, > return type_ptr; > } > > +static const argtype * > +thunk_print_flexible_array(void *arg, const uint8_t *arg_struct, > + const argtype *type_ptr, const StructEntry > *se) { > + int array_length, len_field_idx, arg_size, i; > + uint8_t *a; > + int is_string = 0; > + > + assert(*type_ptr == TYPE_FLEXIBLE_ARRAY); > + type_ptr++; > + len_field_idx = *type_ptr++; > + > + array_length = tswap32( > + *(const uint32_t *)(arg_struct + > se->field_offsets[0][len_field_idx])); > + arg_size = thunk_type_size(type_ptr, 0); > + a = arg; > + > + if (*type_ptr == TYPE_CHAR) { > + qemu_log("\""); > + is_string = 1; > + } else { > + qemu_log("["); > + } > + > + for (i = 0; i < array_length; i++) { > + if (i > 0 && !is_string) { > + qemu_log(","); > + } > + thunk_print(a, type_ptr); > + a += arg_size; > + } > + > + if (is_string) { > + qemu_log("\""); > + } else { > + qemu_log("]"); > + } > + return thunk_type_next(type_ptr); > +} > + > const argtype *thunk_print(void *arg, const argtype *type_ptr) > { > int type; > @@ -414,17 +499,80 @@ const argtype *thunk_print(void *arg, const argtype > *type_ptr) > if (i > 0) { > qemu_log(","); > } > + if (*field_types == TYPE_FLEXIBLE_ARRAY) { > + field_types = thunk_print_flexible_array( > + a + arg_offsets[i], a, field_types, se); > + continue; > + } > field_types = thunk_print(a + arg_offsets[i], > field_types); > } > qemu_log("}"); > } > break; > + case TYPE_FLEXIBLE_ARRAY: > + fprintf(stderr, > + "Invalid flexible array (type 0x%x) outside of a > structure\n", > + type); > + break; > default: > g_assert_not_reached(); > } > return type_ptr; > } > > +bool thunk_type_has_flexible_array(const argtype *type_ptr) > +{ > + int i; > + const StructEntry *se; > + const argtype *field_types; > + if (*type_ptr != TYPE_STRUCT) { > + return false; > + } > + se = struct_entries + type_ptr[1]; > + field_types = se->field_types; > + for (i = 0; i < se->nb_fields; i++) { > + if (*field_types == TYPE_FLEXIBLE_ARRAY) { > + return true; > + } > + field_types = thunk_type_next(type_ptr); > + } > + return false; > +} > + > +int thunk_type_size_with_src(const void *src, const argtype *type_ptr, > + int is_host) > +{ > + switch (*type_ptr) { > + case TYPE_STRUCT: { > + int i; > + const StructEntry *se = struct_entries + type_ptr[1]; > + const argtype *field_types; > + if (se->thunk_size[is_host] != NULL) { > + return (*se->thunk_size[is_host])(src); > + } > + > + field_types = se->field_types; > + for (i = 0; i < se->nb_fields; i++) { > + if (*field_types == TYPE_FLEXIBLE_ARRAY) { > + uint32_t array_length = *(const uint32_t *)( > + (const uint8_t *)src + > + se->field_offsets[is_host][field_types[1]]); > + if (!is_host) { > + array_length = tswap32(array_length); > + } > + return se->size[is_host] + > + array_length * > + thunk_type_size(field_types + 2, is_host); > + } > + field_types = thunk_type_next(type_ptr); > + } > + return se->size[is_host]; > + } > + default: > + return thunk_type_size(type_ptr, is_host); > + } > +} > + > /* from em86 */ > > /* Utility function: Table-driven functions to translate bitmasks > -- > 2.28.0.220.ged08abb693-goog > > [-- Attachment #1.2: Type: text/html, Size: 14149 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3844 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 7/8] thunk: supports flexible arrays 2020-08-11 21:39 ` Shu-Chun Weng @ 2020-12-18 4:03 ` Shu-Chun Weng 0 siblings, 0 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-12-18 4:03 UTC (permalink / raw) To: qemu-devel, Riku Voipio; +Cc: Laurent Vivier [-- Attachment #1.1: Type: text/plain, Size: 11917 bytes --] Ping -- any comments on https://patchew.org/QEMU/cover.1597129029.git.scw@google.com/e0754f52180aee6418eae8b3b8aa5981fcac12fd.1597129029.git.scw@google.com/ On Tue, Aug 11, 2020 at 2:39 PM Shu-Chun Weng <scw@google.com> wrote: > Forgot to +riku.voipio@iki.fi when generating v2. > > On Tue, Aug 11, 2020 at 12:10 AM Shu-Chun Weng <scw@google.com> wrote: > >> Flexible arrays may appear in the last field of a struct and are heavily >> used in the ioctl(SIOCETHTOOL) system call on Linux. E.g. >> >> struct ethtool_regs { >> __u32 cmd; >> __u32 version; /* driver-specific, indicates different chips/revs >> */ >> __u32 len; /* bytes */ >> __u8 data[0]; >> }; >> >> where number of elements in `data` is specified in `len`. It is translated >> into: >> >> STRUCT(ethtool_regs, >> TYPE_INT, /* cmd */ >> TYPE_INT, /* version */ >> TYPE_INT, /* len */ >> MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */ >> >> where the "2" passed to `MK_FLEXIBLE_ARRAY` means the number of element >> is specified by field number 2 (0-index). >> >> Signed-off-by: Shu-Chun Weng <scw@google.com> >> --- >> v1 -> v2: >> Fix style problems. >> >> include/exec/user/thunk.h | 24 ++++++ >> thunk.c | 152 +++++++++++++++++++++++++++++++++++++- >> 2 files changed, 174 insertions(+), 2 deletions(-) >> >> diff --git a/include/exec/user/thunk.h b/include/exec/user/thunk.h >> index 7992475c9f..d0d7c83f1f 100644 >> --- a/include/exec/user/thunk.h >> +++ b/include/exec/user/thunk.h >> @@ -39,12 +39,21 @@ typedef enum argtype { >> TYPE_ARRAY, >> TYPE_STRUCT, >> TYPE_OLDDEVT, >> + TYPE_FLEXIBLE_ARRAY, >> } argtype; >> >> #define MK_PTR(type) TYPE_PTR, type >> #define MK_ARRAY(type, size) TYPE_ARRAY, size, type >> #define MK_STRUCT(id) TYPE_STRUCT, id >> >> +/* >> + * Should only appear as the last element of a TYPE_STRUCT. >> `len_field_idx` is >> + * the index into the fields in the enclosing struct that specify the >> length of >> + * the flexibly array. The length field MUST be a TYPE_INT field. >> + */ >> +#define MK_FLEXIBLE_ARRAY(type, len_field_idx) \ >> + TYPE_FLEXIBLE_ARRAY, (len_field_idx), type >> + >> #define THUNK_TARGET 0 >> #define THUNK_HOST 1 >> >> @@ -55,6 +64,8 @@ typedef struct { >> int *field_offsets[2]; >> /* special handling */ >> void (*convert[2])(void *dst, const void *src); >> + int (*thunk_size[2])(const void *src); >> + >> int size[2]; >> int align[2]; >> const char *name; >> @@ -75,6 +86,11 @@ const argtype *thunk_convert(void *dst, const void >> *src, >> const argtype *type_ptr, int to_host); >> const argtype *thunk_print(void *arg, const argtype *type_ptr); >> >> +bool thunk_type_has_flexible_array(const argtype *type_ptr); >> +/* thunk_type_size but can handle TYPE_FLEXIBLE_ARRAY */ >> +int thunk_type_size_with_src(const void *src, const argtype *type_ptr, >> + int is_host); >> + >> extern StructEntry *struct_entries; >> >> int thunk_type_size_array(const argtype *type_ptr, int is_host); >> @@ -137,6 +153,12 @@ static inline int thunk_type_size(const argtype >> *type_ptr, int is_host) >> case TYPE_STRUCT: >> se = struct_entries + type_ptr[1]; >> return se->size[is_host]; >> + case TYPE_FLEXIBLE_ARRAY: >> + /* >> + * Flexible arrays do not count toward sizeof(). Users of >> structures >> + * containing them need to calculate it themselves. >> + */ >> + return 0; >> default: >> g_assert_not_reached(); >> } >> @@ -187,6 +209,8 @@ static inline int thunk_type_align(const argtype >> *type_ptr, int is_host) >> case TYPE_STRUCT: >> se = struct_entries + type_ptr[1]; >> return se->align[is_host]; >> + case TYPE_FLEXIBLE_ARRAY: >> + return thunk_type_align_array(type_ptr + 2, is_host); >> default: >> g_assert_not_reached(); >> } >> diff --git a/thunk.c b/thunk.c >> index c5d9719747..d9c6cba3bd 100644 >> --- a/thunk.c >> +++ b/thunk.c >> @@ -50,6 +50,8 @@ static inline const argtype *thunk_type_next(const >> argtype *type_ptr) >> return thunk_type_next_ptr(type_ptr + 1); >> case TYPE_STRUCT: >> return type_ptr + 1; >> + case TYPE_FLEXIBLE_ARRAY: >> + return thunk_type_next_ptr(type_ptr + 1); >> default: >> return NULL; >> } >> @@ -122,6 +124,34 @@ void thunk_register_struct_direct(int id, const char >> *name, >> se->name = name; >> } >> >> +static const argtype * >> +thunk_convert_flexible_array(void *dst, const void *src, >> + const uint8_t *dst_struct, >> + const uint8_t *src_struct, const argtype >> *type_ptr, >> + const StructEntry *se, int to_host) { >> + int len_field_idx, dst_size, src_size, i; >> + uint32_t array_length; >> + uint8_t *d; >> + const uint8_t *s; >> + >> + assert(*type_ptr == TYPE_FLEXIBLE_ARRAY); >> + type_ptr++; >> + len_field_idx = *type_ptr++; >> + array_length = >> + *(const uint32_t *)(to_host ? >> + dst_struct + >> se->field_offsets[1][len_field_idx] : >> + src_struct + >> se->field_offsets[0][len_field_idx]); >> + dst_size = thunk_type_size(type_ptr, to_host); >> + src_size = thunk_type_size(type_ptr, to_host); >> + d = dst; >> + s = src; >> + for (i = 0; i < array_length; i++) { >> + thunk_convert(d, s, type_ptr, to_host); >> + d += dst_size; >> + s += src_size; >> + } >> + return thunk_type_next(type_ptr); >> +} >> >> /* now we can define the main conversion functions */ >> const argtype *thunk_convert(void *dst, const void *src, >> @@ -246,7 +276,7 @@ const argtype *thunk_convert(void *dst, const void >> *src, >> >> assert(*type_ptr < max_struct_entries); >> se = struct_entries + *type_ptr++; >> - if (se->convert[0] != NULL) { >> + if (se->convert[to_host] != NULL) { >> /* specific conversion is needed */ >> (*se->convert[to_host])(dst, src); >> } else { >> @@ -256,7 +286,18 @@ const argtype *thunk_convert(void *dst, const void >> *src, >> src_offsets = se->field_offsets[1 - to_host]; >> d = dst; >> s = src; >> - for(i = 0;i < se->nb_fields; i++) { >> + for (i = 0; i < se->nb_fields; i++) { >> + if (*field_types == TYPE_FLEXIBLE_ARRAY) { >> + field_types = thunk_convert_flexible_array( >> + d + dst_offsets[i], >> + s + src_offsets[i], >> + d, >> + s, >> + field_types, >> + se, >> + to_host); >> + continue; >> + } >> field_types = thunk_convert(d + dst_offsets[i], >> s + src_offsets[i], >> field_types, to_host); >> @@ -264,6 +305,11 @@ const argtype *thunk_convert(void *dst, const void >> *src, >> } >> } >> break; >> + case TYPE_FLEXIBLE_ARRAY: >> + fprintf(stderr, >> + "Invalid flexible array (type 0x%x) outside of a >> structure\n", >> + type); >> + break; >> default: >> fprintf(stderr, "Invalid type 0x%x\n", type); >> break; >> @@ -271,6 +317,45 @@ const argtype *thunk_convert(void *dst, const void >> *src, >> return type_ptr; >> } >> >> +static const argtype * >> +thunk_print_flexible_array(void *arg, const uint8_t *arg_struct, >> + const argtype *type_ptr, const StructEntry >> *se) { >> + int array_length, len_field_idx, arg_size, i; >> + uint8_t *a; >> + int is_string = 0; >> + >> + assert(*type_ptr == TYPE_FLEXIBLE_ARRAY); >> + type_ptr++; >> + len_field_idx = *type_ptr++; >> + >> + array_length = tswap32( >> + *(const uint32_t *)(arg_struct + >> se->field_offsets[0][len_field_idx])); >> + arg_size = thunk_type_size(type_ptr, 0); >> + a = arg; >> + >> + if (*type_ptr == TYPE_CHAR) { >> + qemu_log("\""); >> + is_string = 1; >> + } else { >> + qemu_log("["); >> + } >> + >> + for (i = 0; i < array_length; i++) { >> + if (i > 0 && !is_string) { >> + qemu_log(","); >> + } >> + thunk_print(a, type_ptr); >> + a += arg_size; >> + } >> + >> + if (is_string) { >> + qemu_log("\""); >> + } else { >> + qemu_log("]"); >> + } >> + return thunk_type_next(type_ptr); >> +} >> + >> const argtype *thunk_print(void *arg, const argtype *type_ptr) >> { >> int type; >> @@ -414,17 +499,80 @@ const argtype *thunk_print(void *arg, const argtype >> *type_ptr) >> if (i > 0) { >> qemu_log(","); >> } >> + if (*field_types == TYPE_FLEXIBLE_ARRAY) { >> + field_types = thunk_print_flexible_array( >> + a + arg_offsets[i], a, field_types, se); >> + continue; >> + } >> field_types = thunk_print(a + arg_offsets[i], >> field_types); >> } >> qemu_log("}"); >> } >> break; >> + case TYPE_FLEXIBLE_ARRAY: >> + fprintf(stderr, >> + "Invalid flexible array (type 0x%x) outside of a >> structure\n", >> + type); >> + break; >> default: >> g_assert_not_reached(); >> } >> return type_ptr; >> } >> >> +bool thunk_type_has_flexible_array(const argtype *type_ptr) >> +{ >> + int i; >> + const StructEntry *se; >> + const argtype *field_types; >> + if (*type_ptr != TYPE_STRUCT) { >> + return false; >> + } >> + se = struct_entries + type_ptr[1]; >> + field_types = se->field_types; >> + for (i = 0; i < se->nb_fields; i++) { >> + if (*field_types == TYPE_FLEXIBLE_ARRAY) { >> + return true; >> + } >> + field_types = thunk_type_next(type_ptr); >> + } >> + return false; >> +} >> + >> +int thunk_type_size_with_src(const void *src, const argtype *type_ptr, >> + int is_host) >> +{ >> + switch (*type_ptr) { >> + case TYPE_STRUCT: { >> + int i; >> + const StructEntry *se = struct_entries + type_ptr[1]; >> + const argtype *field_types; >> + if (se->thunk_size[is_host] != NULL) { >> + return (*se->thunk_size[is_host])(src); >> + } >> + >> + field_types = se->field_types; >> + for (i = 0; i < se->nb_fields; i++) { >> + if (*field_types == TYPE_FLEXIBLE_ARRAY) { >> + uint32_t array_length = *(const uint32_t *)( >> + (const uint8_t *)src + >> + se->field_offsets[is_host][field_types[1]]); >> + if (!is_host) { >> + array_length = tswap32(array_length); >> + } >> + return se->size[is_host] + >> + array_length * >> + thunk_type_size(field_types + 2, is_host); >> + } >> + field_types = thunk_type_next(type_ptr); >> + } >> + return se->size[is_host]; >> + } >> + default: >> + return thunk_type_size(type_ptr, is_host); >> + } >> +} >> + >> /* from em86 */ >> >> /* Utility function: Table-driven functions to translate bitmasks >> -- >> 2.28.0.220.ged08abb693-goog >> >> [-- Attachment #1.2: Type: text/html, Size: 14831 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3990 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 8/8] linux-user: Add support for SIOCETHTOOL ioctl 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng ` (6 preceding siblings ...) 2020-08-11 7:09 ` [PATCH v2 7/8] thunk: supports flexible arrays Shu-Chun Weng @ 2020-08-11 7:09 ` Shu-Chun Weng 2020-12-18 4:03 ` Shu-Chun Weng 2020-12-18 8:24 ` [PATCH v2 0/8] fcntl, sockopt, and ioctl options Laurent Vivier 8 siblings, 1 reply; 22+ messages in thread From: Shu-Chun Weng @ 2020-08-11 7:09 UTC (permalink / raw) To: qemu-devel; +Cc: Shu-Chun Weng, laurent The ioctl numeric values are platform-independent and determined by the file include/uapi/linux/sockios.h in Linux kernel source code: #define SIOCETHTOOL 0x8946 These ioctls get (or set) various structures pointed by the field ifr_data in the structure ifreq depending on the first 4 bytes of the memory region. This change clones the ioctl framework into ethtool-specific dispatch logic in its own file. A number of definitions previously only visible in syscall.c are thus exported to syscall_defs.h to be used in the new files. Signed-off-by: Shu-Chun Weng <scw@google.com> --- v1 -> v2: Fix style problems. linux-user/Makefile.objs | 3 +- linux-user/ethtool.c | 840 ++++++++++++++++++++++++++++++++++ linux-user/ethtool.h | 20 + linux-user/ethtool_entries.h | 107 +++++ linux-user/ioctls.h | 2 + linux-user/qemu.h | 1 + linux-user/syscall.c | 36 +- linux-user/syscall_defs.h | 12 + linux-user/syscall_types.h | 280 ++++++++++++ tests/tcg/multiarch/ethtool.c | 423 +++++++++++++++++ 10 files changed, 1712 insertions(+), 12 deletions(-) create mode 100644 linux-user/ethtool.c create mode 100644 linux-user/ethtool.h create mode 100644 linux-user/ethtool_entries.h create mode 100644 tests/tcg/multiarch/ethtool.c diff --git a/linux-user/Makefile.objs b/linux-user/Makefile.objs index 1940910a73..971d43173a 100644 --- a/linux-user/Makefile.objs +++ b/linux-user/Makefile.objs @@ -1,7 +1,8 @@ obj-y = main.o syscall.o strace.o mmap.o signal.o \ elfload.o linuxload.o uaccess.o uname.o \ safe-syscall.o $(TARGET_ABI_DIR)/signal.o \ - $(TARGET_ABI_DIR)/cpu_loop.o exit.o fd-trans.o + $(TARGET_ABI_DIR)/cpu_loop.o exit.o fd-trans.o \ + ethtool.o obj-$(TARGET_HAS_BFLT) += flatload.o obj-$(TARGET_I386) += vm86.o diff --git a/linux-user/ethtool.c b/linux-user/ethtool.c new file mode 100644 index 0000000000..fac97b9ba1 --- /dev/null +++ b/linux-user/ethtool.c @@ -0,0 +1,840 @@ +/* + * Linux ioctl system call SIOCETHTOOL requests + * + * Copyright (c) 2020 Shu-Chun Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "qemu/osdep.h" +#include <stdio.h> +#include <linux/ethtool.h> +#include <linux/if.h> +#include <linux/sockios.h> +#include <linux/unistd.h> +#include "ethtool.h" +#include "qemu.h" +#include "syscall_defs.h" + +/* Non-standard ethtool structure definitions. */ +/* + * struct ethtool_rxnfc { + * __u32 cmd; + * __u32 flow_type; + * __u64 data; + * struct ethtool_rx_flow_spec fs; + * union { + * __u32 rule_cnt; + * __u32 rss_context; + * }; + * __u32 rule_locs[0]; + * }; + * + * Originally defined for ETHTOOL_{G,S}RXFH with only the cmd, flow_type and + * data members. For other commands, dedicated standard structure definitions + * are listed in syscall_types.h. + */ +static void host_to_target_ethtool_rxnfc_get_set_rxfh(void *dst, + const void *src) +{ + static const argtype ethtool_rx_flow_spec_argtype[] = { + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), TYPE_NULL }; + struct ethtool_rxnfc *target = dst; + const struct ethtool_rxnfc *host = src; + + target->cmd = tswap32(host->cmd); + target->flow_type = tswap32(host->flow_type); + target->data = tswap64(host->data); + + if (host->cmd == ETHTOOL_SRXFH) { + /* + * struct ethtool_rxnfc was originally defined for ETHTOOL_{G,S}RXFH + * with only the cmd, flow_type and data members. Guest program might + * still be using that definition. + */ + return; + } + if (host->cmd != ETHTOOL_GRXFH) { + fprintf(stderr, "host_to_target_ethtool_rxnfc_get_set_rxfh called with " + "command 0x%x which is not ETHTOOL_SRXFH or ETHTOOL_GRXFH\n", + host->cmd); + } + if ((host->flow_type & FLOW_RSS) == 0) { + return; + } + /* + * If `FLOW_RSS` was requested then guest program must be using the new + * definition. + */ + thunk_convert(&target->fs, &host->fs, ethtool_rx_flow_spec_argtype, + THUNK_TARGET); + target->rule_cnt = tswap32(host->rule_cnt); +} + +static void target_to_host_ethtool_rxnfc_get_set_rxfh(void *dst, + const void *src) +{ + static const argtype ethtool_rx_flow_spec_argtype[] = { + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), TYPE_NULL }; + struct ethtool_rxnfc *host = dst; + const struct ethtool_rxnfc *target = src; + + host->cmd = tswap32(target->cmd); + host->flow_type = tswap32(target->flow_type); + host->data = tswap64(target->data); + + if (host->cmd == ETHTOOL_SRXFH) { + /* + * struct ethtool_rxnfc was originally defined for ETHTOOL_{G,S}RXFH + * with only the cmd, flow_type and data members. Guest program might + * still be using that definition. + */ + return; + } + if (host->cmd != ETHTOOL_GRXFH) { + fprintf(stderr, "target_to_host_ethtool_rxnfc_get_set_rxfh called with " + "command 0x%x which is not ETHTOOL_SRXFH or ETHTOOL_GRXFH\n", + host->cmd); + } + if ((host->flow_type & FLOW_RSS) == 0) { + return; + } + /* + * If `FLOW_RSS` was requested then guest program must be using the new + * definition. + */ + thunk_convert(&host->fs, &target->fs, ethtool_rx_flow_spec_argtype, + THUNK_HOST); + host->rule_cnt = tswap32(target->rule_cnt); +} + +static int target_ethtool_rxnfc_get_set_rxfh_size(const void *src) +{ + const struct ethtool_rxnfc *target = src; + int cmd = tswap32(target->cmd); + if (cmd == ETHTOOL_SRXFH || + (cmd == ETHTOOL_GRXFH && + (tswap32(target->flow_type) & FLOW_RSS) == 0)) { + return 16; + } + return sizeof(struct ethtool_rxnfc); +} + +static int host_ethtool_rxnfc_get_set_rxfh_size(const void *src) +{ + const struct ethtool_rxnfc *host = src; + if (host->cmd == ETHTOOL_SRXFH || + (host->cmd == ETHTOOL_GRXFH && (host->flow_type & FLOW_RSS) == 0)) { + return 16; + } + return sizeof(struct ethtool_rxnfc); +} + +const StructEntry struct_ethtool_rxnfc_get_set_rxfh_def = { + .convert = { + host_to_target_ethtool_rxnfc_get_set_rxfh, + target_to_host_ethtool_rxnfc_get_set_rxfh }, + .thunk_size = { + target_ethtool_rxnfc_get_set_rxfh_size, + host_ethtool_rxnfc_get_set_rxfh_size }, + .size = { 16, 16 }, + .align = { + __alignof__(struct ethtool_rxnfc), + __alignof__(struct ethtool_rxnfc) }, +}; + +/* + * struct ethtool_sset_info { + * __u32 cmd; + * __u32 reserved; + * __u64 sset_mask; + * __u32 data[0]; + * }; + * + * `sset_mask` is a bitmask of string sets. `data` is the buffer for string set + * sizes, containing number of 1s in `sset_mask`'s binary representation number + * of 4-byte entries. + * + * Since all fields are fixed-width and number of 1s in `sset_mask` does not + * change between architectures, host-to-target and target-to-host are + * identical. + */ +static void convert_ethtool_sset_info(void *dst, const void *src) +{ + int i, set_count; + struct ethtool_sset_info *dst_sset_info = dst; + const struct ethtool_sset_info *src_sset_info = src; + + dst_sset_info->cmd = tswap32(src_sset_info->cmd); + dst_sset_info->sset_mask = tswap64(src_sset_info->sset_mask); + + set_count = ctpop64(src_sset_info->sset_mask); + for (i = 0; i < set_count; ++i) { + dst_sset_info->data[i] = tswap32(src_sset_info->data[i]); + } +} + +static int ethtool_sset_info_size(const void *src) +{ + const struct ethtool_sset_info *src_sset_info = src; + return sizeof(struct ethtool_sset_info) + + ctpop64(src_sset_info->sset_mask) * sizeof(src_sset_info->data[0]); +} + +const StructEntry struct_ethtool_sset_info_def = { + .convert = { + convert_ethtool_sset_info, convert_ethtool_sset_info }, + .thunk_size = { + ethtool_sset_info_size, ethtool_sset_info_size }, + .size = { + sizeof(struct ethtool_sset_info), + sizeof(struct ethtool_sset_info) }, + .align = { + __alignof__(struct ethtool_sset_info), + __alignof__(struct ethtool_sset_info) }, +}; + +/* + * struct ethtool_rxfh { + * __u32 cmd; + * __u32 rss_context; + * __u32 indir_size; + * __u32 key_size; + * __u8 hfunc; + * __u8 rsvd8[3]; + * __u32 rsvd32; + * __u32 rss_config[0]; + * }; + * + * `rss_config`: indirection table of `indir_size` __u32 elements, followed by + * hash key of `key_size` bytes. + * + * `indir_size` could be ETH_RXFH_INDIR_NO_CHANGE when `cmd` is ETHTOOL_SRSSH + * and there would be no indircetion table in `rss_config`. + */ +static void convert_ethtool_rxfh_header(void *dst, const void *src) +{ + struct ethtool_rxfh *dst_rxfh = dst; + const struct ethtool_rxfh *src_rxfh = src; + + dst_rxfh->cmd = tswap32(src_rxfh->cmd); + dst_rxfh->rss_context = tswap32(src_rxfh->rss_context); + dst_rxfh->indir_size = tswap32(src_rxfh->indir_size); + dst_rxfh->key_size = tswap32(src_rxfh->key_size); + dst_rxfh->hfunc = src_rxfh->hfunc; + dst_rxfh->rsvd8[0] = src_rxfh->rsvd8[0]; + dst_rxfh->rsvd8[1] = src_rxfh->rsvd8[1]; + dst_rxfh->rsvd8[2] = src_rxfh->rsvd8[2]; + dst_rxfh->rsvd32 = tswap32(src_rxfh->rsvd32); +} + +static void convert_ethtool_rxfh_rss_config( + void *dst, const void *src, uint32_t indir_size, uint32_t key_size) { + uint32_t *dst_rss_config = (uint32_t *)dst; + const uint32_t *src_rss_config = (const uint32_t *)src; + int i; + for (i = 0; i < indir_size; ++i) { + dst_rss_config[i] = tswap32(src_rss_config[i]); + } + if (key_size > 0) { + memcpy(dst_rss_config + indir_size, + src_rss_config + indir_size, + key_size); + } +} + +static void host_to_target_ethtool_rxfh(void *dst, const void *src) +{ + struct ethtool_rxfh *target = dst; + const struct ethtool_rxfh *host = src; + + convert_ethtool_rxfh_header(dst, src); + + const uint32_t indir_size = + host->cmd == ETHTOOL_SRSSH && + host->indir_size == ETH_RXFH_INDIR_NO_CHANGE ? + 0 : + host->indir_size; + convert_ethtool_rxfh_rss_config(target->rss_config, host->rss_config, + indir_size, host->key_size); +} + +static void target_to_host_ethtool_rxfh(void *dst, const void *src) +{ + struct ethtool_rxfh *host = dst; + const struct ethtool_rxfh *target = src; + + convert_ethtool_rxfh_header(dst, src); + + const uint32_t indir_size = + host->cmd == ETHTOOL_SRSSH && + host->indir_size == ETH_RXFH_INDIR_NO_CHANGE ? + 0 : + host->indir_size; + convert_ethtool_rxfh_rss_config(host->rss_config, target->rss_config, + indir_size, host->key_size); +} + +static int target_ethtool_rxfh_size(const void *src) +{ + const struct ethtool_rxfh *target = src; + if (tswap32(target->cmd) == ETHTOOL_SRSSH && + tswap32(target->indir_size) == ETH_RXFH_INDIR_NO_CHANGE) { + return sizeof(struct ethtool_rxfh) + tswap32(target->key_size); + } + return sizeof(struct ethtool_rxfh) + + tswap32(target->indir_size) * sizeof(target->rss_config[0]) + + tswap32(target->key_size); +} + +static int host_ethtool_rxfh_size(const void *src) +{ + const struct ethtool_rxfh *host = src; + if (host->cmd == ETHTOOL_SRSSH && + host->indir_size == ETH_RXFH_INDIR_NO_CHANGE) { + return sizeof(struct ethtool_rxfh) + host->key_size; + } + return sizeof(struct ethtool_rxfh) + + host->indir_size * sizeof(host->rss_config[0]) + + host->key_size; +} + +const StructEntry struct_ethtool_rxfh_def = { + .convert = { + host_to_target_ethtool_rxfh, target_to_host_ethtool_rxfh }, + .thunk_size = { + target_ethtool_rxfh_size, host_ethtool_rxfh_size }, + .size = { + sizeof(struct ethtool_rxfh), sizeof(struct ethtool_rxfh) }, + .align = { + __alignof__(struct ethtool_rxfh), __alignof__(struct ethtool_rxfh) }, +}; + +/* + * struct ethtool_link_settings { + * __u32 cmd; + * __u32 speed; + * __u8 duplex; + * __u8 port; + * __u8 phy_address; + * __u8 autoneg; + * __u8 mdio_support; + * __u8 eth_tp_mdix; + * __u8 eth_tp_mdix_ctrl; + * __s8 link_mode_masks_nwords; + * __u8 transceiver; + * __u8 reserved1[3]; + * __u32 reserved[7]; + * __u32 link_mode_masks[0]; + * }; + * + * layout of link_mode_masks fields: + * __u32 map_supported[link_mode_masks_nwords]; + * __u32 map_advertising[link_mode_masks_nwords]; + * __u32 map_lp_advertising[link_mode_masks_nwords]; + * + * `link_mode_masks_nwords` can be negative when returning from kernel if the + * provided request size is not supported. + */ + +static void host_to_target_ethtool_link_settings(void *dst, const void *src) +{ + int i; + struct ethtool_link_settings *target = dst; + const struct ethtool_link_settings *host = src; + + target->cmd = tswap32(host->cmd); + target->speed = tswap32(host->speed); + target->duplex = host->duplex; + target->port = host->port; + target->phy_address = host->phy_address; + target->autoneg = host->autoneg; + target->mdio_support = host->mdio_support; + target->eth_tp_mdix = host->eth_tp_mdix; + target->eth_tp_mdix_ctrl = host->eth_tp_mdix_ctrl; + target->link_mode_masks_nwords = host->link_mode_masks_nwords; + target->transceiver = host->transceiver; + for (i = 0; i < 3; ++i) { + target->reserved1[i] = host->reserved1[i]; + } + for (i = 0; i < 7; ++i) { + target->reserved[i] = tswap32(host->reserved[i]); + } + + if (host->link_mode_masks_nwords > 0) { + for (i = 0; i < host->link_mode_masks_nwords * 3; ++i) { + target->link_mode_masks[i] = tswap32(host->link_mode_masks[i]); + } + } +} + +static void target_to_host_ethtool_link_settings(void *dst, const void *src) +{ + int i; + struct ethtool_link_settings *host = dst; + const struct ethtool_link_settings *target = src; + + host->cmd = tswap32(target->cmd); + host->speed = tswap32(target->speed); + host->duplex = target->duplex; + host->port = target->port; + host->phy_address = target->phy_address; + host->autoneg = target->autoneg; + host->mdio_support = target->mdio_support; + host->eth_tp_mdix = target->eth_tp_mdix; + host->eth_tp_mdix_ctrl = target->eth_tp_mdix_ctrl; + host->link_mode_masks_nwords = target->link_mode_masks_nwords; + host->transceiver = target->transceiver; + for (i = 0; i < 3; ++i) { + host->reserved1[i] = target->reserved1[i]; + } + for (i = 0; i < 7; ++i) { + host->reserved[i] = tswap32(target->reserved[i]); + } + + if (host->link_mode_masks_nwords > 0) { + for (i = 0; i < host->link_mode_masks_nwords * 3; ++i) { + host->link_mode_masks[i] = tswap32(target->link_mode_masks[i]); + } + } +} + +static int target_ethtool_link_settings_size(const void *src) +{ + const struct ethtool_link_settings *target = src; + if (target->link_mode_masks_nwords > 0) { + return sizeof(struct ethtool_link_settings) + + 3 * target->link_mode_masks_nwords * + sizeof(target->link_mode_masks[0]); + } else { + return sizeof(struct ethtool_link_settings); + } +} + +static int host_ethtool_link_settings_size(const void *src) +{ + const struct ethtool_link_settings *host = src; + if (host->link_mode_masks_nwords > 0) { + return sizeof(struct ethtool_link_settings) + + 3 * host->link_mode_masks_nwords * + sizeof(host->link_mode_masks[0]); + } else { + return sizeof(struct ethtool_link_settings); + } +} + +const StructEntry struct_ethtool_link_settings_def = { + .convert = { + host_to_target_ethtool_link_settings, + target_to_host_ethtool_link_settings + }, + .thunk_size = { + target_ethtool_link_settings_size, host_ethtool_link_settings_size }, + .size = { + sizeof(struct ethtool_link_settings), + sizeof(struct ethtool_link_settings) }, + .align = { + __alignof__(struct ethtool_link_settings), + __alignof__(struct ethtool_link_settings) }, +}; + +/* + * struct ethtool_per_queue_op { + * __u32 cmd; + * __u32 sub_command; + * __u32 queue_mask[__KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32)]; + * char data[]; + * }; + * + * `queue_mask` are a series of bitmasks of the queues. `data` is a complete + * command structure for each of the queues addressed. + * + * When `cmd` is `ETHTOOL_PERQUEUE` and `sub_command` is `ETHTOOL_GCOALESCE` or + * `ETHTOOL_SCOALESCE`, the command structure is `struct ethtool_coalesce`. + */ +static void host_to_target_ethtool_per_queue_op(void *dst, const void *src) +{ + static const argtype ethtool_coalesce_argtype[] = { + MK_STRUCT(STRUCT_ethtool_coalesce), TYPE_NULL }; + int i, queue_count; + struct ethtool_per_queue_op *target = dst; + const struct ethtool_per_queue_op *host = src; + + target->cmd = tswap32(host->cmd); + target->sub_command = tswap32(host->sub_command); + + queue_count = 0; + for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { + target->queue_mask[i] = tswap32(host->queue_mask[i]); + queue_count += ctpop32(host->queue_mask[i]); + } + + if (host->cmd != ETHTOOL_PERQUEUE || + (host->sub_command != ETHTOOL_GCOALESCE && + host->sub_command != ETHTOOL_SCOALESCE)) { + fprintf(stderr, + "Unknown command 0x%x sub_command 0x%x for " + "ethtool_per_queue_op, unable to convert the `data` field " + "(host-to-target)\n", + host->cmd, host->sub_command); + return; + } + + for (i = 0; i < queue_count; ++i) { + thunk_convert(target->data + i * sizeof(struct ethtool_coalesce), + host->data + i * sizeof(struct ethtool_coalesce), + ethtool_coalesce_argtype, THUNK_TARGET); + } +} + +static void target_to_host_ethtool_per_queue_op(void *dst, const void *src) +{ + static const argtype ethtool_coalesce_argtype[] = { + MK_STRUCT(STRUCT_ethtool_coalesce), TYPE_NULL }; + int i, queue_count; + struct ethtool_per_queue_op *host = dst; + const struct ethtool_per_queue_op *target = src; + + host->cmd = tswap32(target->cmd); + host->sub_command = tswap32(target->sub_command); + + queue_count = 0; + for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { + host->queue_mask[i] = tswap32(target->queue_mask[i]); + queue_count += ctpop32(host->queue_mask[i]); + } + + if (host->cmd != ETHTOOL_PERQUEUE || + (host->sub_command != ETHTOOL_GCOALESCE && + host->sub_command != ETHTOOL_SCOALESCE)) { + fprintf(stderr, + "Unknown command 0x%x sub_command 0x%x for " + "ethtool_per_queue_op, unable to convert the `data` field " + "(target-to-host)\n", + host->cmd, host->sub_command); + return; + } + + for (i = 0; i < queue_count; ++i) { + thunk_convert(host->data + i * sizeof(struct ethtool_coalesce), + target->data + i * sizeof(struct ethtool_coalesce), + ethtool_coalesce_argtype, THUNK_HOST); + } +} + +static int target_ethtool_per_queue_op_size(const void *src) +{ + int i, queue_count; + const struct ethtool_per_queue_op *target = src; + + if (tswap32(target->cmd) != ETHTOOL_PERQUEUE || + (tswap32(target->sub_command) != ETHTOOL_GCOALESCE && + tswap32(target->sub_command) != ETHTOOL_SCOALESCE)) { + fprintf(stderr, + "Unknown command 0x%x sub_command 0x%x for " + "ethtool_per_queue_op, unable to compute the size of the " + "`data` field (target)\n", + tswap32(target->cmd), tswap32(target->sub_command)); + return sizeof(struct ethtool_per_queue_op); + } + + queue_count = 0; + for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { + queue_count += ctpop32(target->queue_mask[i]); + } + return sizeof(struct ethtool_per_queue_op) + + queue_count * sizeof(struct ethtool_coalesce); +} + +static int host_ethtool_per_queue_op_size(const void *src) +{ + int i, queue_count; + const struct ethtool_per_queue_op *host = src; + + if (host->cmd != ETHTOOL_PERQUEUE || + (host->sub_command != ETHTOOL_GCOALESCE && + host->sub_command != ETHTOOL_SCOALESCE)) { + fprintf(stderr, + "Unknown command 0x%x sub_command 0x%x for " + "ethtool_per_queue_op, unable to compute the size of the " + "`data` field (host)\n", + host->cmd, host->sub_command); + return sizeof(struct ethtool_per_queue_op); + } + + queue_count = 0; + for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { + queue_count += ctpop32(host->queue_mask[i]); + } + return sizeof(struct ethtool_per_queue_op) + + queue_count * sizeof(struct ethtool_coalesce); +} + +const StructEntry struct_ethtool_per_queue_op_def = { + .convert = { + host_to_target_ethtool_per_queue_op, + target_to_host_ethtool_per_queue_op + }, + .thunk_size = { + target_ethtool_per_queue_op_size, host_ethtool_per_queue_op_size }, + .size = { + sizeof(struct ethtool_per_queue_op), + sizeof(struct ethtool_per_queue_op) }, + .align = { + __alignof__(struct ethtool_per_queue_op), + __alignof__(struct ethtool_per_queue_op) }, +}; + +#define safe_dev_ethtool(fd, ...) \ + safe_syscall(__NR_ioctl, (fd), SIOCETHTOOL, __VA_ARGS__) + +typedef struct EthtoolEntry EthtoolEntry; + +typedef abi_long do_ethtool_fn(const EthtoolEntry *ee, uint8_t *buf_temp, + int fd, struct ifreq *host_ifreq); + +struct EthtoolEntry { + uint32_t cmd; + int access; + do_ethtool_fn *do_ethtool; + const argtype arg_type[3]; +}; + +#define ETHT_R 0x0001 +#define ETHT_W 0x0002 +#define ETHT_RW (ETHT_R | ETHT_W) + +static do_ethtool_fn do_ethtool_get_rxfh; + +static EthtoolEntry ethtool_entries[] = { +#define ETHTOOL(cmd, access, ...) \ + { cmd, access, 0, { __VA_ARGS__ } }, +#define ETHTOOL_SPECIAL(cmd, access, dofn, ...) \ + { cmd, access, dofn, { __VA_ARGS__ } }, +#include "ethtool_entries.h" +#undef ETHTOOL +#undef ETHTOOL_SPECIAL + { 0, 0 }, +}; + +/* + * ETHTOOL_GRSSH has two modes of operations: querying the sizes of the indir + * and key as well as actually querying the indir and key. When either + * `indir_size` or `key_size` is zero, the size of the corresponding entry is + * retrieved and updated into the `ethtool_rxfh` struct. When either of them is + * non-zero, the actually indir or key is written to `rss_config`. + * + * This causes a problem for the generic framework which converts between host + * and target structures without the context. When the convertion function sees + * an `ethtool_rxfh` struct with non-zero `indir_size` or `key_size`, it has to + * assume that there are entries in `rss_config` and needs to convert them. + * Unfortunately, when converting the returned `ethtool_rxfh` struct from host + * to target after an ETHTOOL_GRSSH call with the first mode, the `indir_size` + * and `key_size` fields are populated but there is no actual data to be + * converted. More importantly, user programs would not have prepared enough + * memory for the convertion to take place safely. + * + * ETHTOOL_GRSSH thus needs a special implementation which is aware of the two + * modes of operations and converts the structure accordingly. + */ +abi_long do_ethtool_get_rxfh(const EthtoolEntry *ee, uint8_t *buf_temp, + int fd, struct ifreq *host_ifreq) +{ + const argtype *arg_type = ee->arg_type; + const abi_long ifreq_data = (abi_long)(unsigned long)host_ifreq->ifr_data; + struct ethtool_rxfh *rxfh = (struct ethtool_rxfh *)buf_temp; + uint32_t user_indir_size, user_key_size; + abi_long ret; + void *argptr; + + assert(arg_type[0] == TYPE_PTR); + assert(ee->access == IOC_RW); + arg_type++; + + /* + * As of Linux kernel v5.8-rc4, ETHTOOL_GRSSH calls never read the + * `rss_config` part. Converting only the "header" part suffices. + */ + argptr = lock_user(VERIFY_READ, ifreq_data, sizeof(*rxfh), 1); + if (!argptr) { + return -TARGET_EFAULT; + } + convert_ethtool_rxfh_header(rxfh, argptr); + unlock_user(argptr, ifreq_data, sizeof(*rxfh)); + + if (rxfh->cmd != ETHTOOL_GRSSH) { + return -TARGET_EINVAL; + } + user_indir_size = rxfh->indir_size; + user_key_size = rxfh->key_size; + + host_ifreq->ifr_data = (void *)rxfh; + ret = get_errno(safe_dev_ethtool(fd, host_ifreq)); + + /* + * When a user program supplies `indir_size` or `key_size` but does not + * match what the kernel has, the syscall returns EINVAL but the structure + * is already updated. Mimicking it here. + */ + argptr = lock_user(VERIFY_WRITE, ifreq_data, sizeof(*rxfh), 0); + if (!argptr) { + return -TARGET_EFAULT; + } + convert_ethtool_rxfh_header(argptr, rxfh); + unlock_user(argptr, ifreq_data, 0); + + if (is_error(ret)) { + return ret; + } + + if (user_indir_size > 0 || user_key_size > 0) { + const int rss_config_size = + user_indir_size * sizeof(rxfh->rss_config[0]) + user_key_size; + argptr = lock_user(VERIFY_WRITE, ifreq_data + sizeof(*rxfh), + rss_config_size, 0); + if (!argptr) { + return -TARGET_EFAULT; + } + convert_ethtool_rxfh_rss_config(argptr, rxfh->rss_config, + user_indir_size, user_key_size); + unlock_user(argptr, ifreq_data + sizeof(*rxfh), rss_config_size); + } + return ret; +} + +/* + * Calculates the size of the data type represented by `type_ptr` with + * `guest_addr` being the underlying memory. Since `type_ptr` may contain + * flexible arrays, we need access to the underlying memory to determine their + * sizes. + */ +static int thunk_size(abi_long guest_addr, const argtype *type_ptr) +{ + /* + * lock_user based on `thunk_type_size` then call `thunk_type_size_with_src` + * on it. + */ + void *src; + int type_size = thunk_type_size(type_ptr, /*is_host=*/ 0); + if (!thunk_type_has_flexible_array(type_ptr)) { + return type_size; + } + + src = lock_user(VERIFY_READ, guest_addr, type_size, 0); + type_size = thunk_type_size_with_src(src, type_ptr, /*is_host=*/ 0); + unlock_user(src, guest_addr, 0); + + return type_size; +} + +abi_long dev_ethtool(int fd, uint8_t *buf_temp) +{ + uint32_t *cmd; + uint32_t host_cmd; + const EthtoolEntry *ee; + const argtype *arg_type; + abi_long ret; + int target_size; + void *argptr; + + /* + * Make a copy of `host_ifreq` because we are going to reuse `buf_temp` and + * overwrite it. Further, we will overwrite `host_ifreq.ifreq_data`, so + * keep a copy in `ifreq_data`. + */ + struct ifreq host_ifreq = *(struct ifreq *)(unsigned long)buf_temp; + const abi_long ifreq_data = (abi_long)(unsigned long)host_ifreq.ifr_data; + + cmd = (uint32_t *)lock_user(VERIFY_READ, ifreq_data, sizeof(uint32_t), 0); + host_cmd = tswap32(*cmd); + unlock_user(cmd, ifreq_data, 0); + + ee = ethtool_entries; + for (;;) { + if (ee->cmd == 0) { + qemu_log_mask(LOG_UNIMP, "Unsupported ethtool cmd=0x%04lx\n", + (long)host_cmd); + return -TARGET_ENOSYS; + } + if (ee->cmd == host_cmd) { + break; + } + ee++; + } + if (ee->do_ethtool) { + return ee->do_ethtool(ee, buf_temp, fd, &host_ifreq); + } + + host_ifreq.ifr_data = buf_temp; + /* Even for ETHT_R, cmd still needs to be copied. */ + *(uint32_t *)buf_temp = host_cmd; + + arg_type = ee->arg_type; + switch (arg_type[0]) { + case TYPE_NULL: + /* no argument other than cmd */ + ret = get_errno(safe_dev_ethtool(fd, &host_ifreq)); + break; + case TYPE_PTR: + arg_type++; + target_size = thunk_size(ifreq_data, arg_type); + switch (ee->access) { + case ETHT_R: + ret = get_errno(safe_dev_ethtool(fd, &host_ifreq)); + if (!is_error(ret)) { + argptr = lock_user(VERIFY_WRITE, ifreq_data, target_size, 0); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET); + unlock_user(argptr, ifreq_data, target_size); + } + break; + case ETHT_W: + argptr = lock_user(VERIFY_READ, ifreq_data, target_size, 1); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); + unlock_user(argptr, ifreq_data, 0); + ret = get_errno(safe_dev_ethtool(fd, &host_ifreq)); + break; + default: + case ETHT_RW: + argptr = lock_user(VERIFY_READ, ifreq_data, target_size, 1); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); + unlock_user(argptr, ifreq_data, 0); + ret = get_errno(safe_dev_ethtool(fd, &host_ifreq)); + if (!is_error(ret)) { + argptr = lock_user(VERIFY_WRITE, ifreq_data, target_size, 0); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET); + unlock_user(argptr, ifreq_data, target_size); + } + break; + } + break; + default: + qemu_log_mask(LOG_UNIMP, + "Unsupported ethtool type: cmd=0x%04lx type=%d\n", + (long)host_cmd, arg_type[0]); + ret = -TARGET_ENOSYS; + break; + } + return ret; +} diff --git a/linux-user/ethtool.h b/linux-user/ethtool.h new file mode 100644 index 0000000000..6942aef095 --- /dev/null +++ b/linux-user/ethtool.h @@ -0,0 +1,20 @@ +#ifndef ETHTOOL_H +#define ETHTOOL_H + +#include <linux/if.h> +#include "qemu.h" + +extern const StructEntry struct_ethtool_rxnfc_get_set_rxfh_def; +extern const StructEntry struct_ethtool_sset_info_def; +extern const StructEntry struct_ethtool_rxfh_def; +extern const StructEntry struct_ethtool_link_settings_def; +extern const StructEntry struct_ethtool_per_queue_op_def; + +/* + * Takes the file descriptor and the buffer for temporarily storing data read + * from / to be written to guest memory. `buf_temp` must now contain the host + * representation of `struct ifreq`. + */ +abi_long dev_ethtool(int fd, uint8_t *buf_temp); + +#endif /* ETHTOOL_H */ diff --git a/linux-user/ethtool_entries.h b/linux-user/ethtool_entries.h new file mode 100644 index 0000000000..14f4e80a21 --- /dev/null +++ b/linux-user/ethtool_entries.h @@ -0,0 +1,107 @@ + ETHTOOL(ETHTOOL_GSET, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_cmd))) + ETHTOOL(ETHTOOL_SSET, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_cmd))) + ETHTOOL(ETHTOOL_GDRVINFO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_drvinfo))) + ETHTOOL(ETHTOOL_GREGS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_regs))) + ETHTOOL(ETHTOOL_GWOL, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_wolinfo))) + ETHTOOL(ETHTOOL_SWOL, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_wolinfo))) + ETHTOOL(ETHTOOL_GMSGLVL, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SMSGLVL, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GEEE, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_eee))) + ETHTOOL(ETHTOOL_SEEE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_eee))) + ETHTOOL(ETHTOOL_NWAY_RST, 0, TYPE_NULL) + ETHTOOL(ETHTOOL_GLINK, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GEEPROM, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom))) + ETHTOOL(ETHTOOL_SEEPROM, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom))) + ETHTOOL(ETHTOOL_GCOALESCE, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_coalesce))) + ETHTOOL(ETHTOOL_SCOALESCE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_coalesce))) + ETHTOOL(ETHTOOL_GRINGPARAM, ETHT_R, + MK_PTR(MK_STRUCT(STRUCT_ethtool_ringparam))) + ETHTOOL(ETHTOOL_SRINGPARAM, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_ringparam))) + ETHTOOL(ETHTOOL_GPAUSEPARAM, ETHT_R, + MK_PTR(MK_STRUCT(STRUCT_ethtool_pauseparam))) + ETHTOOL(ETHTOOL_SPAUSEPARAM, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_pauseparam))) + ETHTOOL(ETHTOOL_TEST, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_test))) + ETHTOOL(ETHTOOL_GSTRINGS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_gstrings))) + ETHTOOL(ETHTOOL_PHYS_ID, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GSTATS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_stats))) + ETHTOOL(ETHTOOL_GPERMADDR, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_perm_addr))) + ETHTOOL(ETHTOOL_GFLAGS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SFLAGS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GPFLAGS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SPFLAGS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GRXFH, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_get_set_rxfh))) + ETHTOOL(ETHTOOL_GRXRINGS, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) + ETHTOOL(ETHTOOL_GRXCLSRLCNT, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rule_cnt))) + ETHTOOL(ETHTOOL_GRXCLSRULE, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) + ETHTOOL(ETHTOOL_GRXCLSRLALL, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rule_locs))) + ETHTOOL(ETHTOOL_SRXFH, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_get_set_rxfh))) + ETHTOOL(ETHTOOL_SRXCLSRLDEL, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) + ETHTOOL(ETHTOOL_SRXCLSRLINS, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) + ETHTOOL(ETHTOOL_FLASHDEV, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_flash))) + ETHTOOL(ETHTOOL_RESET, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GSSET_INFO, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_sset_info))) + ETHTOOL(ETHTOOL_GRXFHINDIR, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh_indir))) + ETHTOOL(ETHTOOL_SRXFHINDIR, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh_indir))) + ETHTOOL_SPECIAL(ETHTOOL_GRSSH, ETHT_RW, do_ethtool_get_rxfh, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh))) + ETHTOOL(ETHTOOL_SRSSH, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh))) + ETHTOOL(ETHTOOL_GFEATURES, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_gfeatures))) + ETHTOOL(ETHTOOL_SFEATURES, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_sfeatures))) + ETHTOOL(ETHTOOL_GTXCSUM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GRXCSUM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GSG, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GTSO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GGSO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GGRO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_STXCSUM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SRXCSUM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SSG, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_STSO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SGSO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SGRO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GCHANNELS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_channels))) + ETHTOOL(ETHTOOL_SCHANNELS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_channels))) + ETHTOOL(ETHTOOL_SET_DUMP, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump_no_data))) + ETHTOOL(ETHTOOL_GET_DUMP_FLAG, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump_no_data))) + ETHTOOL(ETHTOOL_GET_DUMP_DATA, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump))) + ETHTOOL(ETHTOOL_GET_TS_INFO, ETHT_R, + MK_PTR(MK_STRUCT(STRUCT_ethtool_ts_info))) + ETHTOOL(ETHTOOL_GMODULEINFO, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_modinfo))) + ETHTOOL(ETHTOOL_GMODULEEEPROM, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom))) + ETHTOOL(ETHTOOL_GTUNABLE, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) + ETHTOOL(ETHTOOL_STUNABLE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) + ETHTOOL(ETHTOOL_GPHYSTATS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_stats))) + ETHTOOL(ETHTOOL_PERQUEUE, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_per_queue_op))) + ETHTOOL(ETHTOOL_GLINKSETTINGS, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_link_settings))) + ETHTOOL(ETHTOOL_SLINKSETTINGS, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_link_settings))) + ETHTOOL(ETHTOOL_PHY_GTUNABLE, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) + ETHTOOL(ETHTOOL_PHY_STUNABLE, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) + ETHTOOL(ETHTOOL_GFECPARAM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_fecparam))) + ETHTOOL(ETHTOOL_GFECPARAM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_fecparam))) diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h index 0713ae1311..fd6ac963ec 100644 --- a/linux-user/ioctls.h +++ b/linux-user/ioctls.h @@ -238,6 +238,8 @@ IOCTL(SIOCSIFHWADDR, IOC_W, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq))) IOCTL(SIOCGIFTXQLEN, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq))) IOCTL(SIOCSIFTXQLEN, IOC_W, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq))) + IOCTL_SPECIAL(SIOCETHTOOL, IOC_W | IOC_R, do_ioctl_ethtool, + MK_PTR(MK_STRUCT(STRUCT_ptr_ifreq))) IOCTL(SIOCGIFMETRIC, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) IOCTL(SIOCSIFMETRIC, IOC_W, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) IOCTL(SIOCGIFMTU, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 5c964389c1..43f00681f8 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -231,6 +231,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1, abi_long arg2, abi_long arg3, abi_long arg4, abi_long arg5, abi_long arg6, abi_long arg7, abi_long arg8); +abi_long get_errno(abi_long ret); extern __thread CPUState *thread_cpu; void cpu_loop(CPUArchState *env); const char *target_strerror(int err); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index bfc4219104..41fea53716 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -127,6 +127,7 @@ #include "qapi/error.h" #include "fd-trans.h" #include "tcg/tcg.h" +#include "ethtool.h" #ifndef CLONE_IO #define CLONE_IO 0x80000000 /* Clone io context */ @@ -676,7 +677,7 @@ static inline int target_to_host_errno(int err) return err; } -static inline abi_long get_errno(abi_long ret) +abi_long get_errno(abi_long ret) { if (ret == -1) return -host_to_target_errno(errno); @@ -4732,16 +4733,6 @@ static abi_long do_ipc(CPUArchState *cpu_env, #endif /* kernel structure types definitions */ - -#define STRUCT(name, ...) STRUCT_ ## name, -#define STRUCT_SPECIAL(name) STRUCT_ ## name, -enum { -#include "syscall_types.h" -STRUCT_MAX -}; -#undef STRUCT -#undef STRUCT_SPECIAL - #define STRUCT(name, ...) static const argtype struct_ ## name ## _def[] = { __VA_ARGS__, TYPE_NULL }; #define STRUCT_SPECIAL(name) #include "syscall_types.h" @@ -4839,6 +4830,29 @@ static abi_long do_ioctl_fs_ioc_fiemap(const IOCTLEntry *ie, uint8_t *buf_temp, } #endif +static abi_long do_ioctl_ethtool(const IOCTLEntry *ie, uint8_t *buf_temp, + int fd, int cmd, abi_long arg) +{ + const argtype *arg_type = ie->arg_type; + int target_size; + void *argptr; + + assert(arg_type[0] == TYPE_PTR); + assert(ie->access == IOC_RW); + + arg_type++; + target_size = thunk_type_size(arg_type, 0); + + argptr = lock_user(VERIFY_READ, arg, target_size, 1); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); + unlock_user(argptr, arg, target_size); + + return dev_ethtool(fd, buf_temp); +} + static abi_long do_ioctl_ifconf(const IOCTLEntry *ie, uint8_t *buf_temp, int fd, int cmd, abi_long arg) { diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index 70df1a94fb..e25a8cbcc8 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -866,6 +866,8 @@ struct target_rtc_pll_info { #define TARGET_SIOCGIFTXQLEN 0x8942 /* Get the tx queue length */ #define TARGET_SIOCSIFTXQLEN 0x8943 /* Set the tx queue length */ +#define TARGET_SIOCETHTOOL 0x8946 /* Ethtool interface */ + /* ARP cache control calls. */ #define TARGET_OLD_SIOCDARP 0x8950 /* old delete ARP table entry */ #define TARGET_OLD_SIOCGARP 0x8951 /* old get ARP table entry */ @@ -2776,4 +2778,14 @@ struct target_statx { /* 0x100 */ }; +/* kernel structure types definitions */ +#define STRUCT(name, ...) STRUCT_ ## name, +#define STRUCT_SPECIAL(name) STRUCT_ ## name, +enum { +#include "syscall_types.h" +STRUCT_MAX +}; +#undef STRUCT +#undef STRUCT_SPECIAL + #endif diff --git a/linux-user/syscall_types.h b/linux-user/syscall_types.h index 3f1f033464..559924c752 100644 --- a/linux-user/syscall_types.h +++ b/linux-user/syscall_types.h @@ -1,3 +1,4 @@ + STRUCT_SPECIAL(termios) STRUCT(winsize, @@ -464,3 +465,282 @@ STRUCT(usbdevfs_disconnect_claim, TYPE_INT, /* flags */ MK_ARRAY(TYPE_CHAR, USBDEVFS_MAXDRIVERNAME + 1)) /* driver */ #endif /* CONFIG_USBFS */ + +/* ethtool ioctls */ +STRUCT(ethtool_cmd, + TYPE_INT, /* cmd */ + TYPE_INT, /* supported */ + TYPE_INT, /* advertising */ + TYPE_SHORT, /* speed */ + TYPE_CHAR, /* duplex */ + TYPE_CHAR, /* port */ + TYPE_CHAR, /* phy_address */ + TYPE_CHAR, /* transceiver */ + TYPE_CHAR, /* autoneg */ + TYPE_CHAR, /* mdio_support */ + TYPE_INT, /* maxtxpkt */ + TYPE_INT, /* maxrxpkt */ + TYPE_SHORT, /* speed_hi */ + TYPE_CHAR, /* eth_tp_mdix */ + TYPE_CHAR, /* eth_tp_mdix_ctrl */ + TYPE_INT, /* lp_advertising */ + MK_ARRAY(TYPE_INT, 2)) /* reserved */ + +STRUCT(ethtool_drvinfo, + TYPE_INT, /* cmd */ + MK_ARRAY(TYPE_CHAR, 32), /* driver */ + MK_ARRAY(TYPE_CHAR, 32), /* version */ + MK_ARRAY(TYPE_CHAR, 32), /* fw_version[ETHTOOL_FWVERS_LEN] */ + MK_ARRAY(TYPE_CHAR, 32), /* bus_info[ETHTOOL_BUSINFO_LEN] */ + MK_ARRAY(TYPE_CHAR, 32), /* erom_version[ETHTOOL_EROMVERS_LEN] */ + MK_ARRAY(TYPE_CHAR, 12), /* reserved2 */ + TYPE_INT, /* n_priv_flags */ + TYPE_INT, /* n_stats */ + TYPE_INT, /* testinfo_len */ + TYPE_INT, /* eedump_len */ + TYPE_INT) /* regdump_len */ + +STRUCT(ethtool_regs, + TYPE_INT, /* cmd */ + TYPE_INT, /* version */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */ + +STRUCT(ethtool_wolinfo, + TYPE_INT, /* cmd */ + TYPE_INT, /* supported */ + TYPE_INT, /* wolopts */ + MK_ARRAY(TYPE_CHAR, 6)) /* sopass[SOPASS_MAX] */ + +STRUCT(ethtool_value, + TYPE_INT, /* cmd */ + TYPE_INT) /* data */ + +STRUCT(ethtool_eee, + TYPE_INT, /* cmd */ + TYPE_INT, /* supported */ + TYPE_INT, /* advertised */ + TYPE_INT, /* lp_advertised */ + TYPE_INT, /* eee_active */ + TYPE_INT, /* eee_enabled */ + TYPE_INT, /* tx_lpi_enabled */ + TYPE_INT, /* tx_lpi_timer */ + MK_ARRAY(TYPE_INT, 2)) /* reserved */ + +STRUCT(ethtool_eeprom, + TYPE_INT, /* cmd */ + TYPE_INT, /* magic */ + TYPE_INT, /* offset */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 3)) /* data[0]: len */ + +STRUCT(ethtool_coalesce, + TYPE_INT, /* cmd */ + TYPE_INT, /* rx_coalesce_usecs */ + TYPE_INT, /* rx_max_coalesced_frames */ + TYPE_INT, /* rx_coalesce_usecs_irq */ + TYPE_INT, /* rx_max_coalesced_frames_irq */ + TYPE_INT, /* tx_coalesce_usecs */ + TYPE_INT, /* tx_max_coalesced_frames */ + TYPE_INT, /* tx_coalesce_usecs_irq */ + TYPE_INT, /* tx_max_coalesced_frames_irq */ + TYPE_INT, /* stats_block_coalesce_usecs */ + TYPE_INT, /* use_adaptive_rx_coalesce */ + TYPE_INT, /* use_adaptive_tx_coalesce */ + TYPE_INT, /* pkt_rate_low */ + TYPE_INT, /* rx_coalesce_usecs_low */ + TYPE_INT, /* rx_max_coalesced_frames_low */ + TYPE_INT, /* tx_coalesce_usecs_low */ + TYPE_INT, /* tx_max_coalesced_frames_low */ + TYPE_INT, /* pkt_rate_high */ + TYPE_INT, /* rx_coalesce_usecs_high */ + TYPE_INT, /* rx_max_coalesced_frames_high */ + TYPE_INT, /* tx_coalesce_usecs_high */ + TYPE_INT, /* tx_max_coalesced_frames_high */ + TYPE_INT) /* rate_sample_interval */ + +STRUCT(ethtool_ringparam, + TYPE_INT, /* cmd */ + TYPE_INT, /* rx_max_pending */ + TYPE_INT, /* rx_mini_max_pending */ + TYPE_INT, /* rx_jumbo_max_pending */ + TYPE_INT, /* tx_max_pending */ + TYPE_INT, /* rx_pending */ + TYPE_INT, /* rx_mini_pending */ + TYPE_INT, /* rx_jumbo_pending */ + TYPE_INT) /* tx_pending */ + +STRUCT(ethtool_pauseparam, + TYPE_INT, /* cmd */ + TYPE_INT, /* autoneg */ + TYPE_INT, /* rx_pause */ + TYPE_INT) /* tx_pause */ + +STRUCT(ethtool_test, + TYPE_INT, /* cmd */ + TYPE_INT, /* flags */ + TYPE_INT, /* reserved */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_LONGLONG, 3)) /* data[0]: len */ + +STRUCT(ethtool_gstrings, + TYPE_INT, /* cmd */ + TYPE_INT, /* string_set */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(MK_ARRAY(TYPE_CHAR, 32), 2)) + /* data[0]: len * ETH_GSTRING_LEN */ + +STRUCT(ethtool_stats, + TYPE_INT, /* cmd */ + TYPE_INT, /* n_stats */ + MK_FLEXIBLE_ARRAY(TYPE_LONGLONG, 1)) /* data[0]: n_stats */ + +STRUCT(ethtool_perm_addr, + TYPE_INT, /* cmd */ + TYPE_INT, /* size */ + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 1)) /* data[0]: size */ + +STRUCT(ethtool_flow_ext, + MK_ARRAY(TYPE_CHAR, 2), /* padding */ + MK_ARRAY(TYPE_CHAR, 6), /* h_dest[ETH_ALEN] */ + MK_ARRAY(TYPE_CHAR, 2), /* __be16 vlan_etype */ + MK_ARRAY(TYPE_CHAR, 2), /* __be16 vlan_tci */ + MK_ARRAY(TYPE_CHAR, 8)) /* __be32 data[2] */ + +/* + * Union ethtool_flow_union contains alternatives that are either struct that + * only uses __be* types or char/__u8, or "__u8 hdata[52]". We can treat it as + * byte array in all cases. + */ +STRUCT(ethtool_rx_flow_spec, + TYPE_INT, /* flow_type */ + MK_ARRAY(TYPE_CHAR, 52), /* union ethtool_flow_union h_u */ + MK_STRUCT(STRUCT_ethtool_flow_ext), /* h_ext */ + MK_ARRAY(TYPE_CHAR, 52), /* union ethtool_flow_union m_u */ + MK_STRUCT(STRUCT_ethtool_flow_ext), /* m_ext */ + TYPE_LONGLONG, /* ring_cookie */ + TYPE_INT) /* location */ + +STRUCT(ethtool_rxnfc_rss_context, + TYPE_INT, /* cmd */ + TYPE_INT, /* flow_type */ + TYPE_LONGLONG, /* data */ + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ + TYPE_INT) /* rss_context */ + +STRUCT(ethtool_rxnfc_rule_cnt, + TYPE_INT, /* cmd */ + TYPE_INT, /* flow_type */ + TYPE_LONGLONG, /* data */ + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ + TYPE_INT) /* rss_cnt */ + +STRUCT(ethtool_rxnfc_rule_locs, + TYPE_INT, /* cmd */ + TYPE_INT, /* flow_type */ + TYPE_LONGLONG, /* data */ + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ + TYPE_INT, /* rss_cnt */ + MK_FLEXIBLE_ARRAY(TYPE_INT, 4)) /* rule_locs[0]: rss_cnt */ + +/* + * For ETHTOOL_{G,S}RXFH, originally only the first three fields are defined, + * but with certain options, more fields are used. + */ +STRUCT_SPECIAL(ethtool_rxnfc_get_set_rxfh) + +STRUCT(ethtool_flash, + TYPE_INT, /* cmd */ + TYPE_INT, /* region */ + MK_ARRAY(TYPE_CHAR, 128)) /* data[ETHTOOL_FLASH_MAX_FILENAME] */ + +STRUCT_SPECIAL(ethtool_sset_info) + +STRUCT(ethtool_rxfh_indir, + TYPE_INT, /* cmd */ + TYPE_INT, /* size */ + MK_FLEXIBLE_ARRAY(TYPE_INT, 1)) /* ring_index[0]: size */ + +STRUCT_SPECIAL(ethtool_rxfh) + +STRUCT(ethtool_get_features_block, + TYPE_INT, /* available */ + TYPE_INT, /* requested */ + TYPE_INT, /* active */ + TYPE_INT) /* never_changed */ + +STRUCT(ethtool_gfeatures, + TYPE_INT, /* cmd */ + TYPE_INT, /* size */ + MK_FLEXIBLE_ARRAY(MK_STRUCT(STRUCT_ethtool_get_features_block), 1)) + /* features[0]: size */ + +STRUCT(ethtool_set_features_block, + TYPE_INT, /* valid */ + TYPE_INT) /* requested */ + +STRUCT(ethtool_sfeatures, + TYPE_INT, /* cmd */ + TYPE_INT, /* size */ + MK_FLEXIBLE_ARRAY(MK_STRUCT(STRUCT_ethtool_set_features_block), 1)) + /* features[0]: size */ + +STRUCT(ethtool_channels, + TYPE_INT, /* cmd */ + TYPE_INT, /* max_rx */ + TYPE_INT, /* max_tx */ + TYPE_INT, /* max_other */ + TYPE_INT, /* max_combined */ + TYPE_INT, /* rx_count */ + TYPE_INT, /* tx_count */ + TYPE_INT, /* other_count */ + TYPE_INT) /* combined_count */ + +/* + * For ETHTOOL_SET_DUMP and ETHTOOL_GET_DUMP_FLAG, the flexible array `data` is + * not used. + */ +STRUCT(ethtool_dump_no_data, + TYPE_INT, /* cmd */ + TYPE_INT, /* version */ + TYPE_INT, /* flag */ + TYPE_INT) /* len */ + +STRUCT(ethtool_dump, + TYPE_INT, /* cmd */ + TYPE_INT, /* version */ + TYPE_INT, /* flag */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 3)) /* data[0]: len */ + +STRUCT(ethtool_ts_info, + TYPE_INT, /* cmd */ + TYPE_INT, /* so_timestamping */ + TYPE_INT, /* phc_index */ + TYPE_INT, /* tx_types */ + MK_ARRAY(TYPE_INT, 3), /* tx_reserved */ + TYPE_INT, /* rx_filters */ + MK_ARRAY(TYPE_INT, 3)) /* rx_reserved */ + +STRUCT(ethtool_modinfo, + TYPE_INT, /* cmd */ + TYPE_INT, /* type */ + TYPE_INT, /* eeprom_len */ + MK_ARRAY(TYPE_INT, 8)) /* reserved */ + +STRUCT(ethtool_tunable, + TYPE_INT, /* cmd */ + TYPE_INT, /* id */ + TYPE_INT, /* type_id */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_PTRVOID, 3)) /* data[0]: len */ + +STRUCT_SPECIAL(ethtool_link_settings) + +STRUCT(ethtool_fecparam, + TYPE_INT, /* cmd */ + TYPE_INT, /* active_fec */ + TYPE_INT, /* fec */ + TYPE_INT) /* reserved */ + +STRUCT_SPECIAL(ethtool_per_queue_op) diff --git a/tests/tcg/multiarch/ethtool.c b/tests/tcg/multiarch/ethtool.c new file mode 100644 index 0000000000..dcb10230e0 --- /dev/null +++ b/tests/tcg/multiarch/ethtool.c @@ -0,0 +1,423 @@ +#include <asm-generic/errno.h> +#include <assert.h> +#include <errno.h> +#include <inttypes.h> +#include <linux/ethtool.h> +#include <linux/if.h> +#include <linux/sockios.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +const int number_of_entries_to_print = 10; +const uint32_t protected_memory_pattern[] = { + 0xdeadc0de, 0xb0bb1e, 0xfacade, 0xfeeb1e }; + +static void fail_with(const char *action, const char *cmd_name, int cmd, + int err) +{ + if (errno == EOPNOTSUPP) { + printf("Unsupported operation: %s; errno = %d: %s.\n" + "TEST SKIPPED (%s = 0x%x).\n", + action, err, strerror(err), cmd_name, cmd); + return; + } + if (err) { + fprintf(stderr, + "Failed to %s (%s = 0x%x): errno = %d: %s\n", + action, cmd_name, cmd, err, strerror(err)); + } else { + fprintf(stderr, + "Failed to %s (%s = 0x%x): no errno\n", + action, cmd_name, cmd); + } + exit(err); +} +#define FAIL(action, cmd) fail_with(action, #cmd, cmd, errno) + +/* + * `calloc_protected` and `protected_memory_changed` can be used to verify that + * a system call does not write pass intended memory boundary. + * + * `ptr = calloc_protected(n)` will allocate extra memory after `n` bytes and + * populate it with a memory pattern. The first `n` bytes are still guaranteed + * to be zeroed out like `calloc(1, n)`. `protected_memory_changed(ptr, n)` + * takes the pointer and the original size `n` and checks that the memory + * pattern is intact. + */ +uint8_t *calloc_protected(size_t struct_size) +{ + uint8_t *buf = (uint8_t *) calloc( + 1, + struct_size + sizeof(protected_memory_pattern)); + memcpy(buf + struct_size, protected_memory_pattern, + sizeof(protected_memory_pattern)); + return buf; +} + +bool protected_memory_changed(const uint8_t *ptr, size_t struct_size) +{ + return memcmp(ptr + struct_size, protected_memory_pattern, + sizeof(protected_memory_pattern)) != 0; +} + +void print_entries(const char *fmt, int len, uint32_t *entries) +{ + int i; + for (i = 0; i < len && i < number_of_entries_to_print; ++i) { + printf(fmt, entries[i]); + } + if (len > number_of_entries_to_print) { + printf(" (%d more omitted)", len - number_of_entries_to_print); + } +} + +void basic_test(int socketfd, struct ifreq ifr) +{ + struct ethtool_drvinfo drvinfo; + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (void *)&drvinfo; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get driver info", ETHTOOL_GDRVINFO); + return; + } + printf("Driver: %s (version %s)\n", drvinfo.driver, drvinfo.version); +} + +/* Test flexible array. */ +void test_get_stats(int socketfd, struct ifreq ifr, int n_stats) +{ + int i; + struct ethtool_stats *stats = (struct ethtool_stats *)calloc( + 1, sizeof(*stats) + sizeof(stats->data[0]) * n_stats); + stats->cmd = ETHTOOL_GSTATS; + stats->n_stats = n_stats; + ifr.ifr_data = (void *)stats; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get statastics", ETHTOOL_GSTATS); + free(stats); + return; + } + if (stats->n_stats != n_stats) { + FAIL("get consistent number of statistics", ETHTOOL_GSTATS); + } + for (i = 0; i < stats->n_stats && i < number_of_entries_to_print; ++i) { + printf("stats[%d] = %llu\n", i, (unsigned long long)stats->data[i]); + } + if (stats->n_stats > number_of_entries_to_print) { + printf("(%d more omitted)\n", + stats->n_stats - number_of_entries_to_print); + } + free(stats); +} + +/* Test flexible array with char array as elements. */ +void test_get_strings(int socketfd, struct ifreq ifr, int n_stats) +{ + int i; + struct ethtool_gstrings *gstrings = + (struct ethtool_gstrings *)calloc( + 1, sizeof(*gstrings) + ETH_GSTRING_LEN * n_stats); + gstrings->cmd = ETHTOOL_GSTRINGS; + gstrings->string_set = ETH_SS_STATS; + gstrings->len = n_stats; + ifr.ifr_data = (void *)gstrings; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get string set", ETHTOOL_GSTRINGS); + free(gstrings); + return; + } + if (gstrings->len != n_stats) { + FAIL("get consistent number of statistics", ETHTOOL_GSTRINGS); + } + for (i = 0; i < gstrings->len && i < number_of_entries_to_print; ++i) { + printf("stat_names[%d] = %.*s\n", + i, ETH_GSTRING_LEN, gstrings->data + i * ETH_GSTRING_LEN); + } + if (gstrings->len > number_of_entries_to_print) { + printf("(%d more omitted)\n", + gstrings->len - number_of_entries_to_print); + } + free(gstrings); +} + +/* + * Testing manual implementation of converting `struct ethtool_sset_info`, also + * info for subsequent tests. + */ +int test_get_sset_info(int socketfd, struct ifreq ifr) +{ + const int n_sset = 2; + int n_stats; + struct ethtool_sset_info *sset_info = + (struct ethtool_sset_info *)calloc( + 1, sizeof(*sset_info) + sizeof(sset_info->data[0]) * n_sset); + sset_info->cmd = ETHTOOL_GSSET_INFO; + sset_info->sset_mask = 1 << ETH_SS_TEST | 1 << ETH_SS_STATS; + assert(__builtin_popcount(sset_info->sset_mask) == n_sset); + ifr.ifr_data = (void *)sset_info; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + fail_with("get string set info", "ETHTOOL_GSSET_INFO", + ETHTOOL_GSSET_INFO, errno); + free(sset_info); + return 0; + } + if ((sset_info->sset_mask & (1 << ETH_SS_STATS)) == 0) { + puts("No stats string set info, SKIPPING dependent tests"); + free(sset_info); + return 0; + } + n_stats = (sset_info->sset_mask & (1 << ETH_SS_TEST)) ? + sset_info->data[1] : + sset_info->data[0]; + printf("n_stats = %d\n", n_stats); + free(sset_info); + return n_stats; +} + +/* + * Test manual implementation of converting `struct ethtool_rxnfc`, focusing on + * the case where only the first three fields are present. (The original struct + * definition.) + */ +void test_get_rxfh(int socketfd, struct ifreq ifr) +{ + struct ethtool_rxnfc *rxnfc; + const int rxnfc_first_three_field_size = + sizeof(rxnfc->cmd) + sizeof(rxnfc->flow_type) + sizeof(rxnfc->data); + rxnfc = (struct ethtool_rxnfc *)calloc_protected( + rxnfc_first_three_field_size); + rxnfc->cmd = ETHTOOL_GRXFH; + rxnfc->flow_type = TCP_V4_FLOW; + ifr.ifr_data = (void *)rxnfc; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get RX flow classification rules", ETHTOOL_GRXFH); + free(rxnfc); + return; + } + if (protected_memory_changed((const uint8_t *)rxnfc, + rxnfc_first_three_field_size)) { + FAIL("preserve memory after the first three fields", ETHTOOL_GRXFH); + } + printf("Flow hash bitmask (flow_type = TCP v4): 0x%llx\n", + (unsigned long long)rxnfc->data); + free(rxnfc); +} + +/* Test manual implementation of converting `struct ethtool_link_settings`. */ +void test_get_link_settings(int socketfd, struct ifreq ifr) +{ + int link_mode_masks_nwords; + struct ethtool_link_settings *link_settings_header = + (struct ethtool_link_settings *) calloc_protected( + sizeof(*link_settings_header)); + link_settings_header->cmd = ETHTOOL_GLINKSETTINGS; + link_settings_header->link_mode_masks_nwords = 0; + ifr.ifr_data = (void *)link_settings_header; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get link settings mask sizes", ETHTOOL_GLINKSETTINGS); + free(link_settings_header); + return; + } + if (protected_memory_changed((const uint8_t *)link_settings_header, + sizeof(*link_settings_header))) { + FAIL("preserve link_mode_masks", ETHTOOL_GLINKSETTINGS); + } + if (link_settings_header->link_mode_masks_nwords >= 0) { + FAIL("complete handshake", ETHTOOL_GLINKSETTINGS); + } + link_mode_masks_nwords = -link_settings_header->link_mode_masks_nwords; + + struct ethtool_link_settings *link_settings = + (struct ethtool_link_settings *)calloc( + 1, + sizeof(*link_settings) + + sizeof(link_settings_header->link_mode_masks[0]) * + link_mode_masks_nwords * 3); + link_settings->cmd = ETHTOOL_GLINKSETTINGS; + link_settings->link_mode_masks_nwords = link_mode_masks_nwords; + ifr.ifr_data = (void *)link_settings; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get link settings", ETHTOOL_GLINKSETTINGS); + free(link_settings_header); + free(link_settings); + return; + } + if (link_settings->link_mode_masks_nwords != link_mode_masks_nwords) { + FAIL("have consistent number of mode masks", ETHTOOL_GLINKSETTINGS); + } + + printf("Link speed: %d MB\n", link_settings->speed); + printf("Number of link mode masks: %d\n", + link_settings->link_mode_masks_nwords); + if (link_settings->link_mode_masks_nwords > 0) { + printf("Supported bitmap:"); + print_entries(" 0x%08x", + link_settings->link_mode_masks_nwords, + link_settings->link_mode_masks); + putchar('\n'); + + printf("Advertising bitmap:"); + print_entries(" 0x%08x", + link_settings->link_mode_masks_nwords, + link_settings->link_mode_masks + + link_settings->link_mode_masks_nwords); + putchar('\n'); + + printf("Lp advertising bitmap:"); + print_entries(" 0x%08x", + link_settings->link_mode_masks_nwords, + link_settings->link_mode_masks + + 2 * link_settings->link_mode_masks_nwords); + putchar('\n'); + } + + free(link_settings_header); + free(link_settings); +} + +/* Test manual implementation of converting `struct ethtool_per_queue_op`. */ +void test_perqueue(int socketfd, struct ifreq ifr) +{ + const int n_queue = 2; + int i; + struct ethtool_per_queue_op *per_queue_op = + (struct ethtool_per_queue_op *)calloc( + 1, + sizeof(*per_queue_op) + sizeof(struct ethtool_coalesce) * n_queue); + per_queue_op->cmd = ETHTOOL_PERQUEUE; + per_queue_op->sub_command = ETHTOOL_GCOALESCE; + per_queue_op->queue_mask[0] = 0x3; + ifr.ifr_data = (void *)per_queue_op; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get coalesce per queue", ETHTOOL_PERQUEUE); + free(per_queue_op); + return; + } + for (i = 0; i < n_queue; ++i) { + struct ethtool_coalesce *coalesce = (struct ethtool_coalesce *)( + per_queue_op->data + sizeof(*coalesce) * i); + if (coalesce->cmd != ETHTOOL_GCOALESCE) { + fprintf(stderr, + "ETHTOOL_PERQUEUE (%d) sub_command ETHTOOL_GCOALESCE (%d) " + "fails to set entry %d's cmd to ETHTOOL_GCOALESCE, got %d " + "instead\n", + ETHTOOL_PERQUEUE, ETHTOOL_GCOALESCE, i, + coalesce->cmd); + exit(-1); + } + printf("rx_coalesce_usecs[%d] = %u\nrx_max_coalesced_frames[%d] = %u\n", + i, coalesce->rx_coalesce_usecs, + i, coalesce->rx_max_coalesced_frames); + } + + free(per_queue_op); +} + +/* Test manual implementation of ETHTOOL_GRSSH. */ +void test_get_rssh(int socketfd, struct ifreq ifr) +{ + int i; + struct ethtool_rxfh *rxfh_header = + (struct ethtool_rxfh *)calloc_protected(sizeof(*rxfh_header)); + rxfh_header->cmd = ETHTOOL_GRSSH; + ifr.ifr_data = (void *)rxfh_header; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get RX flow hash indir and hash key size", ETHTOOL_GRSSH); + free(rxfh_header); + return; + } + if (protected_memory_changed((const uint8_t *)rxfh_header, + sizeof(*rxfh_header))) { + FAIL("preserve rss_config", ETHTOOL_GRSSH); + } + printf("RX flow hash indir size = %d\nRX flow hash key size = %d\n", + rxfh_header->indir_size, rxfh_header->key_size); + + struct ethtool_rxfh *rxfh = (struct ethtool_rxfh *)calloc( + 1, + sizeof(*rxfh) + 4 * rxfh_header->indir_size + rxfh_header->key_size); + *rxfh = *rxfh_header; + ifr.ifr_data = (void *)rxfh; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { + FAIL("get RX flow hash indir and hash key", ETHTOOL_GRSSH); + free(rxfh_header); + free(rxfh); + return; + } + + if (rxfh->indir_size == 0) { + printf("No RX flow hash indir\n"); + } else { + printf("RX flow hash indir:"); + print_entries(" 0x%08x", rxfh->indir_size, rxfh->rss_config); + putchar('\n'); + } + + if (rxfh->key_size == 0) { + printf("No RX flow hash key\n"); + } else { + char *key = (char *)(rxfh->rss_config + rxfh->indir_size); + printf("RX flow hash key:"); + for (i = 0; i < rxfh->key_size; ++i) { + if (i % 2 == 0) { + putchar(' '); + } + printf("%02hhx", key[i]); + } + putchar('\n'); + } + free(rxfh_header); + free(rxfh); +} + +int main(int argc, char **argv) +{ + int socketfd, n_stats, i; + struct ifreq ifr; + + socketfd = socket(AF_INET, SOCK_DGRAM, 0); + if (socketfd == -1) { + int err = errno; + fprintf(stderr, + "Failed to open socket: errno = %d: %s\n", + err, strerror(err)); + return err; + } + + for (i = 1;; ++i) { + ifr.ifr_ifindex = i; + if (ioctl(socketfd, SIOCGIFNAME, &ifr) == -1) { + puts("Could not find a non-loopback interface, SKIPPING"); + return 0; + } + if (strncmp(ifr.ifr_name, "lo", IFNAMSIZ) != 0) { + break; + } + } + printf("Interface index: %d\nInterface name: %.*s\n", + ifr.ifr_ifindex, IFNAMSIZ, ifr.ifr_name); + + basic_test(socketfd, ifr); + + n_stats = test_get_sset_info(socketfd, ifr); + if (n_stats > 0) { + /* Testing lexible arrays. */ + test_get_stats(socketfd, ifr, n_stats); + test_get_strings(socketfd, ifr, n_stats); + } + + /* Testing manual implementations of structure convertions. */ + test_get_rxfh(socketfd, ifr); + test_get_link_settings(socketfd, ifr); + test_perqueue(socketfd, ifr); + + /* Testing manual implementations of operations. */ + test_get_rssh(socketfd, ifr); + + return 0; +} -- 2.28.0.220.ged08abb693-goog ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 8/8] linux-user: Add support for SIOCETHTOOL ioctl 2020-08-11 7:09 ` [PATCH v2 8/8] linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng @ 2020-12-18 4:03 ` Shu-Chun Weng 0 siblings, 0 replies; 22+ messages in thread From: Shu-Chun Weng @ 2020-12-18 4:03 UTC (permalink / raw) To: qemu-devel; +Cc: Laurent Vivier [-- Attachment #1.1: Type: text/plain, Size: 71910 bytes --] Ping -- any comments on https://patchew.org/QEMU/cover.1597129029.git.scw@google.com/61323061160b6d21f43b266764eda813b5e021e2.1597129029.git.scw@google.com/ On Tue, Aug 11, 2020 at 12:10 AM Shu-Chun Weng <scw@google.com> wrote: > The ioctl numeric values are platform-independent and determined by > the file include/uapi/linux/sockios.h in Linux kernel source code: > > #define SIOCETHTOOL 0x8946 > > These ioctls get (or set) various structures pointed by the field > ifr_data in the structure ifreq depending on the first 4 bytes of the > memory region. > > This change clones the ioctl framework into ethtool-specific dispatch > logic in its own file. A number of definitions previously only visible > in syscall.c are thus exported to syscall_defs.h to be used in the new > files. > > Signed-off-by: Shu-Chun Weng <scw@google.com> > --- > v1 -> v2: > Fix style problems. > > linux-user/Makefile.objs | 3 +- > linux-user/ethtool.c | 840 ++++++++++++++++++++++++++++++++++ > linux-user/ethtool.h | 20 + > linux-user/ethtool_entries.h | 107 +++++ > linux-user/ioctls.h | 2 + > linux-user/qemu.h | 1 + > linux-user/syscall.c | 36 +- > linux-user/syscall_defs.h | 12 + > linux-user/syscall_types.h | 280 ++++++++++++ > tests/tcg/multiarch/ethtool.c | 423 +++++++++++++++++ > 10 files changed, 1712 insertions(+), 12 deletions(-) > create mode 100644 linux-user/ethtool.c > create mode 100644 linux-user/ethtool.h > create mode 100644 linux-user/ethtool_entries.h > create mode 100644 tests/tcg/multiarch/ethtool.c > > diff --git a/linux-user/Makefile.objs b/linux-user/Makefile.objs > index 1940910a73..971d43173a 100644 > --- a/linux-user/Makefile.objs > +++ b/linux-user/Makefile.objs > @@ -1,7 +1,8 @@ > obj-y = main.o syscall.o strace.o mmap.o signal.o \ > elfload.o linuxload.o uaccess.o uname.o \ > safe-syscall.o $(TARGET_ABI_DIR)/signal.o \ > - $(TARGET_ABI_DIR)/cpu_loop.o exit.o fd-trans.o > + $(TARGET_ABI_DIR)/cpu_loop.o exit.o fd-trans.o \ > + ethtool.o > > obj-$(TARGET_HAS_BFLT) += flatload.o > obj-$(TARGET_I386) += vm86.o > diff --git a/linux-user/ethtool.c b/linux-user/ethtool.c > new file mode 100644 > index 0000000000..fac97b9ba1 > --- /dev/null > +++ b/linux-user/ethtool.c > @@ -0,0 +1,840 @@ > +/* > + * Linux ioctl system call SIOCETHTOOL requests > + * > + * Copyright (c) 2020 Shu-Chun Weng > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, see <http://www.gnu.org/licenses/>. > + */ > +#include "qemu/osdep.h" > +#include <stdio.h> > +#include <linux/ethtool.h> > +#include <linux/if.h> > +#include <linux/sockios.h> > +#include <linux/unistd.h> > +#include "ethtool.h" > +#include "qemu.h" > +#include "syscall_defs.h" > + > +/* Non-standard ethtool structure definitions. */ > +/* > + * struct ethtool_rxnfc { > + * __u32 cmd; > + * __u32 flow_type; > + * __u64 data; > + * struct ethtool_rx_flow_spec fs; > + * union { > + * __u32 rule_cnt; > + * __u32 rss_context; > + * }; > + * __u32 rule_locs[0]; > + * }; > + * > + * Originally defined for ETHTOOL_{G,S}RXFH with only the cmd, flow_type > and > + * data members. For other commands, dedicated standard structure > definitions > + * are listed in syscall_types.h. > + */ > +static void host_to_target_ethtool_rxnfc_get_set_rxfh(void *dst, > + const void *src) > +{ > + static const argtype ethtool_rx_flow_spec_argtype[] = { > + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), TYPE_NULL }; > + struct ethtool_rxnfc *target = dst; > + const struct ethtool_rxnfc *host = src; > + > + target->cmd = tswap32(host->cmd); > + target->flow_type = tswap32(host->flow_type); > + target->data = tswap64(host->data); > + > + if (host->cmd == ETHTOOL_SRXFH) { > + /* > + * struct ethtool_rxnfc was originally defined for > ETHTOOL_{G,S}RXFH > + * with only the cmd, flow_type and data members. Guest program > might > + * still be using that definition. > + */ > + return; > + } > + if (host->cmd != ETHTOOL_GRXFH) { > + fprintf(stderr, "host_to_target_ethtool_rxnfc_get_set_rxfh called > with " > + "command 0x%x which is not ETHTOOL_SRXFH or > ETHTOOL_GRXFH\n", > + host->cmd); > + } > + if ((host->flow_type & FLOW_RSS) == 0) { > + return; > + } > + /* > + * If `FLOW_RSS` was requested then guest program must be using the > new > + * definition. > + */ > + thunk_convert(&target->fs, &host->fs, ethtool_rx_flow_spec_argtype, > + THUNK_TARGET); > + target->rule_cnt = tswap32(host->rule_cnt); > +} > + > +static void target_to_host_ethtool_rxnfc_get_set_rxfh(void *dst, > + const void *src) > +{ > + static const argtype ethtool_rx_flow_spec_argtype[] = { > + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), TYPE_NULL }; > + struct ethtool_rxnfc *host = dst; > + const struct ethtool_rxnfc *target = src; > + > + host->cmd = tswap32(target->cmd); > + host->flow_type = tswap32(target->flow_type); > + host->data = tswap64(target->data); > + > + if (host->cmd == ETHTOOL_SRXFH) { > + /* > + * struct ethtool_rxnfc was originally defined for > ETHTOOL_{G,S}RXFH > + * with only the cmd, flow_type and data members. Guest program > might > + * still be using that definition. > + */ > + return; > + } > + if (host->cmd != ETHTOOL_GRXFH) { > + fprintf(stderr, "target_to_host_ethtool_rxnfc_get_set_rxfh called > with " > + "command 0x%x which is not ETHTOOL_SRXFH or > ETHTOOL_GRXFH\n", > + host->cmd); > + } > + if ((host->flow_type & FLOW_RSS) == 0) { > + return; > + } > + /* > + * If `FLOW_RSS` was requested then guest program must be using the > new > + * definition. > + */ > + thunk_convert(&host->fs, &target->fs, ethtool_rx_flow_spec_argtype, > + THUNK_HOST); > + host->rule_cnt = tswap32(target->rule_cnt); > +} > + > +static int target_ethtool_rxnfc_get_set_rxfh_size(const void *src) > +{ > + const struct ethtool_rxnfc *target = src; > + int cmd = tswap32(target->cmd); > + if (cmd == ETHTOOL_SRXFH || > + (cmd == ETHTOOL_GRXFH && > + (tswap32(target->flow_type) & FLOW_RSS) == 0)) { > + return 16; > + } > + return sizeof(struct ethtool_rxnfc); > +} > + > +static int host_ethtool_rxnfc_get_set_rxfh_size(const void *src) > +{ > + const struct ethtool_rxnfc *host = src; > + if (host->cmd == ETHTOOL_SRXFH || > + (host->cmd == ETHTOOL_GRXFH && (host->flow_type & FLOW_RSS) == > 0)) { > + return 16; > + } > + return sizeof(struct ethtool_rxnfc); > +} > + > +const StructEntry struct_ethtool_rxnfc_get_set_rxfh_def = { > + .convert = { > + host_to_target_ethtool_rxnfc_get_set_rxfh, > + target_to_host_ethtool_rxnfc_get_set_rxfh }, > + .thunk_size = { > + target_ethtool_rxnfc_get_set_rxfh_size, > + host_ethtool_rxnfc_get_set_rxfh_size }, > + .size = { 16, 16 }, > + .align = { > + __alignof__(struct ethtool_rxnfc), > + __alignof__(struct ethtool_rxnfc) }, > +}; > + > +/* > + * struct ethtool_sset_info { > + * __u32 cmd; > + * __u32 reserved; > + * __u64 sset_mask; > + * __u32 data[0]; > + * }; > + * > + * `sset_mask` is a bitmask of string sets. `data` is the buffer for > string set > + * sizes, containing number of 1s in `sset_mask`'s binary representation > number > + * of 4-byte entries. > + * > + * Since all fields are fixed-width and number of 1s in `sset_mask` does > not > + * change between architectures, host-to-target and target-to-host are > + * identical. > + */ > +static void convert_ethtool_sset_info(void *dst, const void *src) > +{ > + int i, set_count; > + struct ethtool_sset_info *dst_sset_info = dst; > + const struct ethtool_sset_info *src_sset_info = src; > + > + dst_sset_info->cmd = tswap32(src_sset_info->cmd); > + dst_sset_info->sset_mask = tswap64(src_sset_info->sset_mask); > + > + set_count = ctpop64(src_sset_info->sset_mask); > + for (i = 0; i < set_count; ++i) { > + dst_sset_info->data[i] = tswap32(src_sset_info->data[i]); > + } > +} > + > +static int ethtool_sset_info_size(const void *src) > +{ > + const struct ethtool_sset_info *src_sset_info = src; > + return sizeof(struct ethtool_sset_info) + > + ctpop64(src_sset_info->sset_mask) * > sizeof(src_sset_info->data[0]); > +} > + > +const StructEntry struct_ethtool_sset_info_def = { > + .convert = { > + convert_ethtool_sset_info, convert_ethtool_sset_info }, > + .thunk_size = { > + ethtool_sset_info_size, ethtool_sset_info_size }, > + .size = { > + sizeof(struct ethtool_sset_info), > + sizeof(struct ethtool_sset_info) }, > + .align = { > + __alignof__(struct ethtool_sset_info), > + __alignof__(struct ethtool_sset_info) }, > +}; > + > +/* > + * struct ethtool_rxfh { > + * __u32 cmd; > + * __u32 rss_context; > + * __u32 indir_size; > + * __u32 key_size; > + * __u8 hfunc; > + * __u8 rsvd8[3]; > + * __u32 rsvd32; > + * __u32 rss_config[0]; > + * }; > + * > + * `rss_config`: indirection table of `indir_size` __u32 elements, > followed by > + * hash key of `key_size` bytes. > + * > + * `indir_size` could be ETH_RXFH_INDIR_NO_CHANGE when `cmd` is > ETHTOOL_SRSSH > + * and there would be no indircetion table in `rss_config`. > + */ > +static void convert_ethtool_rxfh_header(void *dst, const void *src) > +{ > + struct ethtool_rxfh *dst_rxfh = dst; > + const struct ethtool_rxfh *src_rxfh = src; > + > + dst_rxfh->cmd = tswap32(src_rxfh->cmd); > + dst_rxfh->rss_context = tswap32(src_rxfh->rss_context); > + dst_rxfh->indir_size = tswap32(src_rxfh->indir_size); > + dst_rxfh->key_size = tswap32(src_rxfh->key_size); > + dst_rxfh->hfunc = src_rxfh->hfunc; > + dst_rxfh->rsvd8[0] = src_rxfh->rsvd8[0]; > + dst_rxfh->rsvd8[1] = src_rxfh->rsvd8[1]; > + dst_rxfh->rsvd8[2] = src_rxfh->rsvd8[2]; > + dst_rxfh->rsvd32 = tswap32(src_rxfh->rsvd32); > +} > + > +static void convert_ethtool_rxfh_rss_config( > + void *dst, const void *src, uint32_t indir_size, uint32_t key_size) { > + uint32_t *dst_rss_config = (uint32_t *)dst; > + const uint32_t *src_rss_config = (const uint32_t *)src; > + int i; > + for (i = 0; i < indir_size; ++i) { > + dst_rss_config[i] = tswap32(src_rss_config[i]); > + } > + if (key_size > 0) { > + memcpy(dst_rss_config + indir_size, > + src_rss_config + indir_size, > + key_size); > + } > +} > + > +static void host_to_target_ethtool_rxfh(void *dst, const void *src) > +{ > + struct ethtool_rxfh *target = dst; > + const struct ethtool_rxfh *host = src; > + > + convert_ethtool_rxfh_header(dst, src); > + > + const uint32_t indir_size = > + host->cmd == ETHTOOL_SRSSH && > + host->indir_size == ETH_RXFH_INDIR_NO_CHANGE ? > + 0 : > + host->indir_size; > + convert_ethtool_rxfh_rss_config(target->rss_config, host->rss_config, > + indir_size, host->key_size); > +} > + > +static void target_to_host_ethtool_rxfh(void *dst, const void *src) > +{ > + struct ethtool_rxfh *host = dst; > + const struct ethtool_rxfh *target = src; > + > + convert_ethtool_rxfh_header(dst, src); > + > + const uint32_t indir_size = > + host->cmd == ETHTOOL_SRSSH && > + host->indir_size == ETH_RXFH_INDIR_NO_CHANGE ? > + 0 : > + host->indir_size; > + convert_ethtool_rxfh_rss_config(host->rss_config, target->rss_config, > + indir_size, host->key_size); > +} > + > +static int target_ethtool_rxfh_size(const void *src) > +{ > + const struct ethtool_rxfh *target = src; > + if (tswap32(target->cmd) == ETHTOOL_SRSSH && > + tswap32(target->indir_size) == ETH_RXFH_INDIR_NO_CHANGE) { > + return sizeof(struct ethtool_rxfh) + tswap32(target->key_size); > + } > + return sizeof(struct ethtool_rxfh) + > + tswap32(target->indir_size) * sizeof(target->rss_config[0]) + > + tswap32(target->key_size); > +} > + > +static int host_ethtool_rxfh_size(const void *src) > +{ > + const struct ethtool_rxfh *host = src; > + if (host->cmd == ETHTOOL_SRSSH && > + host->indir_size == ETH_RXFH_INDIR_NO_CHANGE) { > + return sizeof(struct ethtool_rxfh) + host->key_size; > + } > + return sizeof(struct ethtool_rxfh) + > + host->indir_size * sizeof(host->rss_config[0]) + > + host->key_size; > +} > + > +const StructEntry struct_ethtool_rxfh_def = { > + .convert = { > + host_to_target_ethtool_rxfh, target_to_host_ethtool_rxfh }, > + .thunk_size = { > + target_ethtool_rxfh_size, host_ethtool_rxfh_size }, > + .size = { > + sizeof(struct ethtool_rxfh), sizeof(struct ethtool_rxfh) }, > + .align = { > + __alignof__(struct ethtool_rxfh), __alignof__(struct > ethtool_rxfh) }, > +}; > + > +/* > + * struct ethtool_link_settings { > + * __u32 cmd; > + * __u32 speed; > + * __u8 duplex; > + * __u8 port; > + * __u8 phy_address; > + * __u8 autoneg; > + * __u8 mdio_support; > + * __u8 eth_tp_mdix; > + * __u8 eth_tp_mdix_ctrl; > + * __s8 link_mode_masks_nwords; > + * __u8 transceiver; > + * __u8 reserved1[3]; > + * __u32 reserved[7]; > + * __u32 link_mode_masks[0]; > + * }; > + * > + * layout of link_mode_masks fields: > + * __u32 map_supported[link_mode_masks_nwords]; > + * __u32 map_advertising[link_mode_masks_nwords]; > + * __u32 map_lp_advertising[link_mode_masks_nwords]; > + * > + * `link_mode_masks_nwords` can be negative when returning from kernel if > the > + * provided request size is not supported. > + */ > + > +static void host_to_target_ethtool_link_settings(void *dst, const void > *src) > +{ > + int i; > + struct ethtool_link_settings *target = dst; > + const struct ethtool_link_settings *host = src; > + > + target->cmd = tswap32(host->cmd); > + target->speed = tswap32(host->speed); > + target->duplex = host->duplex; > + target->port = host->port; > + target->phy_address = host->phy_address; > + target->autoneg = host->autoneg; > + target->mdio_support = host->mdio_support; > + target->eth_tp_mdix = host->eth_tp_mdix; > + target->eth_tp_mdix_ctrl = host->eth_tp_mdix_ctrl; > + target->link_mode_masks_nwords = host->link_mode_masks_nwords; > + target->transceiver = host->transceiver; > + for (i = 0; i < 3; ++i) { > + target->reserved1[i] = host->reserved1[i]; > + } > + for (i = 0; i < 7; ++i) { > + target->reserved[i] = tswap32(host->reserved[i]); > + } > + > + if (host->link_mode_masks_nwords > 0) { > + for (i = 0; i < host->link_mode_masks_nwords * 3; ++i) { > + target->link_mode_masks[i] = > tswap32(host->link_mode_masks[i]); > + } > + } > +} > + > +static void target_to_host_ethtool_link_settings(void *dst, const void > *src) > +{ > + int i; > + struct ethtool_link_settings *host = dst; > + const struct ethtool_link_settings *target = src; > + > + host->cmd = tswap32(target->cmd); > + host->speed = tswap32(target->speed); > + host->duplex = target->duplex; > + host->port = target->port; > + host->phy_address = target->phy_address; > + host->autoneg = target->autoneg; > + host->mdio_support = target->mdio_support; > + host->eth_tp_mdix = target->eth_tp_mdix; > + host->eth_tp_mdix_ctrl = target->eth_tp_mdix_ctrl; > + host->link_mode_masks_nwords = target->link_mode_masks_nwords; > + host->transceiver = target->transceiver; > + for (i = 0; i < 3; ++i) { > + host->reserved1[i] = target->reserved1[i]; > + } > + for (i = 0; i < 7; ++i) { > + host->reserved[i] = tswap32(target->reserved[i]); > + } > + > + if (host->link_mode_masks_nwords > 0) { > + for (i = 0; i < host->link_mode_masks_nwords * 3; ++i) { > + host->link_mode_masks[i] = > tswap32(target->link_mode_masks[i]); > + } > + } > +} > + > +static int target_ethtool_link_settings_size(const void *src) > +{ > + const struct ethtool_link_settings *target = src; > + if (target->link_mode_masks_nwords > 0) { > + return sizeof(struct ethtool_link_settings) + > + 3 * target->link_mode_masks_nwords * > + sizeof(target->link_mode_masks[0]); > + } else { > + return sizeof(struct ethtool_link_settings); > + } > +} > + > +static int host_ethtool_link_settings_size(const void *src) > +{ > + const struct ethtool_link_settings *host = src; > + if (host->link_mode_masks_nwords > 0) { > + return sizeof(struct ethtool_link_settings) + > + 3 * host->link_mode_masks_nwords * > + sizeof(host->link_mode_masks[0]); > + } else { > + return sizeof(struct ethtool_link_settings); > + } > +} > + > +const StructEntry struct_ethtool_link_settings_def = { > + .convert = { > + host_to_target_ethtool_link_settings, > + target_to_host_ethtool_link_settings > + }, > + .thunk_size = { > + target_ethtool_link_settings_size, > host_ethtool_link_settings_size }, > + .size = { > + sizeof(struct ethtool_link_settings), > + sizeof(struct ethtool_link_settings) }, > + .align = { > + __alignof__(struct ethtool_link_settings), > + __alignof__(struct ethtool_link_settings) }, > +}; > + > +/* > + * struct ethtool_per_queue_op { > + * __u32 cmd; > + * __u32 sub_command; > + * __u32 queue_mask[__KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32)]; > + * char data[]; > + * }; > + * > + * `queue_mask` are a series of bitmasks of the queues. `data` is a > complete > + * command structure for each of the queues addressed. > + * > + * When `cmd` is `ETHTOOL_PERQUEUE` and `sub_command` is > `ETHTOOL_GCOALESCE` or > + * `ETHTOOL_SCOALESCE`, the command structure is `struct > ethtool_coalesce`. > + */ > +static void host_to_target_ethtool_per_queue_op(void *dst, const void > *src) > +{ > + static const argtype ethtool_coalesce_argtype[] = { > + MK_STRUCT(STRUCT_ethtool_coalesce), TYPE_NULL }; > + int i, queue_count; > + struct ethtool_per_queue_op *target = dst; > + const struct ethtool_per_queue_op *host = src; > + > + target->cmd = tswap32(host->cmd); > + target->sub_command = tswap32(host->sub_command); > + > + queue_count = 0; > + for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { > + target->queue_mask[i] = tswap32(host->queue_mask[i]); > + queue_count += ctpop32(host->queue_mask[i]); > + } > + > + if (host->cmd != ETHTOOL_PERQUEUE || > + (host->sub_command != ETHTOOL_GCOALESCE && > + host->sub_command != ETHTOOL_SCOALESCE)) { > + fprintf(stderr, > + "Unknown command 0x%x sub_command 0x%x for " > + "ethtool_per_queue_op, unable to convert the `data` field > " > + "(host-to-target)\n", > + host->cmd, host->sub_command); > + return; > + } > + > + for (i = 0; i < queue_count; ++i) { > + thunk_convert(target->data + i * sizeof(struct ethtool_coalesce), > + host->data + i * sizeof(struct ethtool_coalesce), > + ethtool_coalesce_argtype, THUNK_TARGET); > + } > +} > + > +static void target_to_host_ethtool_per_queue_op(void *dst, const void > *src) > +{ > + static const argtype ethtool_coalesce_argtype[] = { > + MK_STRUCT(STRUCT_ethtool_coalesce), TYPE_NULL }; > + int i, queue_count; > + struct ethtool_per_queue_op *host = dst; > + const struct ethtool_per_queue_op *target = src; > + > + host->cmd = tswap32(target->cmd); > + host->sub_command = tswap32(target->sub_command); > + > + queue_count = 0; > + for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { > + host->queue_mask[i] = tswap32(target->queue_mask[i]); > + queue_count += ctpop32(host->queue_mask[i]); > + } > + > + if (host->cmd != ETHTOOL_PERQUEUE || > + (host->sub_command != ETHTOOL_GCOALESCE && > + host->sub_command != ETHTOOL_SCOALESCE)) { > + fprintf(stderr, > + "Unknown command 0x%x sub_command 0x%x for " > + "ethtool_per_queue_op, unable to convert the `data` field > " > + "(target-to-host)\n", > + host->cmd, host->sub_command); > + return; > + } > + > + for (i = 0; i < queue_count; ++i) { > + thunk_convert(host->data + i * sizeof(struct ethtool_coalesce), > + target->data + i * sizeof(struct ethtool_coalesce), > + ethtool_coalesce_argtype, THUNK_HOST); > + } > +} > + > +static int target_ethtool_per_queue_op_size(const void *src) > +{ > + int i, queue_count; > + const struct ethtool_per_queue_op *target = src; > + > + if (tswap32(target->cmd) != ETHTOOL_PERQUEUE || > + (tswap32(target->sub_command) != ETHTOOL_GCOALESCE && > + tswap32(target->sub_command) != ETHTOOL_SCOALESCE)) { > + fprintf(stderr, > + "Unknown command 0x%x sub_command 0x%x for " > + "ethtool_per_queue_op, unable to compute the size of the " > + "`data` field (target)\n", > + tswap32(target->cmd), tswap32(target->sub_command)); > + return sizeof(struct ethtool_per_queue_op); > + } > + > + queue_count = 0; > + for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { > + queue_count += ctpop32(target->queue_mask[i]); > + } > + return sizeof(struct ethtool_per_queue_op) + > + queue_count * sizeof(struct ethtool_coalesce); > +} > + > +static int host_ethtool_per_queue_op_size(const void *src) > +{ > + int i, queue_count; > + const struct ethtool_per_queue_op *host = src; > + > + if (host->cmd != ETHTOOL_PERQUEUE || > + (host->sub_command != ETHTOOL_GCOALESCE && > + host->sub_command != ETHTOOL_SCOALESCE)) { > + fprintf(stderr, > + "Unknown command 0x%x sub_command 0x%x for " > + "ethtool_per_queue_op, unable to compute the size of the " > + "`data` field (host)\n", > + host->cmd, host->sub_command); > + return sizeof(struct ethtool_per_queue_op); > + } > + > + queue_count = 0; > + for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { > + queue_count += ctpop32(host->queue_mask[i]); > + } > + return sizeof(struct ethtool_per_queue_op) + > + queue_count * sizeof(struct ethtool_coalesce); > +} > + > +const StructEntry struct_ethtool_per_queue_op_def = { > + .convert = { > + host_to_target_ethtool_per_queue_op, > + target_to_host_ethtool_per_queue_op > + }, > + .thunk_size = { > + target_ethtool_per_queue_op_size, host_ethtool_per_queue_op_size > }, > + .size = { > + sizeof(struct ethtool_per_queue_op), > + sizeof(struct ethtool_per_queue_op) }, > + .align = { > + __alignof__(struct ethtool_per_queue_op), > + __alignof__(struct ethtool_per_queue_op) }, > +}; > + > +#define safe_dev_ethtool(fd, ...) \ > + safe_syscall(__NR_ioctl, (fd), SIOCETHTOOL, __VA_ARGS__) > + > +typedef struct EthtoolEntry EthtoolEntry; > + > +typedef abi_long do_ethtool_fn(const EthtoolEntry *ee, uint8_t *buf_temp, > + int fd, struct ifreq *host_ifreq); > + > +struct EthtoolEntry { > + uint32_t cmd; > + int access; > + do_ethtool_fn *do_ethtool; > + const argtype arg_type[3]; > +}; > + > +#define ETHT_R 0x0001 > +#define ETHT_W 0x0002 > +#define ETHT_RW (ETHT_R | ETHT_W) > + > +static do_ethtool_fn do_ethtool_get_rxfh; > + > +static EthtoolEntry ethtool_entries[] = { > +#define ETHTOOL(cmd, access, ...) \ > + { cmd, access, 0, { __VA_ARGS__ } }, > +#define ETHTOOL_SPECIAL(cmd, access, dofn, ...) \ > + { cmd, access, dofn, { __VA_ARGS__ } }, > +#include "ethtool_entries.h" > +#undef ETHTOOL > +#undef ETHTOOL_SPECIAL > + { 0, 0 }, > +}; > + > +/* > + * ETHTOOL_GRSSH has two modes of operations: querying the sizes of the > indir > + * and key as well as actually querying the indir and key. When either > + * `indir_size` or `key_size` is zero, the size of the corresponding > entry is > + * retrieved and updated into the `ethtool_rxfh` struct. When either of > them is > + * non-zero, the actually indir or key is written to `rss_config`. > + * > + * This causes a problem for the generic framework which converts between > host > + * and target structures without the context. When the convertion > function sees > + * an `ethtool_rxfh` struct with non-zero `indir_size` or `key_size`, it > has to > + * assume that there are entries in `rss_config` and needs to convert > them. > + * Unfortunately, when converting the returned `ethtool_rxfh` struct from > host > + * to target after an ETHTOOL_GRSSH call with the first mode, the > `indir_size` > + * and `key_size` fields are populated but there is no actual data to be > + * converted. More importantly, user programs would not have prepared > enough > + * memory for the convertion to take place safely. > + * > + * ETHTOOL_GRSSH thus needs a special implementation which is aware of > the two > + * modes of operations and converts the structure accordingly. > + */ > +abi_long do_ethtool_get_rxfh(const EthtoolEntry *ee, uint8_t *buf_temp, > + int fd, struct ifreq *host_ifreq) > +{ > + const argtype *arg_type = ee->arg_type; > + const abi_long ifreq_data = (abi_long)(unsigned > long)host_ifreq->ifr_data; > + struct ethtool_rxfh *rxfh = (struct ethtool_rxfh *)buf_temp; > + uint32_t user_indir_size, user_key_size; > + abi_long ret; > + void *argptr; > + > + assert(arg_type[0] == TYPE_PTR); > + assert(ee->access == IOC_RW); > + arg_type++; > + > + /* > + * As of Linux kernel v5.8-rc4, ETHTOOL_GRSSH calls never read the > + * `rss_config` part. Converting only the "header" part suffices. > + */ > + argptr = lock_user(VERIFY_READ, ifreq_data, sizeof(*rxfh), 1); > + if (!argptr) { > + return -TARGET_EFAULT; > + } > + convert_ethtool_rxfh_header(rxfh, argptr); > + unlock_user(argptr, ifreq_data, sizeof(*rxfh)); > + > + if (rxfh->cmd != ETHTOOL_GRSSH) { > + return -TARGET_EINVAL; > + } > + user_indir_size = rxfh->indir_size; > + user_key_size = rxfh->key_size; > + > + host_ifreq->ifr_data = (void *)rxfh; > + ret = get_errno(safe_dev_ethtool(fd, host_ifreq)); > + > + /* > + * When a user program supplies `indir_size` or `key_size` but does > not > + * match what the kernel has, the syscall returns EINVAL but the > structure > + * is already updated. Mimicking it here. > + */ > + argptr = lock_user(VERIFY_WRITE, ifreq_data, sizeof(*rxfh), 0); > + if (!argptr) { > + return -TARGET_EFAULT; > + } > + convert_ethtool_rxfh_header(argptr, rxfh); > + unlock_user(argptr, ifreq_data, 0); > + > + if (is_error(ret)) { > + return ret; > + } > + > + if (user_indir_size > 0 || user_key_size > 0) { > + const int rss_config_size = > + user_indir_size * sizeof(rxfh->rss_config[0]) + user_key_size; > + argptr = lock_user(VERIFY_WRITE, ifreq_data + sizeof(*rxfh), > + rss_config_size, 0); > + if (!argptr) { > + return -TARGET_EFAULT; > + } > + convert_ethtool_rxfh_rss_config(argptr, rxfh->rss_config, > + user_indir_size, user_key_size); > + unlock_user(argptr, ifreq_data + sizeof(*rxfh), rss_config_size); > + } > + return ret; > +} > + > +/* > + * Calculates the size of the data type represented by `type_ptr` with > + * `guest_addr` being the underlying memory. Since `type_ptr` may contain > + * flexible arrays, we need access to the underlying memory to determine > their > + * sizes. > + */ > +static int thunk_size(abi_long guest_addr, const argtype *type_ptr) > +{ > + /* > + * lock_user based on `thunk_type_size` then call > `thunk_type_size_with_src` > + * on it. > + */ > + void *src; > + int type_size = thunk_type_size(type_ptr, /*is_host=*/ 0); > + if (!thunk_type_has_flexible_array(type_ptr)) { > + return type_size; > + } > + > + src = lock_user(VERIFY_READ, guest_addr, type_size, 0); > + type_size = thunk_type_size_with_src(src, type_ptr, /*is_host=*/ 0); > + unlock_user(src, guest_addr, 0); > + > + return type_size; > +} > + > +abi_long dev_ethtool(int fd, uint8_t *buf_temp) > +{ > + uint32_t *cmd; > + uint32_t host_cmd; > + const EthtoolEntry *ee; > + const argtype *arg_type; > + abi_long ret; > + int target_size; > + void *argptr; > + > + /* > + * Make a copy of `host_ifreq` because we are going to reuse > `buf_temp` and > + * overwrite it. Further, we will overwrite `host_ifreq.ifreq_data`, > so > + * keep a copy in `ifreq_data`. > + */ > + struct ifreq host_ifreq = *(struct ifreq *)(unsigned long)buf_temp; > + const abi_long ifreq_data = (abi_long)(unsigned > long)host_ifreq.ifr_data; > + > + cmd = (uint32_t *)lock_user(VERIFY_READ, ifreq_data, > sizeof(uint32_t), 0); > + host_cmd = tswap32(*cmd); > + unlock_user(cmd, ifreq_data, 0); > + > + ee = ethtool_entries; > + for (;;) { > + if (ee->cmd == 0) { > + qemu_log_mask(LOG_UNIMP, "Unsupported ethtool cmd=0x%04lx\n", > + (long)host_cmd); > + return -TARGET_ENOSYS; > + } > + if (ee->cmd == host_cmd) { > + break; > + } > + ee++; > + } > + if (ee->do_ethtool) { > + return ee->do_ethtool(ee, buf_temp, fd, &host_ifreq); > + } > + > + host_ifreq.ifr_data = buf_temp; > + /* Even for ETHT_R, cmd still needs to be copied. */ > + *(uint32_t *)buf_temp = host_cmd; > + > + arg_type = ee->arg_type; > + switch (arg_type[0]) { > + case TYPE_NULL: > + /* no argument other than cmd */ > + ret = get_errno(safe_dev_ethtool(fd, &host_ifreq)); > + break; > + case TYPE_PTR: > + arg_type++; > + target_size = thunk_size(ifreq_data, arg_type); > + switch (ee->access) { > + case ETHT_R: > + ret = get_errno(safe_dev_ethtool(fd, &host_ifreq)); > + if (!is_error(ret)) { > + argptr = lock_user(VERIFY_WRITE, ifreq_data, target_size, > 0); > + if (!argptr) { > + return -TARGET_EFAULT; > + } > + thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET); > + unlock_user(argptr, ifreq_data, target_size); > + } > + break; > + case ETHT_W: > + argptr = lock_user(VERIFY_READ, ifreq_data, target_size, 1); > + if (!argptr) { > + return -TARGET_EFAULT; > + } > + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); > + unlock_user(argptr, ifreq_data, 0); > + ret = get_errno(safe_dev_ethtool(fd, &host_ifreq)); > + break; > + default: > + case ETHT_RW: > + argptr = lock_user(VERIFY_READ, ifreq_data, target_size, 1); > + if (!argptr) { > + return -TARGET_EFAULT; > + } > + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); > + unlock_user(argptr, ifreq_data, 0); > + ret = get_errno(safe_dev_ethtool(fd, &host_ifreq)); > + if (!is_error(ret)) { > + argptr = lock_user(VERIFY_WRITE, ifreq_data, target_size, > 0); > + if (!argptr) { > + return -TARGET_EFAULT; > + } > + thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET); > + unlock_user(argptr, ifreq_data, target_size); > + } > + break; > + } > + break; > + default: > + qemu_log_mask(LOG_UNIMP, > + "Unsupported ethtool type: cmd=0x%04lx type=%d\n", > + (long)host_cmd, arg_type[0]); > + ret = -TARGET_ENOSYS; > + break; > + } > + return ret; > +} > diff --git a/linux-user/ethtool.h b/linux-user/ethtool.h > new file mode 100644 > index 0000000000..6942aef095 > --- /dev/null > +++ b/linux-user/ethtool.h > @@ -0,0 +1,20 @@ > +#ifndef ETHTOOL_H > +#define ETHTOOL_H > + > +#include <linux/if.h> > +#include "qemu.h" > + > +extern const StructEntry struct_ethtool_rxnfc_get_set_rxfh_def; > +extern const StructEntry struct_ethtool_sset_info_def; > +extern const StructEntry struct_ethtool_rxfh_def; > +extern const StructEntry struct_ethtool_link_settings_def; > +extern const StructEntry struct_ethtool_per_queue_op_def; > + > +/* > + * Takes the file descriptor and the buffer for temporarily storing data > read > + * from / to be written to guest memory. `buf_temp` must now contain the > host > + * representation of `struct ifreq`. > + */ > +abi_long dev_ethtool(int fd, uint8_t *buf_temp); > + > +#endif /* ETHTOOL_H */ > diff --git a/linux-user/ethtool_entries.h b/linux-user/ethtool_entries.h > new file mode 100644 > index 0000000000..14f4e80a21 > --- /dev/null > +++ b/linux-user/ethtool_entries.h > @@ -0,0 +1,107 @@ > + ETHTOOL(ETHTOOL_GSET, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_cmd))) > + ETHTOOL(ETHTOOL_SSET, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_cmd))) > + ETHTOOL(ETHTOOL_GDRVINFO, ETHT_R, > MK_PTR(MK_STRUCT(STRUCT_ethtool_drvinfo))) > + ETHTOOL(ETHTOOL_GREGS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_regs))) > + ETHTOOL(ETHTOOL_GWOL, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_wolinfo))) > + ETHTOOL(ETHTOOL_SWOL, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_wolinfo))) > + ETHTOOL(ETHTOOL_GMSGLVL, ETHT_R, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_SMSGLVL, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GEEE, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_eee))) > + ETHTOOL(ETHTOOL_SEEE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_eee))) > + ETHTOOL(ETHTOOL_NWAY_RST, 0, TYPE_NULL) > + ETHTOOL(ETHTOOL_GLINK, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GEEPROM, ETHT_RW, > MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom))) > + ETHTOOL(ETHTOOL_SEEPROM, ETHT_RW, > MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom))) > + ETHTOOL(ETHTOOL_GCOALESCE, ETHT_R, > MK_PTR(MK_STRUCT(STRUCT_ethtool_coalesce))) > + ETHTOOL(ETHTOOL_SCOALESCE, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_coalesce))) > + ETHTOOL(ETHTOOL_GRINGPARAM, ETHT_R, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_ringparam))) > + ETHTOOL(ETHTOOL_SRINGPARAM, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_ringparam))) > + ETHTOOL(ETHTOOL_GPAUSEPARAM, ETHT_R, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_pauseparam))) > + ETHTOOL(ETHTOOL_SPAUSEPARAM, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_pauseparam))) > + ETHTOOL(ETHTOOL_TEST, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_test))) > + ETHTOOL(ETHTOOL_GSTRINGS, ETHT_RW, > MK_PTR(MK_STRUCT(STRUCT_ethtool_gstrings))) > + ETHTOOL(ETHTOOL_PHYS_ID, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GSTATS, ETHT_RW, > MK_PTR(MK_STRUCT(STRUCT_ethtool_stats))) > + ETHTOOL(ETHTOOL_GPERMADDR, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_perm_addr))) > + ETHTOOL(ETHTOOL_GFLAGS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_SFLAGS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GPFLAGS, ETHT_R, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_SPFLAGS, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GRXFH, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_get_set_rxfh))) > + ETHTOOL(ETHTOOL_GRXRINGS, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) > + ETHTOOL(ETHTOOL_GRXCLSRLCNT, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rule_cnt))) > + ETHTOOL(ETHTOOL_GRXCLSRULE, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) > + ETHTOOL(ETHTOOL_GRXCLSRLALL, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rule_locs))) > + ETHTOOL(ETHTOOL_SRXFH, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_get_set_rxfh))) > + ETHTOOL(ETHTOOL_SRXCLSRLDEL, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) > + ETHTOOL(ETHTOOL_SRXCLSRLINS, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) > + ETHTOOL(ETHTOOL_FLASHDEV, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_flash))) > + ETHTOOL(ETHTOOL_RESET, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GSSET_INFO, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_sset_info))) > + ETHTOOL(ETHTOOL_GRXFHINDIR, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh_indir))) > + ETHTOOL(ETHTOOL_SRXFHINDIR, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh_indir))) > + ETHTOOL_SPECIAL(ETHTOOL_GRSSH, ETHT_RW, do_ethtool_get_rxfh, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh))) > + ETHTOOL(ETHTOOL_SRSSH, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh))) > + ETHTOOL(ETHTOOL_GFEATURES, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_gfeatures))) > + ETHTOOL(ETHTOOL_SFEATURES, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_sfeatures))) > + ETHTOOL(ETHTOOL_GTXCSUM, ETHT_R, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GRXCSUM, ETHT_R, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GSG, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GTSO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GGSO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GGRO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_STXCSUM, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_SRXCSUM, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_SSG, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_STSO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_SGSO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_SGRO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) > + ETHTOOL(ETHTOOL_GCHANNELS, ETHT_R, > MK_PTR(MK_STRUCT(STRUCT_ethtool_channels))) > + ETHTOOL(ETHTOOL_SCHANNELS, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_channels))) > + ETHTOOL(ETHTOOL_SET_DUMP, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump_no_data))) > + ETHTOOL(ETHTOOL_GET_DUMP_FLAG, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump_no_data))) > + ETHTOOL(ETHTOOL_GET_DUMP_DATA, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump))) > + ETHTOOL(ETHTOOL_GET_TS_INFO, ETHT_R, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_ts_info))) > + ETHTOOL(ETHTOOL_GMODULEINFO, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_modinfo))) > + ETHTOOL(ETHTOOL_GMODULEEEPROM, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom))) > + ETHTOOL(ETHTOOL_GTUNABLE, ETHT_RW, > MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) > + ETHTOOL(ETHTOOL_STUNABLE, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) > + ETHTOOL(ETHTOOL_GPHYSTATS, ETHT_RW, > MK_PTR(MK_STRUCT(STRUCT_ethtool_stats))) > + ETHTOOL(ETHTOOL_PERQUEUE, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_per_queue_op))) > + ETHTOOL(ETHTOOL_GLINKSETTINGS, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_link_settings))) > + ETHTOOL(ETHTOOL_SLINKSETTINGS, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_link_settings))) > + ETHTOOL(ETHTOOL_PHY_GTUNABLE, ETHT_RW, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) > + ETHTOOL(ETHTOOL_PHY_STUNABLE, ETHT_W, > + MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) > + ETHTOOL(ETHTOOL_GFECPARAM, ETHT_R, > MK_PTR(MK_STRUCT(STRUCT_ethtool_fecparam))) > + ETHTOOL(ETHTOOL_GFECPARAM, ETHT_W, > MK_PTR(MK_STRUCT(STRUCT_ethtool_fecparam))) > diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h > index 0713ae1311..fd6ac963ec 100644 > --- a/linux-user/ioctls.h > +++ b/linux-user/ioctls.h > @@ -238,6 +238,8 @@ > IOCTL(SIOCSIFHWADDR, IOC_W, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq))) > IOCTL(SIOCGIFTXQLEN, IOC_W | IOC_R, > MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq))) > IOCTL(SIOCSIFTXQLEN, IOC_W, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq))) > + IOCTL_SPECIAL(SIOCETHTOOL, IOC_W | IOC_R, do_ioctl_ethtool, > + MK_PTR(MK_STRUCT(STRUCT_ptr_ifreq))) > IOCTL(SIOCGIFMETRIC, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) > IOCTL(SIOCSIFMETRIC, IOC_W, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) > IOCTL(SIOCGIFMTU, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) > diff --git a/linux-user/qemu.h b/linux-user/qemu.h > index 5c964389c1..43f00681f8 100644 > --- a/linux-user/qemu.h > +++ b/linux-user/qemu.h > @@ -231,6 +231,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long > arg1, > abi_long arg2, abi_long arg3, abi_long arg4, > abi_long arg5, abi_long arg6, abi_long arg7, > abi_long arg8); > +abi_long get_errno(abi_long ret); > extern __thread CPUState *thread_cpu; > void cpu_loop(CPUArchState *env); > const char *target_strerror(int err); > diff --git a/linux-user/syscall.c b/linux-user/syscall.c > index bfc4219104..41fea53716 100644 > --- a/linux-user/syscall.c > +++ b/linux-user/syscall.c > @@ -127,6 +127,7 @@ > #include "qapi/error.h" > #include "fd-trans.h" > #include "tcg/tcg.h" > +#include "ethtool.h" > > #ifndef CLONE_IO > #define CLONE_IO 0x80000000 /* Clone io context */ > @@ -676,7 +677,7 @@ static inline int target_to_host_errno(int err) > return err; > } > > -static inline abi_long get_errno(abi_long ret) > +abi_long get_errno(abi_long ret) > { > if (ret == -1) > return -host_to_target_errno(errno); > @@ -4732,16 +4733,6 @@ static abi_long do_ipc(CPUArchState *cpu_env, > #endif > > /* kernel structure types definitions */ > - > -#define STRUCT(name, ...) STRUCT_ ## name, > -#define STRUCT_SPECIAL(name) STRUCT_ ## name, > -enum { > -#include "syscall_types.h" > -STRUCT_MAX > -}; > -#undef STRUCT > -#undef STRUCT_SPECIAL > - > #define STRUCT(name, ...) static const argtype struct_ ## name ## _def[] > = { __VA_ARGS__, TYPE_NULL }; > #define STRUCT_SPECIAL(name) > #include "syscall_types.h" > @@ -4839,6 +4830,29 @@ static abi_long do_ioctl_fs_ioc_fiemap(const > IOCTLEntry *ie, uint8_t *buf_temp, > } > #endif > > +static abi_long do_ioctl_ethtool(const IOCTLEntry *ie, uint8_t *buf_temp, > + int fd, int cmd, abi_long arg) > +{ > + const argtype *arg_type = ie->arg_type; > + int target_size; > + void *argptr; > + > + assert(arg_type[0] == TYPE_PTR); > + assert(ie->access == IOC_RW); > + > + arg_type++; > + target_size = thunk_type_size(arg_type, 0); > + > + argptr = lock_user(VERIFY_READ, arg, target_size, 1); > + if (!argptr) { > + return -TARGET_EFAULT; > + } > + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); > + unlock_user(argptr, arg, target_size); > + > + return dev_ethtool(fd, buf_temp); > +} > + > static abi_long do_ioctl_ifconf(const IOCTLEntry *ie, uint8_t *buf_temp, > int fd, int cmd, abi_long arg) > { > diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h > index 70df1a94fb..e25a8cbcc8 100644 > --- a/linux-user/syscall_defs.h > +++ b/linux-user/syscall_defs.h > @@ -866,6 +866,8 @@ struct target_rtc_pll_info { > #define TARGET_SIOCGIFTXQLEN 0x8942 /* Get the tx queue > length */ > #define TARGET_SIOCSIFTXQLEN 0x8943 /* Set the tx queue > length */ > > +#define TARGET_SIOCETHTOOL 0x8946 /* Ethtool interface > */ > + > /* ARP cache control calls. */ > #define TARGET_OLD_SIOCDARP 0x8950 /* old delete ARP table > entry */ > #define TARGET_OLD_SIOCGARP 0x8951 /* old get ARP table > entry */ > @@ -2776,4 +2778,14 @@ struct target_statx { > /* 0x100 */ > }; > > +/* kernel structure types definitions */ > +#define STRUCT(name, ...) STRUCT_ ## name, > +#define STRUCT_SPECIAL(name) STRUCT_ ## name, > +enum { > +#include "syscall_types.h" > +STRUCT_MAX > +}; > +#undef STRUCT > +#undef STRUCT_SPECIAL > + > #endif > diff --git a/linux-user/syscall_types.h b/linux-user/syscall_types.h > index 3f1f033464..559924c752 100644 > --- a/linux-user/syscall_types.h > +++ b/linux-user/syscall_types.h > @@ -1,3 +1,4 @@ > + > STRUCT_SPECIAL(termios) > > STRUCT(winsize, > @@ -464,3 +465,282 @@ STRUCT(usbdevfs_disconnect_claim, > TYPE_INT, /* flags */ > MK_ARRAY(TYPE_CHAR, USBDEVFS_MAXDRIVERNAME + 1)) /* driver */ > #endif /* CONFIG_USBFS */ > + > +/* ethtool ioctls */ > +STRUCT(ethtool_cmd, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* supported */ > + TYPE_INT, /* advertising */ > + TYPE_SHORT, /* speed */ > + TYPE_CHAR, /* duplex */ > + TYPE_CHAR, /* port */ > + TYPE_CHAR, /* phy_address */ > + TYPE_CHAR, /* transceiver */ > + TYPE_CHAR, /* autoneg */ > + TYPE_CHAR, /* mdio_support */ > + TYPE_INT, /* maxtxpkt */ > + TYPE_INT, /* maxrxpkt */ > + TYPE_SHORT, /* speed_hi */ > + TYPE_CHAR, /* eth_tp_mdix */ > + TYPE_CHAR, /* eth_tp_mdix_ctrl */ > + TYPE_INT, /* lp_advertising */ > + MK_ARRAY(TYPE_INT, 2)) /* reserved */ > + > +STRUCT(ethtool_drvinfo, > + TYPE_INT, /* cmd */ > + MK_ARRAY(TYPE_CHAR, 32), /* driver */ > + MK_ARRAY(TYPE_CHAR, 32), /* version */ > + MK_ARRAY(TYPE_CHAR, 32), /* fw_version[ETHTOOL_FWVERS_LEN] */ > + MK_ARRAY(TYPE_CHAR, 32), /* bus_info[ETHTOOL_BUSINFO_LEN] */ > + MK_ARRAY(TYPE_CHAR, 32), /* erom_version[ETHTOOL_EROMVERS_LEN] */ > + MK_ARRAY(TYPE_CHAR, 12), /* reserved2 */ > + TYPE_INT, /* n_priv_flags */ > + TYPE_INT, /* n_stats */ > + TYPE_INT, /* testinfo_len */ > + TYPE_INT, /* eedump_len */ > + TYPE_INT) /* regdump_len */ > + > +STRUCT(ethtool_regs, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* version */ > + TYPE_INT, /* len */ > + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */ > + > +STRUCT(ethtool_wolinfo, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* supported */ > + TYPE_INT, /* wolopts */ > + MK_ARRAY(TYPE_CHAR, 6)) /* sopass[SOPASS_MAX] */ > + > +STRUCT(ethtool_value, > + TYPE_INT, /* cmd */ > + TYPE_INT) /* data */ > + > +STRUCT(ethtool_eee, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* supported */ > + TYPE_INT, /* advertised */ > + TYPE_INT, /* lp_advertised */ > + TYPE_INT, /* eee_active */ > + TYPE_INT, /* eee_enabled */ > + TYPE_INT, /* tx_lpi_enabled */ > + TYPE_INT, /* tx_lpi_timer */ > + MK_ARRAY(TYPE_INT, 2)) /* reserved */ > + > +STRUCT(ethtool_eeprom, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* magic */ > + TYPE_INT, /* offset */ > + TYPE_INT, /* len */ > + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 3)) /* data[0]: len */ > + > +STRUCT(ethtool_coalesce, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* rx_coalesce_usecs */ > + TYPE_INT, /* rx_max_coalesced_frames */ > + TYPE_INT, /* rx_coalesce_usecs_irq */ > + TYPE_INT, /* rx_max_coalesced_frames_irq */ > + TYPE_INT, /* tx_coalesce_usecs */ > + TYPE_INT, /* tx_max_coalesced_frames */ > + TYPE_INT, /* tx_coalesce_usecs_irq */ > + TYPE_INT, /* tx_max_coalesced_frames_irq */ > + TYPE_INT, /* stats_block_coalesce_usecs */ > + TYPE_INT, /* use_adaptive_rx_coalesce */ > + TYPE_INT, /* use_adaptive_tx_coalesce */ > + TYPE_INT, /* pkt_rate_low */ > + TYPE_INT, /* rx_coalesce_usecs_low */ > + TYPE_INT, /* rx_max_coalesced_frames_low */ > + TYPE_INT, /* tx_coalesce_usecs_low */ > + TYPE_INT, /* tx_max_coalesced_frames_low */ > + TYPE_INT, /* pkt_rate_high */ > + TYPE_INT, /* rx_coalesce_usecs_high */ > + TYPE_INT, /* rx_max_coalesced_frames_high */ > + TYPE_INT, /* tx_coalesce_usecs_high */ > + TYPE_INT, /* tx_max_coalesced_frames_high */ > + TYPE_INT) /* rate_sample_interval */ > + > +STRUCT(ethtool_ringparam, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* rx_max_pending */ > + TYPE_INT, /* rx_mini_max_pending */ > + TYPE_INT, /* rx_jumbo_max_pending */ > + TYPE_INT, /* tx_max_pending */ > + TYPE_INT, /* rx_pending */ > + TYPE_INT, /* rx_mini_pending */ > + TYPE_INT, /* rx_jumbo_pending */ > + TYPE_INT) /* tx_pending */ > + > +STRUCT(ethtool_pauseparam, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* autoneg */ > + TYPE_INT, /* rx_pause */ > + TYPE_INT) /* tx_pause */ > + > +STRUCT(ethtool_test, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* flags */ > + TYPE_INT, /* reserved */ > + TYPE_INT, /* len */ > + MK_FLEXIBLE_ARRAY(TYPE_LONGLONG, 3)) /* data[0]: len */ > + > +STRUCT(ethtool_gstrings, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* string_set */ > + TYPE_INT, /* len */ > + MK_FLEXIBLE_ARRAY(MK_ARRAY(TYPE_CHAR, 32), 2)) > + /* data[0]: len * ETH_GSTRING_LEN */ > + > +STRUCT(ethtool_stats, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* n_stats */ > + MK_FLEXIBLE_ARRAY(TYPE_LONGLONG, 1)) /* data[0]: n_stats */ > + > +STRUCT(ethtool_perm_addr, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* size */ > + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 1)) /* data[0]: size */ > + > +STRUCT(ethtool_flow_ext, > + MK_ARRAY(TYPE_CHAR, 2), /* padding */ > + MK_ARRAY(TYPE_CHAR, 6), /* h_dest[ETH_ALEN] */ > + MK_ARRAY(TYPE_CHAR, 2), /* __be16 vlan_etype */ > + MK_ARRAY(TYPE_CHAR, 2), /* __be16 vlan_tci */ > + MK_ARRAY(TYPE_CHAR, 8)) /* __be32 data[2] */ > + > +/* > + * Union ethtool_flow_union contains alternatives that are either struct > that > + * only uses __be* types or char/__u8, or "__u8 hdata[52]". We can treat > it as > + * byte array in all cases. > + */ > +STRUCT(ethtool_rx_flow_spec, > + TYPE_INT, /* flow_type */ > + MK_ARRAY(TYPE_CHAR, 52), /* union ethtool_flow_union > h_u */ > + MK_STRUCT(STRUCT_ethtool_flow_ext), /* h_ext */ > + MK_ARRAY(TYPE_CHAR, 52), /* union ethtool_flow_union > m_u */ > + MK_STRUCT(STRUCT_ethtool_flow_ext), /* m_ext */ > + TYPE_LONGLONG, /* ring_cookie */ > + TYPE_INT) /* location */ > + > +STRUCT(ethtool_rxnfc_rss_context, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* flow_type */ > + TYPE_LONGLONG, /* data */ > + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ > + TYPE_INT) /* rss_context */ > + > +STRUCT(ethtool_rxnfc_rule_cnt, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* flow_type */ > + TYPE_LONGLONG, /* data */ > + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ > + TYPE_INT) /* rss_cnt */ > + > +STRUCT(ethtool_rxnfc_rule_locs, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* flow_type */ > + TYPE_LONGLONG, /* data */ > + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ > + TYPE_INT, /* rss_cnt */ > + MK_FLEXIBLE_ARRAY(TYPE_INT, 4)) /* rule_locs[0]: rss_cnt */ > + > +/* > + * For ETHTOOL_{G,S}RXFH, originally only the first three fields are > defined, > + * but with certain options, more fields are used. > + */ > +STRUCT_SPECIAL(ethtool_rxnfc_get_set_rxfh) > + > +STRUCT(ethtool_flash, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* region */ > + MK_ARRAY(TYPE_CHAR, 128)) /* data[ETHTOOL_FLASH_MAX_FILENAME] */ > + > +STRUCT_SPECIAL(ethtool_sset_info) > + > +STRUCT(ethtool_rxfh_indir, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* size */ > + MK_FLEXIBLE_ARRAY(TYPE_INT, 1)) /* ring_index[0]: size */ > + > +STRUCT_SPECIAL(ethtool_rxfh) > + > +STRUCT(ethtool_get_features_block, > + TYPE_INT, /* available */ > + TYPE_INT, /* requested */ > + TYPE_INT, /* active */ > + TYPE_INT) /* never_changed */ > + > +STRUCT(ethtool_gfeatures, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* size */ > + MK_FLEXIBLE_ARRAY(MK_STRUCT(STRUCT_ethtool_get_features_block), 1)) > + /* features[0]: size */ > + > +STRUCT(ethtool_set_features_block, > + TYPE_INT, /* valid */ > + TYPE_INT) /* requested */ > + > +STRUCT(ethtool_sfeatures, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* size */ > + MK_FLEXIBLE_ARRAY(MK_STRUCT(STRUCT_ethtool_set_features_block), 1)) > + /* features[0]: size */ > + > +STRUCT(ethtool_channels, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* max_rx */ > + TYPE_INT, /* max_tx */ > + TYPE_INT, /* max_other */ > + TYPE_INT, /* max_combined */ > + TYPE_INT, /* rx_count */ > + TYPE_INT, /* tx_count */ > + TYPE_INT, /* other_count */ > + TYPE_INT) /* combined_count */ > + > +/* > + * For ETHTOOL_SET_DUMP and ETHTOOL_GET_DUMP_FLAG, the flexible array > `data` is > + * not used. > + */ > +STRUCT(ethtool_dump_no_data, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* version */ > + TYPE_INT, /* flag */ > + TYPE_INT) /* len */ > + > +STRUCT(ethtool_dump, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* version */ > + TYPE_INT, /* flag */ > + TYPE_INT, /* len */ > + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 3)) /* data[0]: len */ > + > +STRUCT(ethtool_ts_info, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* so_timestamping */ > + TYPE_INT, /* phc_index */ > + TYPE_INT, /* tx_types */ > + MK_ARRAY(TYPE_INT, 3), /* tx_reserved */ > + TYPE_INT, /* rx_filters */ > + MK_ARRAY(TYPE_INT, 3)) /* rx_reserved */ > + > +STRUCT(ethtool_modinfo, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* type */ > + TYPE_INT, /* eeprom_len */ > + MK_ARRAY(TYPE_INT, 8)) /* reserved */ > + > +STRUCT(ethtool_tunable, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* id */ > + TYPE_INT, /* type_id */ > + TYPE_INT, /* len */ > + MK_FLEXIBLE_ARRAY(TYPE_PTRVOID, 3)) /* data[0]: len */ > + > +STRUCT_SPECIAL(ethtool_link_settings) > + > +STRUCT(ethtool_fecparam, > + TYPE_INT, /* cmd */ > + TYPE_INT, /* active_fec */ > + TYPE_INT, /* fec */ > + TYPE_INT) /* reserved */ > + > +STRUCT_SPECIAL(ethtool_per_queue_op) > diff --git a/tests/tcg/multiarch/ethtool.c b/tests/tcg/multiarch/ethtool.c > new file mode 100644 > index 0000000000..dcb10230e0 > --- /dev/null > +++ b/tests/tcg/multiarch/ethtool.c > @@ -0,0 +1,423 @@ > +#include <asm-generic/errno.h> > +#include <assert.h> > +#include <errno.h> > +#include <inttypes.h> > +#include <linux/ethtool.h> > +#include <linux/if.h> > +#include <linux/sockios.h> > +#include <netinet/in.h> > +#include <stdbool.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <sys/ioctl.h> > +#include <sys/socket.h> > + > +const int number_of_entries_to_print = 10; > +const uint32_t protected_memory_pattern[] = { > + 0xdeadc0de, 0xb0bb1e, 0xfacade, 0xfeeb1e }; > + > +static void fail_with(const char *action, const char *cmd_name, int cmd, > + int err) > +{ > + if (errno == EOPNOTSUPP) { > + printf("Unsupported operation: %s; errno = %d: %s.\n" > + "TEST SKIPPED (%s = 0x%x).\n", > + action, err, strerror(err), cmd_name, cmd); > + return; > + } > + if (err) { > + fprintf(stderr, > + "Failed to %s (%s = 0x%x): errno = %d: %s\n", > + action, cmd_name, cmd, err, strerror(err)); > + } else { > + fprintf(stderr, > + "Failed to %s (%s = 0x%x): no errno\n", > + action, cmd_name, cmd); > + } > + exit(err); > +} > +#define FAIL(action, cmd) fail_with(action, #cmd, cmd, errno) > + > +/* > + * `calloc_protected` and `protected_memory_changed` can be used to > verify that > + * a system call does not write pass intended memory boundary. > + * > + * `ptr = calloc_protected(n)` will allocate extra memory after `n` bytes > and > + * populate it with a memory pattern. The first `n` bytes are still > guaranteed > + * to be zeroed out like `calloc(1, n)`. `protected_memory_changed(ptr, > n)` > + * takes the pointer and the original size `n` and checks that the memory > + * pattern is intact. > + */ > +uint8_t *calloc_protected(size_t struct_size) > +{ > + uint8_t *buf = (uint8_t *) calloc( > + 1, > + struct_size + sizeof(protected_memory_pattern)); > + memcpy(buf + struct_size, protected_memory_pattern, > + sizeof(protected_memory_pattern)); > + return buf; > +} > + > +bool protected_memory_changed(const uint8_t *ptr, size_t struct_size) > +{ > + return memcmp(ptr + struct_size, protected_memory_pattern, > + sizeof(protected_memory_pattern)) != 0; > +} > + > +void print_entries(const char *fmt, int len, uint32_t *entries) > +{ > + int i; > + for (i = 0; i < len && i < number_of_entries_to_print; ++i) { > + printf(fmt, entries[i]); > + } > + if (len > number_of_entries_to_print) { > + printf(" (%d more omitted)", len - number_of_entries_to_print); > + } > +} > + > +void basic_test(int socketfd, struct ifreq ifr) > +{ > + struct ethtool_drvinfo drvinfo; > + drvinfo.cmd = ETHTOOL_GDRVINFO; > + ifr.ifr_data = (void *)&drvinfo; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get driver info", ETHTOOL_GDRVINFO); > + return; > + } > + printf("Driver: %s (version %s)\n", drvinfo.driver, drvinfo.version); > +} > + > +/* Test flexible array. */ > +void test_get_stats(int socketfd, struct ifreq ifr, int n_stats) > +{ > + int i; > + struct ethtool_stats *stats = (struct ethtool_stats *)calloc( > + 1, sizeof(*stats) + sizeof(stats->data[0]) * n_stats); > + stats->cmd = ETHTOOL_GSTATS; > + stats->n_stats = n_stats; > + ifr.ifr_data = (void *)stats; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get statastics", ETHTOOL_GSTATS); > + free(stats); > + return; > + } > + if (stats->n_stats != n_stats) { > + FAIL("get consistent number of statistics", ETHTOOL_GSTATS); > + } > + for (i = 0; i < stats->n_stats && i < number_of_entries_to_print; > ++i) { > + printf("stats[%d] = %llu\n", i, (unsigned long > long)stats->data[i]); > + } > + if (stats->n_stats > number_of_entries_to_print) { > + printf("(%d more omitted)\n", > + stats->n_stats - number_of_entries_to_print); > + } > + free(stats); > +} > + > +/* Test flexible array with char array as elements. */ > +void test_get_strings(int socketfd, struct ifreq ifr, int n_stats) > +{ > + int i; > + struct ethtool_gstrings *gstrings = > + (struct ethtool_gstrings *)calloc( > + 1, sizeof(*gstrings) + ETH_GSTRING_LEN * n_stats); > + gstrings->cmd = ETHTOOL_GSTRINGS; > + gstrings->string_set = ETH_SS_STATS; > + gstrings->len = n_stats; > + ifr.ifr_data = (void *)gstrings; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get string set", ETHTOOL_GSTRINGS); > + free(gstrings); > + return; > + } > + if (gstrings->len != n_stats) { > + FAIL("get consistent number of statistics", ETHTOOL_GSTRINGS); > + } > + for (i = 0; i < gstrings->len && i < number_of_entries_to_print; ++i) > { > + printf("stat_names[%d] = %.*s\n", > + i, ETH_GSTRING_LEN, gstrings->data + i * ETH_GSTRING_LEN); > + } > + if (gstrings->len > number_of_entries_to_print) { > + printf("(%d more omitted)\n", > + gstrings->len - number_of_entries_to_print); > + } > + free(gstrings); > +} > + > +/* > + * Testing manual implementation of converting `struct > ethtool_sset_info`, also > + * info for subsequent tests. > + */ > +int test_get_sset_info(int socketfd, struct ifreq ifr) > +{ > + const int n_sset = 2; > + int n_stats; > + struct ethtool_sset_info *sset_info = > + (struct ethtool_sset_info *)calloc( > + 1, sizeof(*sset_info) + sizeof(sset_info->data[0]) * n_sset); > + sset_info->cmd = ETHTOOL_GSSET_INFO; > + sset_info->sset_mask = 1 << ETH_SS_TEST | 1 << ETH_SS_STATS; > + assert(__builtin_popcount(sset_info->sset_mask) == n_sset); > + ifr.ifr_data = (void *)sset_info; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + fail_with("get string set info", "ETHTOOL_GSSET_INFO", > + ETHTOOL_GSSET_INFO, errno); > + free(sset_info); > + return 0; > + } > + if ((sset_info->sset_mask & (1 << ETH_SS_STATS)) == 0) { > + puts("No stats string set info, SKIPPING dependent tests"); > + free(sset_info); > + return 0; > + } > + n_stats = (sset_info->sset_mask & (1 << ETH_SS_TEST)) ? > + sset_info->data[1] : > + sset_info->data[0]; > + printf("n_stats = %d\n", n_stats); > + free(sset_info); > + return n_stats; > +} > + > +/* > + * Test manual implementation of converting `struct ethtool_rxnfc`, > focusing on > + * the case where only the first three fields are present. (The original > struct > + * definition.) > + */ > +void test_get_rxfh(int socketfd, struct ifreq ifr) > +{ > + struct ethtool_rxnfc *rxnfc; > + const int rxnfc_first_three_field_size = > + sizeof(rxnfc->cmd) + sizeof(rxnfc->flow_type) + > sizeof(rxnfc->data); > + rxnfc = (struct ethtool_rxnfc *)calloc_protected( > + rxnfc_first_three_field_size); > + rxnfc->cmd = ETHTOOL_GRXFH; > + rxnfc->flow_type = TCP_V4_FLOW; > + ifr.ifr_data = (void *)rxnfc; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get RX flow classification rules", ETHTOOL_GRXFH); > + free(rxnfc); > + return; > + } > + if (protected_memory_changed((const uint8_t *)rxnfc, > + rxnfc_first_three_field_size)) { > + FAIL("preserve memory after the first three fields", > ETHTOOL_GRXFH); > + } > + printf("Flow hash bitmask (flow_type = TCP v4): 0x%llx\n", > + (unsigned long long)rxnfc->data); > + free(rxnfc); > +} > + > +/* Test manual implementation of converting `struct > ethtool_link_settings`. */ > +void test_get_link_settings(int socketfd, struct ifreq ifr) > +{ > + int link_mode_masks_nwords; > + struct ethtool_link_settings *link_settings_header = > + (struct ethtool_link_settings *) calloc_protected( > + sizeof(*link_settings_header)); > + link_settings_header->cmd = ETHTOOL_GLINKSETTINGS; > + link_settings_header->link_mode_masks_nwords = 0; > + ifr.ifr_data = (void *)link_settings_header; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get link settings mask sizes", ETHTOOL_GLINKSETTINGS); > + free(link_settings_header); > + return; > + } > + if (protected_memory_changed((const uint8_t *)link_settings_header, > + sizeof(*link_settings_header))) { > + FAIL("preserve link_mode_masks", ETHTOOL_GLINKSETTINGS); > + } > + if (link_settings_header->link_mode_masks_nwords >= 0) { > + FAIL("complete handshake", ETHTOOL_GLINKSETTINGS); > + } > + link_mode_masks_nwords = > -link_settings_header->link_mode_masks_nwords; > + > + struct ethtool_link_settings *link_settings = > + (struct ethtool_link_settings *)calloc( > + 1, > + sizeof(*link_settings) + > + sizeof(link_settings_header->link_mode_masks[0]) * > + link_mode_masks_nwords * 3); > + link_settings->cmd = ETHTOOL_GLINKSETTINGS; > + link_settings->link_mode_masks_nwords = link_mode_masks_nwords; > + ifr.ifr_data = (void *)link_settings; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get link settings", ETHTOOL_GLINKSETTINGS); > + free(link_settings_header); > + free(link_settings); > + return; > + } > + if (link_settings->link_mode_masks_nwords != link_mode_masks_nwords) { > + FAIL("have consistent number of mode masks", > ETHTOOL_GLINKSETTINGS); > + } > + > + printf("Link speed: %d MB\n", link_settings->speed); > + printf("Number of link mode masks: %d\n", > + link_settings->link_mode_masks_nwords); > + if (link_settings->link_mode_masks_nwords > 0) { > + printf("Supported bitmap:"); > + print_entries(" 0x%08x", > + link_settings->link_mode_masks_nwords, > + link_settings->link_mode_masks); > + putchar('\n'); > + > + printf("Advertising bitmap:"); > + print_entries(" 0x%08x", > + link_settings->link_mode_masks_nwords, > + link_settings->link_mode_masks + > + link_settings->link_mode_masks_nwords); > + putchar('\n'); > + > + printf("Lp advertising bitmap:"); > + print_entries(" 0x%08x", > + link_settings->link_mode_masks_nwords, > + link_settings->link_mode_masks + > + 2 * link_settings->link_mode_masks_nwords); > + putchar('\n'); > + } > + > + free(link_settings_header); > + free(link_settings); > +} > + > +/* Test manual implementation of converting `struct > ethtool_per_queue_op`. */ > +void test_perqueue(int socketfd, struct ifreq ifr) > +{ > + const int n_queue = 2; > + int i; > + struct ethtool_per_queue_op *per_queue_op = > + (struct ethtool_per_queue_op *)calloc( > + 1, > + sizeof(*per_queue_op) + sizeof(struct ethtool_coalesce) * > n_queue); > + per_queue_op->cmd = ETHTOOL_PERQUEUE; > + per_queue_op->sub_command = ETHTOOL_GCOALESCE; > + per_queue_op->queue_mask[0] = 0x3; > + ifr.ifr_data = (void *)per_queue_op; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get coalesce per queue", ETHTOOL_PERQUEUE); > + free(per_queue_op); > + return; > + } > + for (i = 0; i < n_queue; ++i) { > + struct ethtool_coalesce *coalesce = (struct ethtool_coalesce *)( > + per_queue_op->data + sizeof(*coalesce) * i); > + if (coalesce->cmd != ETHTOOL_GCOALESCE) { > + fprintf(stderr, > + "ETHTOOL_PERQUEUE (%d) sub_command ETHTOOL_GCOALESCE > (%d) " > + "fails to set entry %d's cmd to ETHTOOL_GCOALESCE, > got %d " > + "instead\n", > + ETHTOOL_PERQUEUE, ETHTOOL_GCOALESCE, i, > + coalesce->cmd); > + exit(-1); > + } > + printf("rx_coalesce_usecs[%d] = %u\nrx_max_coalesced_frames[%d] = > %u\n", > + i, coalesce->rx_coalesce_usecs, > + i, coalesce->rx_max_coalesced_frames); > + } > + > + free(per_queue_op); > +} > + > +/* Test manual implementation of ETHTOOL_GRSSH. */ > +void test_get_rssh(int socketfd, struct ifreq ifr) > +{ > + int i; > + struct ethtool_rxfh *rxfh_header = > + (struct ethtool_rxfh *)calloc_protected(sizeof(*rxfh_header)); > + rxfh_header->cmd = ETHTOOL_GRSSH; > + ifr.ifr_data = (void *)rxfh_header; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get RX flow hash indir and hash key size", ETHTOOL_GRSSH); > + free(rxfh_header); > + return; > + } > + if (protected_memory_changed((const uint8_t *)rxfh_header, > + sizeof(*rxfh_header))) { > + FAIL("preserve rss_config", ETHTOOL_GRSSH); > + } > + printf("RX flow hash indir size = %d\nRX flow hash key size = %d\n", > + rxfh_header->indir_size, rxfh_header->key_size); > + > + struct ethtool_rxfh *rxfh = (struct ethtool_rxfh *)calloc( > + 1, > + sizeof(*rxfh) + 4 * rxfh_header->indir_size + > rxfh_header->key_size); > + *rxfh = *rxfh_header; > + ifr.ifr_data = (void *)rxfh; > + if (ioctl(socketfd, SIOCETHTOOL, &ifr) == -1) { > + FAIL("get RX flow hash indir and hash key", ETHTOOL_GRSSH); > + free(rxfh_header); > + free(rxfh); > + return; > + } > + > + if (rxfh->indir_size == 0) { > + printf("No RX flow hash indir\n"); > + } else { > + printf("RX flow hash indir:"); > + print_entries(" 0x%08x", rxfh->indir_size, rxfh->rss_config); > + putchar('\n'); > + } > + > + if (rxfh->key_size == 0) { > + printf("No RX flow hash key\n"); > + } else { > + char *key = (char *)(rxfh->rss_config + rxfh->indir_size); > + printf("RX flow hash key:"); > + for (i = 0; i < rxfh->key_size; ++i) { > + if (i % 2 == 0) { > + putchar(' '); > + } > + printf("%02hhx", key[i]); > + } > + putchar('\n'); > + } > + free(rxfh_header); > + free(rxfh); > +} > + > +int main(int argc, char **argv) > +{ > + int socketfd, n_stats, i; > + struct ifreq ifr; > + > + socketfd = socket(AF_INET, SOCK_DGRAM, 0); > + if (socketfd == -1) { > + int err = errno; > + fprintf(stderr, > + "Failed to open socket: errno = %d: %s\n", > + err, strerror(err)); > + return err; > + } > + > + for (i = 1;; ++i) { > + ifr.ifr_ifindex = i; > + if (ioctl(socketfd, SIOCGIFNAME, &ifr) == -1) { > + puts("Could not find a non-loopback interface, SKIPPING"); > + return 0; > + } > + if (strncmp(ifr.ifr_name, "lo", IFNAMSIZ) != 0) { > + break; > + } > + } > + printf("Interface index: %d\nInterface name: %.*s\n", > + ifr.ifr_ifindex, IFNAMSIZ, ifr.ifr_name); > + > + basic_test(socketfd, ifr); > + > + n_stats = test_get_sset_info(socketfd, ifr); > + if (n_stats > 0) { > + /* Testing lexible arrays. */ > + test_get_stats(socketfd, ifr, n_stats); > + test_get_strings(socketfd, ifr, n_stats); > + } > + > + /* Testing manual implementations of structure convertions. */ > + test_get_rxfh(socketfd, ifr); > + test_get_link_settings(socketfd, ifr); > + test_perqueue(socketfd, ifr); > + > + /* Testing manual implementations of operations. */ > + test_get_rssh(socketfd, ifr); > + > + return 0; > +} > -- > 2.28.0.220.ged08abb693-goog > > [-- Attachment #1.2: Type: text/html, Size: 84766 bytes --] [-- Attachment #2: S/MIME Cryptographic Signature --] [-- Type: application/pkcs7-signature, Size: 3990 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 0/8] fcntl, sockopt, and ioctl options 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng ` (7 preceding siblings ...) 2020-08-11 7:09 ` [PATCH v2 8/8] linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng @ 2020-12-18 8:24 ` Laurent Vivier 8 siblings, 0 replies; 22+ messages in thread From: Laurent Vivier @ 2020-12-18 8:24 UTC (permalink / raw) To: Shu-Chun Weng, qemu-devel Hi Shu-Chun, I'm sorry for the delay. Your series doesn't apply anymore. Could you rebase it and send a new version. I'll merge the 4 first patches. Thanks, Laurent Le 11/08/2020 à 09:09, Shu-Chun Weng a écrit : > Hi Laurent, > > This is a series of 8 patches in 4 groups, putting into a single thread for > easier tracking. > > [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls > An incidental follow up on > https://lists.nongnu.org/archive/html/qemu-devel/2019-09/msg01925.html > > [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option > [PATCH v2 3/8] linux-user: add missing IPv6 get/setsockopt option > [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() > Updated https://lists.nongnu.org/archive/html/qemu-devel/2019-09/msg01317.html > to consistently add them in get/setsockopt > > [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW > [PATCH v2 6/8] linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING > Updated https://lists.nongnu.org/archive/html/qemu-devel/2019-09/msg01319.html > to only use TARGET_SO_*_OLD/NEW > > [PATCH v2 7/8] thunk: supports flexible arrays > [PATCH v2 8/8] linux-user: Add support for SIOCETHTOOL ioctl > Updated https://lists.nongnu.org/archive/html/qemu-devel/2019-08/msg05090.html > > v1 -> v2: > Address comments on the first 5 (was 3) patches. > Fix style problems. > > Shu-Chun Weng (8): > linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls > linux-user: add missing UDP get/setsockopt option > linux-user: add missing IPv6 get/setsockopt option > linux-user: Add IPv6 options to do_print_sockopt() > linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW > linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING > thunk: supports flexible arrays > linux-user: Add support for SIOCETHTOOL ioctl > > include/exec/user/thunk.h | 24 + > linux-user/Makefile.objs | 3 +- > linux-user/alpha/sockbits.h | 21 +- > linux-user/ethtool.c | 840 +++++++++++++++++++++++++ > linux-user/ethtool.h | 20 + > linux-user/ethtool_entries.h | 107 ++++ > linux-user/generic/sockbits.h | 17 +- > linux-user/hppa/sockbits.h | 20 +- > linux-user/ioctls.h | 2 + > linux-user/mips/sockbits.h | 16 +- > linux-user/qemu.h | 1 + > linux-user/sparc/sockbits.h | 21 +- > linux-user/strace.c | 188 +++++- > linux-user/syscall.c | 286 ++++++++- > linux-user/syscall_defs.h | 26 +- > linux-user/syscall_types.h | 280 +++++++++ > tests/tcg/multiarch/ethtool.c | 423 +++++++++++++ > tests/tcg/multiarch/socket_timestamp.c | 540 ++++++++++++++++ > thunk.c | 152 ++++- > 19 files changed, 2916 insertions(+), 71 deletions(-) > create mode 100644 linux-user/ethtool.c > create mode 100644 linux-user/ethtool.h > create mode 100644 linux-user/ethtool_entries.h > create mode 100644 tests/tcg/multiarch/ethtool.c > create mode 100644 tests/tcg/multiarch/socket_timestamp.c > ^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2020-12-18 8:26 UTC | newest] Thread overview: 22+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2020-08-11 7:09 [PATCH v2 0/8] fcntl, sockopt, and ioctl options Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 1/8] linux-user: Support F_ADD_SEALS and F_GET_SEALS fcntls Shu-Chun Weng 2020-08-11 14:09 ` Laurent Vivier 2020-08-11 7:09 ` [PATCH v2 2/8] linux-user: add missing UDP get/setsockopt option Shu-Chun Weng 2020-08-11 14:21 ` Laurent Vivier 2020-08-11 20:04 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 3/8] linux-user: add missing IPv6 " Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 4/8] linux-user: Add IPv6 options to do_print_sockopt() Shu-Chun Weng 2020-09-17 7:26 ` Shu-Chun Weng 2020-09-29 23:29 ` Laurent Vivier 2020-12-18 3:58 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 5/8] linux-user: Update SO_TIMESTAMP to SO_TIMESTAMP_OLD/NEW Shu-Chun Weng 2020-09-17 7:29 ` Shu-Chun Weng 2020-12-18 4:01 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 6/8] linux-user: setsockopt() SO_TIMESTAMPNS and SO_TIMESTAMPING Shu-Chun Weng 2020-12-18 4:02 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 7/8] thunk: supports flexible arrays Shu-Chun Weng 2020-08-11 21:39 ` Shu-Chun Weng 2020-12-18 4:03 ` Shu-Chun Weng 2020-08-11 7:09 ` [PATCH v2 8/8] linux-user: Add support for SIOCETHTOOL ioctl Shu-Chun Weng 2020-12-18 4:03 ` Shu-Chun Weng 2020-12-18 8:24 ` [PATCH v2 0/8] fcntl, sockopt, and ioctl options Laurent Vivier
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).