All of lore.kernel.org
 help / color / mirror / Atom feed
* [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

* [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

* [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

* [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

* [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

* [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

* [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 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

* 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

* 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 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 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 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

* 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

* 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

* 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

* 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.