summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ethtool.py10
-rw-r--r--python/vyos/netlink/timestamp.py204
2 files changed, 214 insertions, 0 deletions
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index 79db25324..34c884e0b 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -20,6 +20,7 @@ from json import loads
from vyos.utils.network import interface_exists
from vyos.utils.process import popen
from vyos.netlink import coalesce
+from vyos.netlink import timestamp
# These drivers do not support using ethtool to change the speed, duplex, or
# flow control settings
@@ -68,6 +69,7 @@ class Ethtool:
_flow_control = None
_channels = ''
_coalesce = None
+ _hw_timestamp_filters = None
def __init__(self, ifname):
# Get driver used for interface
@@ -129,6 +131,10 @@ class Ethtool:
with contextlib.suppress(coalesce.CoalesceError, coalesce.GeneralNetlinkError):
self._coalesce = coalesce.get_coalesce(ifname)
+ # Get supported hardware timestamp receive filters
+ with contextlib.suppress(timestamp.TsInfoError, timestamp.GeneralNetlinkError):
+ self._hw_timestamp_filters = timestamp.get_hw_timestamp_filters(ifname)
+
def check_auto_negotiation_supported(self):
""" Check if the NIC supports changing auto-negotiation """
return self._base_settings['supports-auto-negotiation']
@@ -255,3 +261,7 @@ class Ethtool:
"""Get all 'coalesce' parameters for the interface"""
return self._coalesce.copy() if self._coalesce else {}
+
+ def get_hw_timestamp_filters(self):
+ """Get supported hardware timestamp receive filter names"""
+ return self._hw_timestamp_filters or set()
diff --git a/python/vyos/netlink/timestamp.py b/python/vyos/netlink/timestamp.py
new file mode 100644
index 000000000..d9f47678f
--- /dev/null
+++ b/python/vyos/netlink/timestamp.py
@@ -0,0 +1,204 @@
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from pyroute2.netlink import genlmsg
+from pyroute2.netlink import nla
+from pyroute2.netlink import NLM_F_REQUEST
+from pyroute2.netlink.exceptions import NetlinkError
+from pyroute2.netlink.generic import GenericNetlinkSocket
+from pyroute2.netlink.generic.ethtool import ETHTOOL_GENL_NAME
+from pyroute2.netlink.generic.ethtool import ETHTOOL_GENL_VERSION
+from pyroute2.netlink.generic.ethtool import ethtoolheader
+
+# Netlink message type for tsinfo (from Linux kernel uapi/linux/ethtool_netlink.h)
+ETHTOOL_MSG_TSINFO_GET = 0x19
+
+# Operation not supported error code from the kernel (95 decimal)
+EOPNOTSUPP = 0x5F
+
+# HWTSTAMP_FILTER_* types from <linux/net_tstamp.h>
+# Each enum value N maps to bit (1 << N) in the rx_filters bitmask.
+# 'ptp' is a combined mask of all PTP-related filter variants.
+HWTSTAMP_FILTER = {
+ 'all': 1 << 1, # HWTSTAMP_FILTER_ALL
+ 'ntp': 1 << 15, # HWTSTAMP_FILTER_NTP_ALL
+ 'ptp': (1 << 3) # HWTSTAMP_FILTER_PTP_V1_L4_EVENT
+ | (1 << 4) # HWTSTAMP_FILTER_PTP_V1_L4_SYNC
+ | (1 << 5) # HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ
+ | (1 << 6) # HWTSTAMP_FILTER_PTP_V2_L4_EVENT
+ | (1 << 7) # HWTSTAMP_FILTER_PTP_V2_L4_SYNC
+ | (1 << 8) # HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ
+ | (1 << 9) # HWTSTAMP_FILTER_PTP_V2_L2_EVENT
+ | (1 << 10) # HWTSTAMP_FILTER_PTP_V2_L2_SYNC
+ | (1 << 11) # HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ
+ | (1 << 12) # HWTSTAMP_FILTER_PTP_V2_EVENT
+ | (1 << 13) # HWTSTAMP_FILTER_PTP_V2_SYNC
+ | (1 << 14), # HWTSTAMP_FILTER_PTP_V2_DELAY_REQ
+}
+
+
+class ethtool_bitset_bit(nla):
+ """Single bit entry inside a verbose ethtool bitset."""
+
+ nla_map = (
+ ('ETHTOOL_A_BITSET_BIT_UNSPEC', 'none'),
+ ('ETHTOOL_A_BITSET_BIT_INDEX', 'uint32'),
+ ('ETHTOOL_A_BITSET_BIT_NAME', 'asciiz'),
+ ('ETHTOOL_A_BITSET_BIT_VALUE', 'flag'),
+ )
+
+
+class ethtool_bitset_bits(nla):
+ """Container layer for nesting bit entries in a verbose ethtool bitset."""
+
+ ethtool_bitset_bit = ethtool_bitset_bit
+ nla_map = (
+ ('ETHTOOL_A_BITSET_BIT_UNSPEC', 'none'),
+ ('ETHTOOL_A_BITSET_BIT', 'ethtool_bitset_bit'),
+ )
+
+
+class ethtool_bitset(nla):
+ """Ethtool verbose bitset NLA structure.
+
+ The kernel returns the verbose form by default (ETHTOOL_A_BITSET_BITS nested entries).
+ ETHTOOL_A_BITSET_VALUE and ETHTOOL_A_BITSET_MASK are listed for positional completeness
+ only — they are not read by this implementation.
+ """
+
+ ethtool_bitset_bits = ethtool_bitset_bits
+ nla_map = (
+ ('ETHTOOL_A_BITSET_UNSPEC', 'none'),
+ ('ETHTOOL_A_BITSET_NOMASK', 'flag'),
+ ('ETHTOOL_A_BITSET_SIZE', 'uint32'),
+ ('ETHTOOL_A_BITSET_BITS', 'ethtool_bitset_bits'),
+ ('ETHTOOL_A_BITSET_VALUE', 'binary'),
+ ('ETHTOOL_A_BITSET_MASK', 'binary'),
+ )
+
+
+class ethtool_tsinfo_msg(genlmsg):
+ """Netlink message structure for ETHTOOL_MSG_TSINFO_GET.
+
+ nla_map indices are strictly positional — all attributes up to the last needed index must be
+ listed. Intermediate unused attributes are typed 'hex' to skip full NLA decoding.
+ Reference: https://docs.kernel.org/networking/ethtool-netlink.html#tsinfo-get
+ """
+
+ ethtoolheader = ethtoolheader
+ ethtool_bitset = ethtool_bitset
+ nla_map = (
+ ('ETHTOOL_A_TSINFO_UNSPEC', 'none'),
+ ('ETHTOOL_A_TSINFO_HEADER', 'ethtoolheader'),
+ ('ETHTOOL_A_TSINFO_TIMESTAMPING', 'hex'),
+ ('ETHTOOL_A_TSINFO_TX_TYPES', 'hex'),
+ ('ETHTOOL_A_TSINFO_RX_FILTERS', 'ethtool_bitset'),
+ )
+
+
+GeneralNetlinkError = NetlinkError
+
+
+class TsInfoError(Exception):
+ """Custom exception for timestamp info retrieval errors."""
+
+ pass
+
+
+def _bitset_to_int(attr) -> int:
+ """Reconstruct an integer bitmask from a verbose ethtool_bitset NLA."""
+ if attr is None:
+ return 0
+ bits_attr = attr.get_attr('ETHTOOL_A_BITSET_BITS')
+ if bits_attr is None:
+ return 0
+ bitmask = 0
+ for bit in bits_attr.get_attrs('ETHTOOL_A_BITSET_BIT'):
+ index = bit.get_attr('ETHTOOL_A_BITSET_BIT_INDEX')
+ if index is not None:
+ bitmask |= 1 << index
+ return bitmask
+
+
+class TsInfoNetlink(GenericNetlinkSocket):
+ """Hardware timestamp info queries using pyroute2 with netlink support."""
+
+ def __init__(self, ifname: str):
+ super().__init__()
+ self._bound = False
+ self._ifname = ifname
+
+ def _ensure_bound(self):
+ """Bind netlink socket to the generic ethtool family structure if not already active."""
+ if not self._bound:
+ self.bind(ETHTOOL_GENL_NAME, ethtool_tsinfo_msg)
+ self._bound = True
+
+ def get_rx_filters(self) -> set:
+ """
+ Query supported hardware timestamp receive filters for the interface.
+
+ Returns:
+ A set of supported filter names ('all', 'ntp', 'ptp'), or an empty set
+ if the driver or interface lacks hardware timestamping support.
+
+ Example:
+ >>> with TsInfoNetlink('eth0') as ts:
+ ... print(ts.get_rx_filters())
+ {'ptp'}
+ """
+ self._ensure_bound()
+
+ msg = ethtool_tsinfo_msg()
+ msg['cmd'] = ETHTOOL_MSG_TSINFO_GET
+ msg['version'] = ETHTOOL_GENL_VERSION
+ msg['attrs'].append(
+ (
+ 'ETHTOOL_A_TSINFO_HEADER',
+ {'attrs': [['ETHTOOL_A_HEADER_DEV_NAME', self._ifname]]},
+ )
+ )
+
+ try:
+ response = self.nlm_request(
+ msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST
+ )
+ except NetlinkError as e:
+ if e.code == EOPNOTSUPP:
+ return set()
+ raise
+
+ if not response:
+ return set()
+
+ bitmask = _bitset_to_int(response[0].get_attr('ETHTOOL_A_TSINFO_RX_FILTERS'))
+ return {name for name, mask in HWTSTAMP_FILTER.items() if bitmask & mask}
+
+
+def get_hw_timestamp_filters(ifname: str) -> set:
+ """
+ Get supported hardware timestamp receive filter names for an interface.
+
+ Args:
+ ifname: Target network interface string (e.g., 'eth0').
+
+ Returns:
+ A set of supported human-readable filters, or an empty set on error/lack of support.
+ """
+ try:
+ with TsInfoNetlink(ifname) as ts:
+ return ts.get_rx_filters()
+ except (NetlinkError, OSError):
+ return set()