use SO_TIMESTAMP, works on Linux, FreeBSD and macOS
This commit is contained in:
+18
-17
@@ -14,22 +14,23 @@ from . import notify as notify_mod
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
eventlog = notify_mod.eventlog
|
eventlog = notify_mod.eventlog
|
||||||
|
|
||||||
# SO_TIMESTAMPNS: kernel attaches a struct timespec to each received datagram.
|
# SO_TIMESTAMP: kernel attaches a struct timeval to each received datagram.
|
||||||
# The constant is not exposed by Python's socket module on all platforms,
|
# Supported on Linux, FreeBSD, and macOS. The constant is not exposed by
|
||||||
# so fall back to the Linux value (35) when absent.
|
# Python's socket module on all platforms, so fall back to the Linux value (29)
|
||||||
_SO_TIMESTAMPNS = getattr(socket, 'SO_TIMESTAMPNS', 35)
|
# when absent.
|
||||||
# struct timespec uses two native C longs: tv_sec and tv_nsec
|
_SO_TIMESTAMP = getattr(socket, 'SO_TIMESTAMP', 29)
|
||||||
_TIMESPEC = struct.Struct('@ll')
|
# struct timeval uses two native C longs: tv_sec and tv_usec
|
||||||
|
_TIMEVAL = struct.Struct('@ll')
|
||||||
|
|
||||||
|
|
||||||
def enable_kernel_timestamps(sock) -> bool:
|
def enable_kernel_timestamps(sock) -> bool:
|
||||||
"""Try to enable SO_TIMESTAMPNS on *sock*.
|
"""Try to enable SO_TIMESTAMP on *sock*.
|
||||||
|
|
||||||
Returns True if the kernel will supply receive timestamps, False otherwise
|
Returns True if the kernel will supply receive timestamps, False otherwise
|
||||||
(non-Linux, older kernel, or insufficient permissions).
|
(unsupported platform, older kernel, or insufficient permissions).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sock.setsockopt(socket.SOL_SOCKET, _SO_TIMESTAMPNS, 1)
|
sock.setsockopt(socket.SOL_SOCKET, _SO_TIMESTAMP, 1)
|
||||||
return True
|
return True
|
||||||
except OSError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
@@ -38,18 +39,18 @@ def enable_kernel_timestamps(sock) -> bool:
|
|||||||
def _extract_kernel_ts(ancdata) -> float | None:
|
def _extract_kernel_ts(ancdata) -> float | None:
|
||||||
"""Parse recvmsg ancillary data and return the kernel receive time.
|
"""Parse recvmsg ancillary data and return the kernel receive time.
|
||||||
|
|
||||||
Returns seconds as a float, or None if no SO_TIMESTAMPNS cmsg is present.
|
Returns seconds as a float, or None if no SO_TIMESTAMP cmsg is present.
|
||||||
"""
|
"""
|
||||||
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
||||||
if cmsg_level == socket.SOL_SOCKET and cmsg_type == _SO_TIMESTAMPNS:
|
if cmsg_level == socket.SOL_SOCKET and cmsg_type == _SO_TIMESTAMP:
|
||||||
if len(cmsg_data) >= _TIMESPEC.size:
|
if len(cmsg_data) >= _TIMEVAL.size:
|
||||||
sec, nsec = _TIMESPEC.unpack_from(cmsg_data)
|
sec, usec = _TIMEVAL.unpack_from(cmsg_data)
|
||||||
return sec + nsec * 1e-9
|
return sec + usec * 1e-6
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class RecvmsgTransport:
|
class RecvmsgTransport:
|
||||||
"""Thin wrapper used when SO_TIMESTAMPNS is active (add_reader path).
|
"""Thin wrapper used when SO_TIMESTAMP is active (add_reader path).
|
||||||
|
|
||||||
Exposes the same sendto() / close() interface as asyncio's DatagramTransport
|
Exposes the same sendto() / close() interface as asyncio's DatagramTransport
|
||||||
so the rest of the code does not need to know which path is in use.
|
so the rest of the code does not need to know which path is in use.
|
||||||
@@ -79,8 +80,8 @@ def make_recvmsg_reader(sock, handler, transport):
|
|||||||
"""Return a callback suitable for loop.add_reader().
|
"""Return a callback suitable for loop.add_reader().
|
||||||
|
|
||||||
Reads one datagram per call using recvmsg() so that kernel timestamps in
|
Reads one datagram per call using recvmsg() so that kernel timestamps in
|
||||||
the ancillary data are accessible. Falls back to time.time() gracefully
|
the ancillary data are accessible. Falls back to time.time() if the
|
||||||
if the cmsg is missing.
|
cmsg is missing.
|
||||||
|
|
||||||
handler(msg, addr, transport, kernel_ts) – same signature as udp_handler
|
handler(msg, addr, transport, kernel_ts) – same signature as udp_handler
|
||||||
in main.py with the optional kernel_ts argument.
|
in main.py with the optional kernel_ts argument.
|
||||||
|
|||||||
Reference in New Issue
Block a user