wenquan2015 opened a new pull request, #20161:
URL: https://github.com/apache/nuttx/pull/20161
Summary
Add Linux-compatible SO_TIMESTAMPING socket option support to NuttX,
enabling hardware and software TX/RX packet timestamping on PKT
sockets with MSG_ERRQUEUE delivery. This is the kernel infrastructure
needed by PTP (IEEE 1588) daemons and network latency
measurement tools.
The series is organized as three foundation patches (from the Xiaomi Vela
team) that modernize the existing SO_TIMESTAMP
infrastructure, followed by five patches that build SO_TIMESTAMPING on top.
What changed (8 commits)
1. nuttx: modify "d_rxtime" and use "iob->io_time" to instead in newer
netdev driver
— Move RX timestamp storage from net_driver_s.d_rxtime into
iob_s.io_time, so each IOB carries its own timestamp through the
stack. Remove the old iob_trycopyin/iob_copyout timestamp packing in
CAN/PKT/UDP paths. Fix iob_clone_partial to copy io_time from
source before the source pointer advances to NULL.
2. nuttx: create "cmsg_store_timestamp" function
— Extract a common cmsg_store_timestamp() helper in
net/utils/net_cmsg.c that checks SO_TIMESTAMP/SO_TIMESTAMPNS via s_options
and appends the appropriate cmsg. Replaces per-protocol timestamp cmsg
formatting in CAN, PKT, and UDP receive paths.
3. nuttx: add NETDEV_RX_STAMP flag in d_features
— Replace the compile-time CONFIG_ARCH_HAVE_NETDEV_TIMESTAMP Kconfig
with a runtime NETDEV_RX_STAMP bit in
net_driver_s.d_features. Drivers that provide hardware RX timestamps set
the flag at probe time; the stack checks it at runtime to
decide whether to fill software timestamps.
4. net/pkt: support option SO_TIMESTAMPING and MSG_ERRQUEUE
— Add SO_TIMESTAMPING TX path for PKT sockets: tagged TX packets loop
back through the driver with io_conn set, are routed into
conn->errahead, and delivered to userspace via recvmsg(MSG_ERRQUEUE) with
SO_TIMESTAMPING cmsg containing struct timespec[3]. Add
poll(POLLPRI) notification when errahead is non-empty.
5. net/pkt: fix time schedule problem when receive MSG_ERRQUEUE
— Fix a scheduling issue where MSG_ERRQUEUE readiness was not properly
waking poll waiters in pkt_netpoll.c.
6. net/socket: use s_options for SO_TIMESTAMP instead of per-conn field
— Remove the redundant udp_conn_s.timestamp field. The socket-level
setsockopt/getsockopt already manages
SO_TIMESTAMP/SO_TIMESTAMPNS via the s_options bitmask; the protocol-level
handlers in inet_sockif.c were shadowing this, causing
SO_TIMESTAMPNS to never work. Remove the protocol-level intercepts so both
options are handled uniformly at the socket layer.
7. net/socket: merge CONFIG_NET_TIMESTAMPING into CONFIG_NET_TIMESTAMP
— Consolidate the two Kconfig options (CONFIG_NET_TIMESTAMP and
CONFIG_NET_TIMESTAMPING) into a single CONFIG_NET_TIMESTAMP that
covers SO_TIMESTAMP, SO_TIMESTAMPNS, and SO_TIMESTAMPING.
8. include/sys/socket.h: add SCM_TIMESTAMPNS and SCM_TIMESTAMPING macros
— Add missing SCM_TIMESTAMPNS and SCM_TIMESTAMPING control message type
definitions for use with recvmsg() cmsg parsing.
Impact
- New functionality: SO_TIMESTAMPING socket option with TX software
timestamping and MSG_ERRQUEUE delivery on PKT sockets.
poll(POLLPRI) notification for errqueue readiness.
- Bug fix: SO_TIMESTAMPNS was broken on UDP sockets because inet_sockif.c
intercepted SO_TIMESTAMP before the socket layer,
preventing s_options from being set. Now both SO_TIMESTAMP and
SO_TIMESTAMPNS work correctly.
- Bug fix: iob_clone_partial could dereference NULL when copying io_time
after the source IOB chain was fully consumed.
- Refactoring: Timestamp storage moved from d_rxtime to iob_s.io_time;
per-protocol cmsg formatting replaced with common
cmsg_store_timestamp() helper; compile-time ARCH_HAVE_NETDEV_TIMESTAMP
replaced with runtime NETDEV_RX_STAMP flag.
- No behavioral change for existing callers when CONFIG_NET_TIMESTAMP is
disabled.
Testing
```
#include <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <poll.h>
/* Test 1: Verify SO_TIMESTAMPING setsockopt/getsockopt */
static int test_setsockopt(void)
{
int fd, val, ret = 0;
socklen_t len;
printf("=== Test 1: SO_TIMESTAMPING setsockopt/getsockopt ===\n");
fd = socket(AF_PACKET, SOCK_RAW, htons(0x0003));
if (fd < 0) { printf("FAIL: socket: %d\n", errno); return -1; }
val = SOF_TIMESTAMPING_TX_SOFTWARE;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)) < 0)
{ printf("FAIL: setsockopt: %d\n", errno); ret = -1; goto out; }
printf(" setsockopt SO_TIMESTAMPING TX: OK\n");
len = sizeof(val); val = 0;
if (getsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, &len) < 0)
{ printf("FAIL: getsockopt: %d\n", errno); ret = -1; goto out; }
printf(" getsockopt: flags=0x%x %s\n", val, val ? "OK" : "FAIL");
if (!val) ret = -1;
out:
close(fd); return ret;
}
/* Test 2: SO_TIMESTAMP backward compatibility */
static int test_so_timestamp(void)
{
int fd, val, ret = 0;
printf("\n=== Test 2: SO_TIMESTAMP backward compatibility ===\n");
fd = socket(AF_PACKET, SOCK_RAW, htons(0x0003));
if (fd < 0) { printf("FAIL: socket: %d\n", errno); return -1; }
val = 1;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)) < 0)
{ printf("FAIL: SO_TIMESTAMP: %d\n", errno); ret = -1; goto out; }
printf(" setsockopt SO_TIMESTAMP: OK\n");
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPNS, &val, sizeof(val)) < 0)
{ printf("FAIL: SO_TIMESTAMPNS: %d\n", errno); ret = -1; goto out; }
printf(" setsockopt SO_TIMESTAMPNS: OK\n");
out:
close(fd); return ret;
}
/* Test 3: TX timestamping + MSG_ERRQUEUE */
static int test_tx_timestamp(void)
{
int fd, val, ret = 0;
unsigned int ifindex;
struct sockaddr_ll addr;
char buf[64], cmsgbuf[256];
struct msghdr msg; struct iovec iov; struct cmsghdr *cmsg;
ssize_t n;
printf("\n=== Test 3: TX timestamping + MSG_ERRQUEUE ===\n");
fd = socket(AF_PACKET, SOCK_DGRAM, htons(0x0800));
if (fd < 0) { printf("FAIL: socket: %d\n", errno); return -1; }
val = SOF_TIMESTAMPING_TX_SOFTWARE;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));
ifindex = if_nametoindex("eth0");
if (!ifindex) { printf(" eth0 not found, skip\n"); goto out; }
memset(&addr, 0, sizeof(addr));
addr.sll_family = AF_PACKET; addr.sll_protocol = htons(0x0003);
addr.sll_ifindex = ifindex;
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
memset(buf, 0xaa, 46);
addr.sll_halen = 6; memset(addr.sll_addr, 0xff, 6);
n = sendto(fd, buf, 46, 0, (struct sockaddr *)&addr, sizeof(addr));
if (n < 0) { printf("FAIL: send: %d\n", errno); ret = -1; goto out; }
printf(" sent %zd bytes\n", n);
usleep(10000);
memset(&msg, 0, sizeof(msg));
iov.iov_base = buf; iov.iov_len = sizeof(buf);
msg.msg_iov = &iov; msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf);
n = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (n < 0) { printf(" MSG_ERRQUEUE: %d\n", errno); goto out; }
printf(" recvmsg MSG_ERRQUEUE: %zd bytes, flags=0x%x\n", n,
msg.msg_flags);
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type ==
SO_TIMESTAMPING)
{ struct timespec *ts = (struct timespec *)CMSG_DATA(cmsg);
printf(" TX timestamp: %lld.%09ld\n PASS!\n",
(long long)ts[0].tv_sec, ts[0].tv_nsec); goto out; }
printf(" WARN: no SO_TIMESTAMPING cmsg\n");
out:
close(fd); return ret;
}
/* Test 4: RX timestamp via SO_TIMESTAMP */
static int test_rx_timestamp(void)
{
int fd, val, ret = 0, retry;
unsigned int ifindex;
struct sockaddr_ll addr;
char buf[128], cmsgbuf[256];
struct msghdr msg; struct iovec iov; struct cmsghdr *cmsg;
ssize_t n;
printf("\n=== Test 4: RX timestamp via SO_TIMESTAMP ===\n");
fd = socket(AF_PACKET, SOCK_RAW, htons(0x0003));
if (fd < 0) { printf("FAIL: socket: %d\n", errno); return -1; }
val = 1;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val));
ifindex = if_nametoindex("eth0");
if (!ifindex) { printf(" eth0 not found, skip\n"); goto out; }
memset(&addr, 0, sizeof(addr));
addr.sll_family = AF_PACKET; addr.sll_protocol = htons(0x0003);
addr.sll_ifindex = ifindex;
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
/* Send broadcast via separate SOCK_DGRAM to trigger RX on RAW socket */
{ int sfd = socket(AF_PACKET, SOCK_DGRAM, htons(0x0800));
if (sfd >= 0) {
struct sockaddr_ll sa = {0};
sa.sll_family = AF_PACKET; sa.sll_protocol = htons(0x0800);
sa.sll_ifindex = ifindex; sa.sll_halen = 6;
memset(sa.sll_addr, 0xff, 6); memset(buf, 0xcc, 46);
sendto(sfd, buf, 46, 0, (struct sockaddr *)&sa, sizeof(sa));
close(sfd); printf(" sent broadcast to trigger RX\n");
} }
memset(&msg, 0, sizeof(msg));
iov.iov_base = buf; iov.iov_len = sizeof(buf);
msg.msg_iov = &iov; msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf);
for (retry = 0; retry < 10; retry++)
{ usleep(50000); n = recvmsg(fd, &msg, MSG_DONTWAIT); if (n > 0)
break; }
if (n <= 0) { printf(" no RX packet\n"); goto out; }
printf(" recvmsg: %zd bytes\n", n);
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP)
{ struct timeval *tv = (struct timeval *)CMSG_DATA(cmsg);
printf(" RX timestamp: %lld.%06ld\n PASS!\n",
(long long)tv->tv_sec, (long)tv->tv_usec); goto out; }
printf(" WARN: no timestamp cmsg\n");
out:
close(fd); return ret;
}
/* Test 5: TX timestamping with poll(POLLPRI) */
static int test_tx_timestamp_poll(void)
{
int fd, val, ret = 0;
unsigned int ifindex;
struct sockaddr_ll addr;
char buf[64], cmsgbuf[256];
struct msghdr msg; struct iovec iov; struct cmsghdr *cmsg;
struct pollfd pfd;
ssize_t n;
printf("\n=== Test 5: TX timestamping with poll() ===\n");
fd = socket(AF_PACKET, SOCK_DGRAM, htons(0x0800));
if (fd < 0) { printf("FAIL: socket: %d\n", errno); return -1; }
val = SOF_TIMESTAMPING_TX_SOFTWARE;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));
ifindex = if_nametoindex("eth0");
memset(buf, 0xaa, 46);
n = sendto(fd, buf, 46, 0, (struct sockaddr *)&addr, sizeof(addr));
if (n < 0) { printf("FAIL: send: %d\n", errno); ret = -1; goto out; }
printf(" sent %zd bytes\n", n);
pfd.fd = fd; pfd.events = POLLPRI; pfd.revents = 0;
n = poll(&pfd, 1, 1000);
if (n <= 0) { printf(" poll timeout/error\n"); ret = -1; goto out; }
printf(" poll: revents=0x%x %s\n", pfd.revents,
(pfd.revents & POLLPRI) ? "(POLLPRI)" : "(unexpected)");
if (!(pfd.revents & POLLPRI)) { ret = -1; goto out; }
memset(&msg, 0, sizeof(msg));
iov.iov_base = buf; iov.iov_len = sizeof(buf);
msg.msg_iov = &iov; msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf);
n = recvmsg(fd, &msg, MSG_ERRQUEUE);
if (n < 0) { printf("FAIL: recvmsg: %d\n", errno); ret = -1; goto out; }
printf(" recvmsg: %zd bytes, flags=0x%x\n", n, msg.msg_flags);
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type ==
SO_TIMESTAMPING)
{ struct timespec *ts = (struct timespec *)CMSG_DATA(cmsg);
printf(" TX timestamp: %lld.%09ld\n", (long long)ts[0].tv_sec,
ts[0].tv_nsec);
printf(" PASS: poll + MSG_ERRQUEUE works!\n"); goto out; }
printf(" WARN: no SO_TIMESTAMPING cmsg\n");
out:
close(fd); return ret;
}
int main(int argc, FAR char *argv[])
{
int fail = 0;
printf("SO_TIMESTAMPING Test Suite\n=========================\n\n");
fail += (test_setsockopt() < 0);
fail += (test_so_timestamp() < 0);
fail += (test_tx_timestamp() < 0);
fail += (test_rx_timestamp() < 0);
fail += (test_tx_timestamp_poll() < 0);
printf("\n=========================\n");
printf(fail ? "%d test(s) FAILED\n" : "All tests PASSED\n", fail);
return fail;
}
```
Tested on NuttX sim (sim:dynconns with CONFIG_NET_TIMESTAMP=y,
CONFIG_NET_PKT=y) using TAP networking:
1. SO_TIMESTAMPING setsockopt/getsockopt — set TX flags, read back,
verified flag value ✅
2. SO_TIMESTAMP/SO_TIMESTAMPNS backward compatibility — setsockopt both
options on PKT socket ✅
3. TX timestamping + MSG_ERRQUEUE — send packet, recvmsg(MSG_ERRQUEUE)
returns SO_TIMESTAMPING cmsg with valid CLOCK_REALTIME
timestamp ✅
4. RX timestamp via SO_TIMESTAMP — receive broadcast packet, cmsg contains
valid struct timeval timestamp ✅
5. TX timestamping with poll(POLLPRI) — send packet, poll() returns
POLLPRI, then recvmsg(MSG_ERRQUEUE) returns valid TX timestamp
✅
SO_TIMESTAMPING Test Suite
=== Test 1: SO_TIMESTAMPING setsockopt/getsockopt ===
setsockopt SO_TIMESTAMPING TX: OK
getsockopt SO_TIMESTAMPING: flags=0x1
TX flag set: OK (val=0x1)
=== Test 2: SO_TIMESTAMP backward compatibility ===
setsockopt SO_TIMESTAMP: OK
setsockopt SO_TIMESTAMPNS: OK
=== Test 3: TX timestamping + MSG_ERRQUEUE ===
sent 46 bytes
recvmsg MSG_ERRQUEUE: got 46 bytes, flags=0x2000
TX timestamp: 1789550304.500344702
PASS: SO_TIMESTAMPING TX works!
=== Test 4: RX timestamp via SO_TIMESTAMP ===
recvmsg: got 60 bytes
RX timestamp: 1789550304.519840
PASS: SO_TIMESTAMP RX works!
=== Test 5: TX timestamping with poll() ===
poll returned: revents=0xa (POLLPRI)
recvmsg: got 46 bytes, flags=0x2000
TX timestamp: 1789550304.579824479
PASS: poll + MSG_ERRQUEUE works!
All tests PASSED
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]