Subject: RFC: PTP Boundary Clock over UDPv4/UDPv6 on Linux bridge

From: Kurt Kanzenbach <[email-masked]>
Date: Tue, 16 Nov 2021 11:19:24 +0100

Hi all,

I'm currently trying to setup a PTP Boundary Clock over UDPv4 or UDPv6
on top of a switch using a Linux bridge. It works fine using PTP Layer 2
transport, but not for UDP. I'm wondering whether this is supported
using Linux or if I'm doing something wrong.

My setup looks like this:

Bridge (DSA):

|$ ip link set eth0 up
|$ ip link set lan0 up
|$ ip link set lan1 up
|$ ip link add name br0 type bridge
|$ ip link set dev lan0 master br0
|$ ip link set dev lan1 master br0
|$ ip link set br0 up
|$ dhclient br0

PTP:

|$ ptp4l -4 -i lan0 -i lan1 --tx_timestamp_timeout=40 -m

It seems like ptp4l cannot receive any PTP messages. Tx works fine.

The following hack solves the problem for me. However, I'm not sure
whether that's the correct approach or not. Any opinions, ideas,
comments?

Thanks,
Kurt

|From 2e8b429b3ebabda8e81693b9704dbe5e5205ab09 Mon Sep 17 00:00:00 2001
|From: Kurt Kanzenbach <[email-masked]>
|Date: Wed, 4 Aug 2021 09:33:12 +0200
|Subject: [PATCH] net: bridge: input: Handle PTP over UDPv4 and UDPv6
|
|PTP is considered management traffic. A time aware switch should intercept all
|PTP messages and handle them accordingly. The corresponding Linux setup is like
|this:
|
|         +-- br0 --+
|        / /   |     \
|       / /    |      \
|      /  |    |     / \
|     /   |    |    /   \
|   swp0 swp1 swp2 swp3 swp4
|
|ptp4l runs on all individual switch ports and needs full control over sending
|and receiving messages on these ports.
|
|However, the bridge code treats PTP messages over UDP transport as regular IP
|messages and forwards them to br0. This way, the running ptp4l instances cannot
|receive these frames on the individual switch port interfaces.
|
|Fix it by intercepting PTP UDP traffic in the bridge code and pass them to the
|regular network processing.
|
|Signed-off-by: Kurt Kanzenbach <[email-masked]>
|---
| net/bridge/br_input.c | 13 +++++++++++++
| 1 file changed, 13 insertions(+)
|
|diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
|index b50382f957c1..4e12be70a003 100644
|--- a/net/bridge/br_input.c
|+++ b/net/bridge/br_input.c
|@@ -271,6 +271,13 @@ static int br_process_frame_type(struct net_bridge_port *p,
| 	return 0;
| }
| 
|+static const unsigned char ptp_ip_destinations[][ETH_ALEN] = {
|+	{ 0x01, 0x00, 0x5e, 0x00, 0x01, 0x81 }, /* IPv4 PTP */
|+	{ 0x01, 0x00, 0x5e, 0x00, 0x00, 0x6b }, /* IPv4 P2P */
|+	{ 0x33, 0x33, 0x00, 0x00, 0x01, 0x81 }, /* IPv6 PTP */
|+	{ 0x33, 0x33, 0x00, 0x00, 0x00, 0x6b }, /* IPv6 P2P */
|+};
|+
| /*
|  * Return NULL if skb is handled
|  * note: already called with rcu_read_lock
|@@ -280,6 +287,7 @@ static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
| 	struct net_bridge_port *p;
| 	struct sk_buff *skb = *pskb;
| 	const unsigned char *dest = eth_hdr(skb)->h_dest;
|+	int i;
| 
| 	if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
| 		return RX_HANDLER_PASS;
|@@ -360,6 +368,11 @@ static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
| 	if (unlikely(br_process_frame_type(p, skb)))
| 		return RX_HANDLER_PASS;
| 
|+	/* Check for PTP over UDPv4 or UDPv6. */
|+	for (i = 0; i < ARRAY_SIZE(ptp_ip_destinations); ++i)
|+		if (ether_addr_equal(ptp_ip_destinations[i], dest))
|+			return RX_HANDLER_PASS;
|+
| forward:
| 	switch (p->state) {
| 	case BR_STATE_FORWARDING:
|-- 
|2.30.2