On 07-05-2026 08:57 pm, Morten Brørup wrote:
From: Rajesh Kumar [mailto:[email protected]]
Sent: Thursday, 7 May 2026 15.45
To: [email protected]
Add PTP (Precision Time Protocol) header structures and inline helper
functions to lib/net following DPDK conventions for protocol libraries
(similar to rte_tcp.h, rte_ip.h).
Provides wire-format structures with endian-annotated types:
- rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID
- rte_ptp_hdr: 34-byte common message header with correctionField
- rte_ptp_timestamp: 10-byte nanosecond-precision timestamp
PTP message type constants for all IEEE 1588-2019 message types
(Sync, Delay_Req, Announce, Management, etc.) and flag field bits
(two-step, unicast, leap indicator).
Inline accessor and utility functions for performance:
- rte_ptp_msg_type(), rte_ptp_version(), rte_ptp_domain()
- rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_is_two_step()
- rte_ptp_correction_ns(), rte_ptp_add_correction()
- rte_ptp_timestamp_to_ns() for timestamp conversion
Supports all PTP encapsulations: L2 (EtherType 0x88F7), VLAN/QinQ,
UDP/IPv4, and UDP/IPv6 (ports 319/320).
All inline functions — zero function call overhead, suitable for
real-time packet processing.
Signed-off-by: Rajesh Kumar <[email protected]>
Great progress.
My next wave of comments inline below. :-)
Appreciate your time and feedback :-)
---
MAINTAINERS | 6 +
lib/net/meson.build | 1 +
lib/net/rte_ptp.h | 264 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 271 insertions(+)
create mode 100644 lib/net/rte_ptp.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 0f5539f851..da31ada871 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1665,6 +1665,12 @@ F: doc/guides/prog_guide/ipsec_lib.rst
M: Vladimir Medvedkin <[email protected]>
F: app/test-sad/
+PTP - lib/net
Please remove lib/net from this headline.
Intead, suggest:
PTP (IEEE 1588 Precision Time Protocol)
Done. Changed to PTP (IEEE 1588 Precision Time Protocol)
+M: Rajesh Kumar <[email protected]>
+F: lib/net/rte_ptp.h
+F: examples/ptp_tap_relay_sw/
+F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
+
PDCP - EXPERIMENTAL
M: Anoob Joseph <[email protected]>
M: Volodymyr Fialko <[email protected]>
diff --git a/lib/net/meson.build b/lib/net/meson.build
index 3fad5edc5b..63d13719f3 100644
--- a/lib/net/meson.build
+++ b/lib/net/meson.build
@@ -28,6 +28,7 @@ headers = files(
'rte_geneve.h',
'rte_l2tpv2.h',
'rte_ppp.h',
+ 'rte_ptp.h',
'rte_ib.h',
)
diff --git a/lib/net/rte_ptp.h b/lib/net/rte_ptp.h
new file mode 100644
index 0000000000..649b944d29
--- /dev/null
+++ b/lib/net/rte_ptp.h
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _RTE_PTP_H_
+#define _RTE_PTP_H_
+
+/**
+ * @file
+ *
+ * PTP (IEEE 1588) protocol definitions
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <rte_byteorder.h>
+#include <rte_common.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * PTP Constants
+ */
+
+/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req,
PDelay_Resp). */
+#define RTE_PTP_EVENT_PORT 319
+
+/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.).
*/
+#define RTE_PTP_GENERAL_PORT 320
The libc header <netinet/in.h> defines IPPORT_xxx.
We should use something similar:
#define RTE_IPPORT_PTP_EVENT and RTE_IPPORT_PTP_GENERAL
Done. Renamed RTE_PTP_EVENT_PORT → RTE_IPPORT_PTP_EVENT and
RTE_PTP_GENERAL_PORT → RTE_IPPORT_PTP_GENERAL. Updated all usages in
ptp_parse.h (IPv4 and IPv6 sections).
+
+/** PTP multicast MAC address: 01:1B:19:00:00:00. */
+#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00
}
+
+/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E. */
+#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00,
0x0E }
Similarly here; we should establish a convention for MAC addresses,
like RTE_ETHER_TYPE_xxx in DPDK <lib/net/rte_ether.h>.
Suggest:
RTE_ETHER_ADDR_PTP_MULTICAST and RTE_ETHER_ADDR_PTP_MULTICAST_PDELAY
This also follows the general naming convention of having the broadest scope is
first and the narrowest scope last.
Done. Renamed to RTE_ETHER_ADDR_PTP_MULTICAST and
RTE_ETHER_ADDR_PTP_MULTICAST_PDELAY.
+
+/*
+ * PTP Message Types (IEEE 1588-2019 Table 36)
+ */
+
+#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event). */
+#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event).
*/
+#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req
(event). */
+#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp
(event). */
+#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general).
*/
+#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp
(general). */
+#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**<
Peer_Delay_Resp_Follow_Up. */
Missing in the description: (general)
Done. Changed to Peer_Delay_Resp_Follow_Up (general).
For consistency, consider renaming RTE_PTP_MSGTYPE_FOLLOW_UP to
RTE_PTP_MSGTYPE_FU.
Done. Renamed RTE_PTP_MSGTYPE_FOLLOW_UP to RTE_PTP_MSGTYPE_FU.
+#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general).
*/
+#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general).
*/
+#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management
(general). */
+
+/*
+ * PTP Flag Field Bits (IEEE 1588-2019 Table 37)
+ *
+ * These constants are for use after rte_be_to_cpu_16(hdr->flags).
+ * flagField[0] (octet 6) maps to host bits 8-15.
+ * flagField[1] (octet 7) maps to host bits 0-7.
+ */
+
+#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag. */
+#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag. */
+#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61. */
+#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59. */
We don't have a RTE_BIT16() macro like the RTE_BIT32/64() macros, so maybe use:
#define RTE_PTP_FLAG_TWO_STEP (UINT16_C(1) << 9) /**< Two-step flag. */
#define RTE_PTP_FLAG_UNICAST (UINT16_C(1) << 10) /**< Unicast flag. */
#define RTE_PTP_FLAG_LI_61 (UINT16_C(1) << 0) /**< Leap indicator 61. */
#define RTE_PTP_FLAG_LI_59 (UINT16_C(1) << 1) /**< Leap indicator 59. */
Done. All four flag macros now use (UINT16_C(1) << N).
+
+/*
+ * PTP Header Structures (IEEE 1588-2019)
+ */
+
+/**
+ * PTP Port Identity (10 bytes).
+ */
+struct __rte_packed_begin rte_ptp_port_id {
+ uint8_t clock_id[8]; /**< clockIdentity (EUI-64). */
+ rte_be16_t port_number; /**< portNumber. */
+} __rte_packed_end;
+
+/**
+ * PTP Common Message Header (34 bytes).
+ */
+struct __rte_packed_begin rte_ptp_hdr {
+ uint8_t msg_type; /**< transportSpecific (4) |
messageType (4). */
+ uint8_t version; /**< minorVersionPTP (4) | versionPTP
(4). */
The two fields above should be split into their nibbles, for direct access,
like the version_ihl field in the IPv4 header:
https://elixir.bootlin.com/dpdk/v26.03/source/lib/net/rte_ip4.h#L43
E.g.:
__extension__
union {
uint8_t ts_msgtype; /**< Message type */
struct {
#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
uint8_t msg_type:4; /**< messageType */
uint8_t ts:4; /**< transportSpecific */
#elif RTE_BYTE_ORDER == RTE_BIG_ENDIAN
uint8_t ts:4; /**< transportSpecific */
uint8_t msg_type:4; /**< messageType */
#endif
};
};
Warning: I'm not sure I got the nibble order right. Make sure you do! :-)
Done. Used __extension__ union { uint8_t ts_msgtype; struct { uint8_t
msg_type:4; uint8_t ts:4; }; }; with RTE_BYTE_ORDER conditional for
endianness, matching rte_ipv4_hdr convention.
+ rte_be16_t msg_length; /**< Total message length in bytes. */
+ uint8_t domain_number; /**< PTP domain (0-255). */
+ uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019). */
+ rte_be16_t flags; /**< Flag field (see RTE_PTP_FLAG_*).
*/
+ rte_be64_t correction; /**< correctionField (scaled ns, 48.16
fixed). */
+ rte_be32_t msg_type_specific; /**< messageTypeSpecific. */
+ struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity.
*/
+ rte_be16_t sequence_id; /**< sequenceId. */
+ uint8_t control; /**< controlField (deprecated in 1588-
2019). */
+ int8_t log_msg_interval; /**< logMessageInterval. */
+} __rte_packed_end;
+
+/**
+ * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies).
The PTP timestamp origo should be mentioned here.
E.g. the UNIX time_t origo is Jan 1st 00:00:00 1970.
Done. Added Seconds since PTP epoch (1 January 1970 00:00:00 TAI) to
struct docstring.
+ */
+struct __rte_packed_begin rte_ptp_timestamp {
+ rte_be16_t seconds_hi; /**< Upper 16 bits of seconds. */
+ rte_be32_t seconds_lo; /**< Lower 32 bits of seconds. */
+ rte_be32_t nanoseconds; /**< Nanoseconds (0-999999999). */
+} __rte_packed_end;
+
+/*
+ * Inline Helpers
+ */
+
+/**
+ * Extract PTP message type from header.
+ *
+ * @param hdr
+ * Pointer to PTP header.
+ * @return
+ * Message type (0x0-0xF).
+ */
+static inline uint8_t
+rte_ptp_msg_type(const struct rte_ptp_hdr *hdr)
+{
+ return hdr->msg_type & 0x0F;
+}
+
+/**
+ * Extract transport-specific field from header.
+ *
+ * @param hdr
+ * Pointer to PTP header.
+ * @return
+ * Transport-specific value (upper nibble, 0x0-0xF).
+ */
+static inline uint8_t
+rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr)
+{
+ return (hdr->msg_type >> 4) & 0x0F;
+}
+
+/**
+ * Extract PTP version from header.
+ *
+ * @param hdr
+ * Pointer to PTP header.
+ * @return
+ * PTP version number (typically 2).
+ */
+static inline uint8_t
+rte_ptp_version(const struct rte_ptp_hdr *hdr)
+{
+ return hdr->version & 0x0F;
+}
+
+/**
+ * Get sequence ID from PTP header (host byte order).
+ *
+ * @param hdr
+ * Pointer to PTP header.
+ * @return
+ * Sequence ID in host byte order.
+ */
+static inline uint16_t
+rte_ptp_seq_id(const struct rte_ptp_hdr *hdr)
+{
+ return rte_be_to_cpu_16(hdr->sequence_id);
+}
+
+/**
+ * Get PTP domain number.
+ *
+ * @param hdr
+ * Pointer to PTP header.
+ * @return
+ * Domain number (0-255).
+ */
+static inline uint8_t
+rte_ptp_domain(const struct rte_ptp_hdr *hdr)
+{
+ return hdr->domain_number;
+}
The above "get" accessor functions are superfluous. Please remove:
rte_ptp_msg_type()
rte_ptp_transport_specific()
rte_ptp_version()
rte_ptp_seq_id()
rte_ptp_domain()
Done. All five removed. Callers updated to use direct field access:
hdr->msg_type, hdr->version, hdr->domain_number,
rte_be_to_cpu_16(hdr->sequence_id), etc.
+
+/**
+ * Check if PTP message type is an event message.
+ * Event messages (msg_type 0x0-0x3) require timestamps.
+ *
+ * @param msg_type
+ * PTP message type value (0x0-0xF).
+ * @return
+ * true if event message, false otherwise.
+ */
+static inline bool
+rte_ptp_is_event(int msg_type)
+{
+ return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP;
+}
Suggest passing the parameter as const struct rte_ptp_hdr *hdr, like in
rte_ptp_is_two_step() below.
Done. Parameter changed from int msg_type to const struct rte_ptp_hdr
*hdr. Body uses hdr->msg_type. All callers updated.
+
+/**
+ * Check if the two-step flag is set in a PTP header.
+ *
+ * @param hdr
+ * Pointer to PTP header.
+ * @return
+ * true if two-step flag is set.
+ */
+static inline bool
+rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr)
+{
+ return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) !=
0;
Faster way:
return (hdr->flags & RTE_BE16(RTE_PTP_FLAG_TWO_STEP)) != 0;
Done. Uses hdr->flags & RTE_BE16(RTE_PTP_FLAG_TWO_STEP) for compile-time
constant comparison.
+}
+
+/**
+ * Get correctionField value in nanoseconds (from 48.16 fixed-point).
+ *
+ * @param hdr
+ * Pointer to PTP header.
+ * @return
+ * Correction value in nanoseconds.
+ */
+static inline int64_t
+rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr)
+{
+ return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16;
+}
This "get" accessor function is superfluous. Please remove:
rte_ptp_correction_ns()
The "correction" field in the PTP header structure should sufficiently describe
the field's fixed-point encoding.
+
+/**
+ * Add a residence time (in nanoseconds) to the correctionField.
+ * Used by Transparent Clocks to account for relay transit delay.
+ * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-
point).
+ *
+ * @param hdr
+ * Pointer to PTP header (will be modified in-place).
+ * @param residence_ns
+ * Residence time in nanoseconds to add.
+ */
+static inline void
+rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns)
+{
+ int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction);
+
+ cf += (int64_t)((uint64_t)residence_ns << 16);
+ hdr->correction = rte_cpu_to_be_64(cf);
+}
I don't think negative time can be added, so please use uint64_t instead of
int64_t for the parameter and inside the function.
Note to reviewers: "residence time" is the term in the standard. "sojourn time"
is not used.
Done. Parameter changed from int64_t residence_ns to uint64_t residence_ns.
+
+/**
+ * Convert a PTP timestamp structure to nanoseconds since epoch.
+ *
+ * @param ts
+ * Pointer to PTP timestamp.
+ * @return
+ * Time in nanoseconds since epoch.
+ */
+static inline uint64_t
+rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts)
+{
+ uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32)
|
+ rte_be_to_cpu_32(ts->seconds_lo);
+
+ return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds);
+}
1000000000ULL -> UINT64_C(1000000000)
Done. Changed 1000000000ULL -> UINT64_C(1000000000).
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_PTP_H_ */
--
2.53.0