2020-09-04 09:20:14

by Henrik Bjoernlund

[permalink] [raw]
Subject: [PATCH RFC 0/7] net: bridge: cfm: Add support for Connectivity Fault Management(CFM)

Connectivity Fault Management (CFM) is defined in 802.1Q section 12.14.

Connectivity Fault Management (CFM) comprises capabilities for
detecting, verifying, and isolating connectivity failures in
Virtual Bridged Networks. These capabilities can be used in
networks operated by multiple independent organizations, each
with restricted management access to each other’s equipment.

CFM functions are partitioned as follows:
— Path discovery
— Fault detection
— Fault verification and isolation
— Fault notification
— Fault recovery

The primary CFM protocol shims are called Maintenance Points (MPs).
A MP can be either a MEP or a MHF.
The MEP:
-It is the Maintenance association End Point
described in 802.1Q section 19.2.
-It is created on a specific level (1-7) and is assuring
that no CFM frames are passing through this MEP on lower levels.
-It initiates and terminates/validates CFM frames on its level.
-It can only exist on a port that is related to a bridge.
The MHF:
-It is the Maintenance Domain Intermediate Point
(MIP) Half Function (MHF) described in 802.1Q section 19.3.
-It is created on a specific level (1-7).
-It is extracting/injecting certain CFM frame on this level.
-It can only exist on a port that is related to a bridge.
-Currently not supported.

There are defined the following CFM protocol functions:
-Continuity Check
-Loopback. Currently not supported.
-Linktrace. Currently not supported.

This CFM component supports create/delete of MEP instances and
configuration of the different CFM protocols. Also status information
can be fetched and delivered through notification due to defect status
change.

The user interacts with CFM using the 'cfm' user space client program, the
client talks with the kernel using netlink. The kernel will try to offload
the requests to the HW via switchdev API (not implemented yet).

Any notification emitted by CFM from the kernel can be monitored in user
space by starting 'cfm_server' program.

Currently this 'cfm' and 'cfm_server' programs are standalone placed in a
cfm repository https://github.com/microchip-ung/cfm but it is considered
to integrate this into 'iproute2'.

Reviewed-by: Horatiu Vultur <[email protected]>
Signed-off-by: Henrik Bjoernlund <[email protected]>

Henrik Bjoernlund (7):
net: bridge: extend the process of special frames
bridge: cfm: Add BRIDGE_CFM to Kconfig.
bridge: uapi: cfm: Added EtherType used by the CFM protocol.
bridge: cfm: Kernel space implementation of CFM.
bridge: cfm: Netlink Interface.
bridge: cfm: Netlink Notifications.
bridge: cfm: Bridge port remove.

include/uapi/linux/cfm_bridge.h | 75 +++
include/uapi/linux/if_bridge.h | 125 +++++
include/uapi/linux/if_ether.h | 1 +
include/uapi/linux/rtnetlink.h | 2 +
net/bridge/Kconfig | 11 +
net/bridge/Makefile | 2 +
net/bridge/br_cfm.c | 936 ++++++++++++++++++++++++++++++++
net/bridge/br_cfm_netlink.c | 690 +++++++++++++++++++++++
net/bridge/br_device.c | 4 +
net/bridge/br_if.c | 1 +
net/bridge/br_input.c | 31 +-
net/bridge/br_mrp.c | 19 +-
net/bridge/br_netlink.c | 126 ++++-
net/bridge/br_private.h | 82 ++-
net/bridge/br_private_cfm.h | 242 +++++++++
15 files changed, 2326 insertions(+), 21 deletions(-)
create mode 100644 include/uapi/linux/cfm_bridge.h
create mode 100644 net/bridge/br_cfm.c
create mode 100644 net/bridge/br_cfm_netlink.c
create mode 100644 net/bridge/br_private_cfm.h

--
2.28.0


2020-09-04 09:20:34

by Henrik Bjoernlund

[permalink] [raw]
Subject: [PATCH RFC 1/7] net: bridge: extend the process of special frames

This patch extends the processing of frames in the bridge. Currently MRP
frames needs special processing and the current implementation doesn't
allow a nice way to process different frame types. Therefore try to
improve this by adding a list that contains frame types that need
special processing. This list is iterated for each input frame and if
there is a match based on frame type then these functions will be called
and decide what to do with the frame. It can process the frame then the
bridge doesn't need to do anything or don't process so then the bridge
will do normal forwarding.

Signed-off-by: Henrik Bjoernlund <[email protected]>
---
net/bridge/br_device.c | 1 +
net/bridge/br_input.c | 31 ++++++++++++++++++++++++++++++-
net/bridge/br_mrp.c | 19 +++++++++++++++----
net/bridge/br_private.h | 18 ++++++++++++------
4 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index 9a2fb4aa1a10..a9232db03108 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -473,6 +473,7 @@ void br_dev_setup(struct net_device *dev)
spin_lock_init(&br->lock);
INIT_LIST_HEAD(&br->port_list);
INIT_HLIST_HEAD(&br->fdb_list);
+ INIT_LIST_HEAD(&br->ftype_list);
#if IS_ENABLED(CONFIG_BRIDGE_MRP)
INIT_LIST_HEAD(&br->mrp_list);
#endif
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 59a318b9f646..0f475b21094c 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -254,6 +254,21 @@ static int nf_hook_bridge_pre(struct sk_buff *skb, struct sk_buff **pskb)
return RX_HANDLER_CONSUMED;
}

+/* Return 0 if the frame was not processed otherwise 1
+ * note: already called with rcu_read_lock
+ */
+static int br_process_frame_type(struct net_bridge_port *p,
+ struct sk_buff *skb)
+{
+ struct br_frame_type *tmp;
+
+ list_for_each_entry_rcu(tmp, &p->br->ftype_list, list) {
+ if (unlikely(tmp->type == skb->protocol))
+ return tmp->func(p, skb);
+ }
+ return 0;
+}
+
/*
* Return NULL if skb is handled
* note: already called with rcu_read_lock
@@ -343,7 +358,7 @@ static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
}
}

- if (unlikely(br_mrp_process(p, skb)))
+ if (unlikely(br_process_frame_type(p, skb)))
return RX_HANDLER_PASS;

forward:
@@ -380,3 +395,17 @@ rx_handler_func_t *br_get_rx_handler(const struct net_device *dev)

return br_handle_frame;
}
+
+void br_add_frame(struct net_bridge *br, struct br_frame_type *ft)
+{
+ list_add_rcu(&ft->list, &br->ftype_list);
+}
+
+void br_del_frame(struct net_bridge *br, struct br_frame_type *ft)
+{
+ struct br_frame_type *tmp;
+
+ list_for_each_entry(tmp, &br->ftype_list, list)
+ if (ft == tmp)
+ list_del_rcu(&ft->list);
+}
diff --git a/net/bridge/br_mrp.c b/net/bridge/br_mrp.c
index b36689e6e7cb..0428e1785041 100644
--- a/net/bridge/br_mrp.c
+++ b/net/bridge/br_mrp.c
@@ -6,6 +6,13 @@
static const u8 mrp_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x1 };
static const u8 mrp_in_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x3 };

+static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb);
+
+static struct br_frame_type mrp_frame_type __read_mostly = {
+ .type = cpu_to_be16(ETH_P_MRP),
+ .func = br_mrp_process,
+};
+
static bool br_mrp_is_ring_port(struct net_bridge_port *p_port,
struct net_bridge_port *s_port,
struct net_bridge_port *port)
@@ -445,6 +452,9 @@ static void br_mrp_del_impl(struct net_bridge *br, struct br_mrp *mrp)

list_del_rcu(&mrp->list);
kfree_rcu(mrp, rcu);
+
+ if (list_empty(&br->mrp_list))
+ br_del_frame(br, &mrp_frame_type);
}

/* Adds a new MRP instance.
@@ -493,6 +503,9 @@ int br_mrp_add(struct net_bridge *br, struct br_mrp_instance *instance)
spin_unlock_bh(&br->lock);
rcu_assign_pointer(mrp->s_port, p);

+ if (list_empty(&br->mrp_list))
+ br_add_frame(br, &mrp_frame_type);
+
INIT_DELAYED_WORK(&mrp->test_work, br_mrp_test_work_expired);
INIT_DELAYED_WORK(&mrp->in_test_work, br_mrp_in_test_work_expired);
list_add_tail_rcu(&mrp->list, &br->mrp_list);
@@ -1172,15 +1185,13 @@ static int br_mrp_rcv(struct net_bridge_port *p,
* normal forwarding.
* note: already called with rcu_read_lock
*/
-int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
+static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
{
/* If there is no MRP instance do normal forwarding */
if (likely(!(p->flags & BR_MRP_AWARE)))
goto out;

- if (unlikely(skb->protocol == htons(ETH_P_MRP)))
- return br_mrp_rcv(p, skb, p->dev);
-
+ return br_mrp_rcv(p, skb, p->dev);
out:
return 0;
}
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index baa1500f384f..e67c6d9e8bea 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -89,6 +89,13 @@ struct bridge_mcast_stats {
};
#endif

+struct br_frame_type {
+ __be16 type;
+ int (*func)(struct net_bridge_port *port,
+ struct sk_buff *skb);
+ struct list_head list;
+};
+
struct br_vlan_stats {
u64 rx_bytes;
u64 rx_packets;
@@ -433,6 +440,8 @@ struct net_bridge {
#endif
struct hlist_head fdb_list;

+ struct list_head ftype_list;
+
#if IS_ENABLED(CONFIG_BRIDGE_MRP)
struct list_head mrp_list;
#endif
@@ -708,6 +717,9 @@ int nbp_backup_change(struct net_bridge_port *p, struct net_device *backup_dev);
int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb);
rx_handler_func_t *br_get_rx_handler(const struct net_device *dev);

+void br_add_frame(struct net_bridge *br, struct br_frame_type *ft);
+void br_del_frame(struct net_bridge *br, struct br_frame_type *ft);
+
static inline bool br_rx_handler_check_rcu(const struct net_device *dev)
{
return rcu_dereference(dev->rx_handler) == br_get_rx_handler(dev);
@@ -1320,7 +1332,6 @@ extern int (*br_fdb_test_addr_hook)(struct net_device *dev, unsigned char *addr)
#if IS_ENABLED(CONFIG_BRIDGE_MRP)
int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p,
struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
-int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb);
bool br_mrp_enabled(struct net_bridge *br);
void br_mrp_port_del(struct net_bridge *br, struct net_bridge_port *p);
int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br);
@@ -1332,11 +1343,6 @@ static inline int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p,
return -EOPNOTSUPP;
}

-static inline int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
-{
- return 0;
-}
-
static inline bool br_mrp_enabled(struct net_bridge *br)
{
return false;
--
2.28.0

2020-09-04 09:20:39

by Henrik Bjoernlund

[permalink] [raw]
Subject: [PATCH RFC 2/7] bridge: cfm: Add BRIDGE_CFM to Kconfig.

This makes it possible to include or exclude the CFM
protocol according to 802.1Q section 12.14.

Signed-off-by: Henrik Bjoernlund <[email protected]>
---
net/bridge/Kconfig | 11 +++++++++++
net/bridge/br_device.c | 3 +++
net/bridge/br_private.h | 3 +++
3 files changed, 17 insertions(+)

diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig
index 80879196560c..3c8ded7d3e84 100644
--- a/net/bridge/Kconfig
+++ b/net/bridge/Kconfig
@@ -73,3 +73,14 @@ config BRIDGE_MRP
Say N to exclude this support and reduce the binary size.

If unsure, say N.
+
+config BRIDGE_CFM
+ bool "CFM protocol"
+ depends on BRIDGE
+ help
+ If you say Y here, then the Ethernet bridge will be able to run CFM
+ protocol according to 802.1Q section 12.14
+
+ Say N to exclude this support and reduce the binary size.
+
+ If unsure, say N.
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index a9232db03108..d12f5626a4b1 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -476,6 +476,9 @@ void br_dev_setup(struct net_device *dev)
INIT_LIST_HEAD(&br->ftype_list);
#if IS_ENABLED(CONFIG_BRIDGE_MRP)
INIT_LIST_HEAD(&br->mrp_list);
+#endif
+#if IS_ENABLED(CONFIG_BRIDGE_CFM)
+ INIT_LIST_HEAD(&br->mep_list);
#endif
spin_lock_init(&br->hash_lock);

diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index e67c6d9e8bea..6294a3e51a33 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -445,6 +445,9 @@ struct net_bridge {
#if IS_ENABLED(CONFIG_BRIDGE_MRP)
struct list_head mrp_list;
#endif
+#if IS_ENABLED(CONFIG_BRIDGE_CFM)
+ struct list_head mep_list;
+#endif
};

struct br_input_skb_cb {
--
2.28.0

2020-09-04 09:20:47

by Henrik Bjoernlund

[permalink] [raw]
Subject: [PATCH RFC 4/7] bridge: cfm: Kernel space implementation of CFM.

This is the implementation of the CFM protocol according to
802.1Q section 12.14.

Connectivity Fault Management (CFM) comprises capabilities for
detecting, verifying, and isolating connectivity failures in
Virtual Bridged Networks. These capabilities can be used in
networks operated by multiple independent organizations, each
with restricted management access to each other’s equipment.

CFM functions are partitioned as follows:
- Path discovery
- Fault detection
- Fault verification and isolation
- Fault notification
- Fault recovery

Interface consists of these functions:
br_cfm_mep_create()
br_cfm_mep_delete()
br_cfm_mep_config_set()
br_cfm_mep_status_get()
br_cfm_mep_counters_get()
br_cfm_mep_counters_clear()
br_cfm_cc_config_set()
br_cfm_cc_peer_mep_add()
br_cfm_cc_peer_mep_remove()
br_cfm_cc_rdi_set()
br_cfm_cc_ccm_tx()
br_cfm_cc_status_get()
br_cfm_cc_counters_get()
br_cfm_cc_counters_clear()
br_cfm_cc_peer_status_get()

A MEP instance is created by br_cfm_mep_create()
-It is the Maintenance association End Point
described in 802.1Q section 19.2.
-It is created on a specific level (1-7) and is assuring
that no CFM frames are passing through this MEP on lower levels.
-It initiates and validates CFM frames on its level.
-It can only exist on a port that is related to a bridge.
-Attributes given cannot be changed until the instance is
deleted.

A MEP instance can be deleted by br_cfm_mep_delete().

A created MEP instance has attributes that can be
configured by br_cfm_mep_config_set().

A MEP contain status and counter information that can be
retrieved by br_cfm_mep_status_get() and
br_cfm_mep_counters_get().

A MEP counters can be cleared by br_cfm_mep_counters_clear().

A MEP Continuity Check feature can be configured by
br_cfm_cc_config_set()
The Continuity Check Receiver state machine can be
enabled and disabled.
According to 802.1Q section 19.2.8

A MEP can have Peer MEPs added and removed by
br_cfm_cc_peer_mep_add() and br_cfm_cc_peer_mep_remove()
The Continuity Check feature can maintain connectivity
status on each added Peer MEP.

A MEP can be configured to start or stop transmission of CCM frames by
br_cfm_cc_ccm_tx()
The CCM will be transmitted for a selected period in seconds.
Must call this function before timeout to keep transmission alive.

A MEP transmitting CCM can be configured with inserted RDI in PDU by
br_cfm_cc_rdi_set()

A MEP contain Continuity Check status and counter information
that can be retrieved by br_cfm_cc_status_get() and
br_cfm_cc_counters_get().

A MEP Continuity Check counters can be cleared
by br_cfm_cc_counters_clear().

A MEP contain Peer MEP Continuity Check status information that
can be retrieved by br_cfm_cc_peer_status_get().

Signed-off-by: Henrik Bjoernlund <[email protected]>
---
include/uapi/linux/cfm_bridge.h | 75 +++
net/bridge/Makefile | 2 +
net/bridge/br_cfm.c | 880 ++++++++++++++++++++++++++++++++
net/bridge/br_private.h | 16 +
net/bridge/br_private_cfm.h | 242 +++++++++
5 files changed, 1215 insertions(+)
create mode 100644 include/uapi/linux/cfm_bridge.h
create mode 100644 net/bridge/br_cfm.c
create mode 100644 net/bridge/br_private_cfm.h

diff --git a/include/uapi/linux/cfm_bridge.h b/include/uapi/linux/cfm_bridge.h
new file mode 100644
index 000000000000..389ea1e1f68e
--- /dev/null
+++ b/include/uapi/linux/cfm_bridge.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+
+#ifndef _UAPI_LINUX_CFM_BRIDGE_H_
+#define _UAPI_LINUX_CFM_BRIDGE_H_
+
+#include <linux/types.h>
+#include <linux/if_ether.h>
+
+#define ETHER_HEADER_LENGTH (6+6+4+2)
+#define CFM_MAID_LENGTH 48
+#define CFM_CCM_PDU_LENGTH 75
+#define CFM_PORT_STATUS_TLV_LENGTH 4
+#define CFM_IF_STATUS_TLV_LENGTH 4
+#define CFM_IF_STATUS_TLV_TYPE 4
+#define CFM_PORT_STATUS_TLV_TYPE 2
+#define CFM_ENDE_TLV_TYPE 0
+#define CFM_CCM_MAX_FRAME_LENGTH (ETHER_HEADER_LENGTH+\
+ CFM_CCM_PDU_LENGTH+\
+ CFM_PORT_STATUS_TLV_LENGTH+\
+ CFM_IF_STATUS_TLV_LENGTH)
+#define CFM_FRAME_PRIO 7
+#define CFM_CCM_OPCODE 1
+#define CFM_CCM_TLV_OFFSET 70
+#define CFM_CCM_PDU_MAID_OFFSET 10
+#define CFM_CCM_PDU_MEPID_OFFSET 8
+#define CFM_CCM_PDU_SEQNR_OFFSET 4
+#define CFM_CCM_PDU_TLV_OFFSET 74
+#define CFM_CCM_ITU_RESERVED_SIZE 16
+
+struct br_cfm_common_hdr {
+ __u8 mdlevel_version;
+ __u8 opcode;
+ __u8 flags;
+ __u8 tlv_offset;
+};
+
+struct br_cfm_status_tlv {
+ __u8 type;
+ __be16 length;
+ __u8 value;
+};
+
+enum br_cfm_opcodes {
+ BR_CFM_OPCODE_CCM = 0x1,
+ BR_CFM_OPCODE_LBR = 0x2,
+ BR_CFM_OPCODE_LBM = 0x3,
+ BR_CFM_OPCODE_LTR = 0x4,
+ BR_CFM_OPCODE_LTM = 0x5,
+};
+
+/* MEP domain */
+enum br_cfm_domain {
+ BR_CFM_PORT,
+ BR_CFM_VLAN,
+};
+
+/* MEP direction */
+enum br_cfm_mep_direction {
+ BR_CFM_MEP_DIRECTION_DOWN,
+ BR_CFM_MEP_DIRECTION_UP,
+};
+
+/* CCM interval supported. */
+enum br_cfm_ccm_interval {
+ BR_CFM_CCM_INTERVAL_NONE,
+ BR_CFM_CCM_INTERVAL_3_3_MS,
+ BR_CFM_CCM_INTERVAL_10_MS,
+ BR_CFM_CCM_INTERVAL_100_MS,
+ BR_CFM_CCM_INTERVAL_1_SEC,
+ BR_CFM_CCM_INTERVAL_10_SEC,
+ BR_CFM_CCM_INTERVAL_1_MIN,
+ BR_CFM_CCM_INTERVAL_10_MIN,
+};
+
+#endif
diff --git a/net/bridge/Makefile b/net/bridge/Makefile
index ccb394236fbd..ddc0a9192348 100644
--- a/net/bridge/Makefile
+++ b/net/bridge/Makefile
@@ -27,3 +27,5 @@ bridge-$(CONFIG_NET_SWITCHDEV) += br_switchdev.o
obj-$(CONFIG_NETFILTER) += netfilter/

bridge-$(CONFIG_BRIDGE_MRP) += br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o
+
+bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o
diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
new file mode 100644
index 000000000000..e38cc3e8f262
--- /dev/null
+++ b/net/bridge/br_cfm.c
@@ -0,0 +1,880 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/cfm_bridge.h>
+#include <uapi/linux/cfm_bridge.h>
+#include "br_private_cfm.h"
+
+static struct br_cfm_mep *br_mep_find(struct net_bridge *br, u32 instance)
+{
+ struct br_cfm_mep *res = NULL;
+ struct br_cfm_mep *mep;
+
+ list_for_each_entry_rcu(mep, &br->mep_list, head,
+ lockdep_rtnl_is_held()) {
+ if (mep->instance == instance) {
+ res = mep;
+ break;
+ }
+ }
+
+ return res;
+}
+
+static struct br_cfm_mep *br_mep_find_ifindex(struct net_bridge *br,
+ u32 ifindex)
+{
+ struct br_cfm_mep *res = NULL;
+ struct br_cfm_mep *mep;
+
+ list_for_each_entry_rcu(mep, &br->mep_list, head,
+ lockdep_rtnl_is_held()) {
+ if (mep->create.ifindex == ifindex) {
+ res = mep;
+ break;
+ }
+ }
+
+ return res;
+}
+
+static struct br_cfm_peer_mep *br_peer_mep_find(struct br_cfm_mep *mep,
+ u32 mepid)
+{
+ struct br_cfm_peer_mep *res = NULL;
+ struct br_cfm_peer_mep *peer_mep;
+
+ list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
+ lockdep_rtnl_is_held()) {
+ if (peer_mep->mepid == mepid) {
+ res = peer_mep;
+ break;
+ }
+ }
+
+ return res;
+}
+
+static struct net_bridge_port *br_mep_get_port(struct net_bridge *br,
+ u32 ifindex)
+{
+ struct net_bridge_port *res = NULL;
+ struct net_bridge_port *port;
+
+ list_for_each_entry(port, &br->port_list, list) {
+ if (port->dev->ifindex == ifindex) {
+ res = port;
+ break;
+ }
+ }
+
+ return res;
+}
+
+/* Calculate the CCM interval in us. */
+static u32 interval_to_us(enum br_cfm_ccm_interval interval)
+{
+ switch (interval) {
+ case BR_CFM_CCM_INTERVAL_NONE:
+ return 0;
+ case BR_CFM_CCM_INTERVAL_3_3_MS:
+ return 3300;
+ case BR_CFM_CCM_INTERVAL_10_MS:
+ return 10 * 1000;
+ case BR_CFM_CCM_INTERVAL_100_MS:
+ return 100 * 1000;
+ case BR_CFM_CCM_INTERVAL_1_SEC:
+ return 1000 * 1000;
+ case BR_CFM_CCM_INTERVAL_10_SEC:
+ return 10 * 1000 * 1000;
+ case BR_CFM_CCM_INTERVAL_1_MIN:
+ return 60 * 1000 * 1000;
+ case BR_CFM_CCM_INTERVAL_10_MIN:
+ return 10 * 60 * 1000 * 1000;
+ }
+ return 0;
+}
+
+/* Convert the interface interval to CCM PDU value. */
+static u32 interval_to_pdu(enum br_cfm_ccm_interval interval)
+{
+ switch (interval) {
+ case BR_CFM_CCM_INTERVAL_NONE:
+ return 0;
+ case BR_CFM_CCM_INTERVAL_3_3_MS:
+ return 1;
+ case BR_CFM_CCM_INTERVAL_10_MS:
+ return 2;
+ case BR_CFM_CCM_INTERVAL_100_MS:
+ return 3;
+ case BR_CFM_CCM_INTERVAL_1_SEC:
+ return 4;
+ case BR_CFM_CCM_INTERVAL_10_SEC:
+ return 5;
+ case BR_CFM_CCM_INTERVAL_1_MIN:
+ return 6;
+ case BR_CFM_CCM_INTERVAL_10_MIN:
+ return 7;
+ }
+ return 0;
+}
+
+/* Convert the CCM PDU value to interval on interface. */
+static u32 pdu_to_interval(u32 value)
+{
+ switch (value) {
+ case 0:
+ return BR_CFM_CCM_INTERVAL_NONE;
+ case 1:
+ return BR_CFM_CCM_INTERVAL_3_3_MS;
+ case 2:
+ return BR_CFM_CCM_INTERVAL_10_MS;
+ case 3:
+ return BR_CFM_CCM_INTERVAL_100_MS;
+ case 4:
+ return BR_CFM_CCM_INTERVAL_1_SEC;
+ case 5:
+ return BR_CFM_CCM_INTERVAL_10_SEC;
+ case 6:
+ return BR_CFM_CCM_INTERVAL_1_MIN;
+ case 7:
+ return BR_CFM_CCM_INTERVAL_10_MIN;
+ }
+ return BR_CFM_CCM_INTERVAL_NONE;
+}
+
+static void ccm_rx_timer_start(struct br_cfm_peer_mep *peer_mep)
+{
+ u32 interval_us;
+
+ interval_us = interval_to_us(peer_mep->mep->cc_config.exp_interval);
+ /* Function ccm_rx_dwork must be called with 1/4
+ * of the configured CC 'expected_interval'
+ * in order to detect CCM defect after 3.25 interval.
+ */
+ queue_delayed_work(system_wq, &peer_mep->ccm_rx_dwork,
+ usecs_to_jiffies(interval_us / 4));
+}
+
+static void cc_peer_enable(struct br_cfm_peer_mep *peer_mep)
+{
+ memset(&peer_mep->cc_status, 0, sizeof(peer_mep->cc_status));
+ peer_mep->ccm_rx_count_miss = 0;
+
+ ccm_rx_timer_start(peer_mep);
+}
+
+static void cc_peer_disable(struct br_cfm_peer_mep *peer_mep)
+{
+ cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork);
+}
+
+static struct sk_buff *ccm_frame_build(struct br_cfm_mep *mep,
+ const struct br_cfm_cc_ccm_tx_info *const tx_info)
+
+{
+ struct br_cfm_common_hdr *common_hdr;
+ struct net_bridge_port *b_port;
+ struct br_cfm_maid *maid;
+ u8 *itu_reserved, *e_tlv;
+ struct ethhdr *eth_hdr;
+ struct sk_buff *skb;
+ __be32 *status_tlv;
+ __be32 *snumber;
+ __be16 *mepid;
+
+ rcu_read_lock();
+ b_port = rcu_dereference(mep->b_port);
+ if (!b_port)
+ return NULL;
+ skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH);
+ if (!skb)
+ return NULL;
+
+ skb->dev = b_port->dev;
+ rcu_read_unlock();
+ skb->protocol = htons(ETH_P_CFM);
+ skb->priority = CFM_FRAME_PRIO;
+
+ /* Ethernet header */
+ eth_hdr = skb_put(skb, sizeof(*eth_hdr));
+ ether_addr_copy(eth_hdr->h_dest, tx_info->dmac.addr);
+ ether_addr_copy(eth_hdr->h_source, mep->config.unicast_mac.addr);
+ eth_hdr->h_proto = htons(ETH_P_CFM);
+
+ /* Common CFM Header */
+ common_hdr = skb_put(skb, sizeof(*common_hdr));
+ common_hdr->mdlevel_version = mep->config.mdlevel << 5;
+ common_hdr->opcode = CFM_CCM_OPCODE;
+ common_hdr->flags = (mep->rdi << 7) |
+ interval_to_pdu(mep->cc_config.exp_interval);
+ common_hdr->tlv_offset = CFM_CCM_TLV_OFFSET;
+
+ /* Sequence number */
+ snumber = skb_put(skb, sizeof(*snumber));
+ if (tx_info->seq_no_update) {
+ *snumber = cpu_to_be32(mep->ccm_tx_snumber);
+ mep->ccm_tx_snumber += 1;
+ } else {
+ *snumber = 0;
+ }
+
+ /* MEP ID */
+ mepid = skb_put(skb, sizeof(*mepid));
+ *mepid = cpu_to_be16((u16)mep->config.mepid);
+
+ /* MA ID */
+ maid = skb_put(skb, sizeof(*maid));
+ memcpy(maid->data, mep->cc_config.exp_maid.data, sizeof(maid->data));
+
+ /* ITU reserved (CFM_CCM_ITU_RESERVED_SIZE octets) */
+ itu_reserved = skb_put(skb, CFM_CCM_ITU_RESERVED_SIZE);
+ memset(itu_reserved, 0, CFM_CCM_ITU_RESERVED_SIZE);
+
+ /* Generel CFM TLV format:
+ * TLV type: one byte
+ * TLV value length: two bytes
+ * TLV value: 'TLV value length' bytes
+ */
+
+ /* Port status TLV. The value length is 1. Total of 4 bytes. */
+ if (tx_info->port_tlv) {
+ status_tlv = skb_put(skb, sizeof(*status_tlv));
+ *status_tlv = cpu_to_be32((CFM_PORT_STATUS_TLV_TYPE << 24) |
+ (1 << 8) | /* Value length */
+ (tx_info->port_tlv_value & 0xFF));
+ }
+
+ /* Interface status TLV. The value length is 1. Total of 4 bytes. */
+ if (tx_info->if_tlv) {
+ status_tlv = skb_put(skb, sizeof(*status_tlv));
+ *status_tlv = cpu_to_be32((CFM_IF_STATUS_TLV_TYPE << 24) |
+ (1 << 8) | /* Value length */
+ (tx_info->if_tlv_value & 0xFF));
+ }
+
+ /* End TLV */
+ e_tlv = skb_put(skb, sizeof(*e_tlv));
+ *e_tlv = CFM_ENDE_TLV_TYPE;
+
+ return skb;
+}
+
+static void ccm_frame_tx(struct sk_buff *skb)
+{
+ skb_reset_network_header(skb);
+ dev_queue_xmit(skb);
+}
+
+/* This function is called with the configured CC 'expected_interval'
+ * in order to drive CCM transmission when enabled.
+ */
+static void ccm_tx_work_expired(struct work_struct *work)
+{
+ struct delayed_work *del_work;
+ struct br_cfm_mep *mep;
+ struct sk_buff *skb;
+ u32 interval_us;
+
+ del_work = to_delayed_work(work);
+ mep = container_of(del_work, struct br_cfm_mep, ccm_tx_dwork);
+
+ if (time_before_eq(mep->ccm_tx_end, jiffies)) {
+ /* Transmission period has ended */
+ mep->cc_ccm_tx_info.period = 0;
+ return;
+ }
+
+ skb = ccm_frame_build(mep, &mep->cc_ccm_tx_info);
+ if (skb)
+ ccm_frame_tx(skb);
+
+ interval_us = interval_to_us(mep->cc_config.exp_interval);
+ queue_delayed_work(system_wq, &mep->ccm_tx_dwork,
+ usecs_to_jiffies(interval_us));
+}
+
+/* This function is called with 1/4 of the configured CC 'expected_interval'
+ * in order to detect CCM defect after 3.25 interval.
+ */
+static void ccm_rx_work_expired(struct work_struct *work)
+{
+ struct br_cfm_peer_mep *peer_mep;
+ struct delayed_work *del_work;
+
+ del_work = to_delayed_work(work);
+ peer_mep = container_of(del_work, struct br_cfm_peer_mep, ccm_rx_dwork);
+
+ /* After 13 counts (4 * 3,25) then 3.25 intervals are expired */
+ if (peer_mep->ccm_rx_count_miss < 13) {
+ /* 3.25 intervals are NOT expired without CCM reception */
+ peer_mep->ccm_rx_count_miss++;
+
+ /* Start timer again */
+ ccm_rx_timer_start(peer_mep);
+ } else {
+ /* 3.25 intervals are expired without CCM reception.
+ * CCM defect detected
+ */
+ peer_mep->cc_status.ccm_defect = true;
+
+ /* Change in CCM defect status - notify */
+ }
+}
+
+static u32 ccm_tlv_extract(struct sk_buff *skb, u32 index,
+ struct br_cfm_peer_mep *peer_mep)
+{
+ __be32 *s_tlv;
+ __be32 _s_tlv;
+ u32 h_s_tlv;
+ u8 *e_tlv;
+ u8 _e_tlv;
+
+ e_tlv = skb_header_pointer(skb, index, sizeof(_e_tlv), &_e_tlv);
+ if (!e_tlv)
+ return 0;
+
+ /* TLV is present - get the status TLV */
+ s_tlv = skb_header_pointer(skb,
+ index,
+ sizeof(_s_tlv), &_s_tlv);
+ if (!s_tlv)
+ return 0;
+
+ h_s_tlv = ntohl(*s_tlv);
+ if ((h_s_tlv >> 24) == CFM_IF_STATUS_TLV_TYPE) {
+ /* Interface status TLV */
+ peer_mep->cc_status.tlv_seen = true;
+ peer_mep->cc_status.if_tlv_value = (h_s_tlv & 0xFF);
+ }
+
+ if ((h_s_tlv >> 24) == CFM_PORT_STATUS_TLV_TYPE) {
+ /* Port status TLV */
+ peer_mep->cc_status.tlv_seen = true;
+ peer_mep->cc_status.port_tlv_value = (h_s_tlv & 0xFF);
+ }
+
+ /* The Sender ID TLV is not handled */
+ /* The Organization-Specific TLV is not handled */
+
+ /* Return the length of this tlv.
+ * This is the length of the value field plus 3 bytes for size of type
+ * field and length field
+ */
+ return ((h_s_tlv >> 8) & 0xFFFF) + 3;
+}
+
+/* note: already called with rcu_read_lock */
+static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb)
+{
+ u32 mdlevel, interval, size, index, max;
+ const struct br_cfm_common_hdr *hdr;
+ struct br_cfm_peer_mep *peer_mep;
+ const struct br_cfm_maid *maid;
+ struct br_cfm_common_hdr _hdr;
+ struct br_cfm_maid _maid;
+ struct br_cfm_mep *mep;
+ struct net_bridge *br;
+ __be32 *snumber;
+ __be32 _snumber;
+ __be16 *mepid;
+ __be16 _mepid;
+
+ /* If port is disabled don't accept any frames */
+ if (port->state == BR_STATE_DISABLED)
+ return 0;
+
+ hdr = skb_header_pointer(skb, 0, sizeof(_hdr), &_hdr);
+ if (!hdr)
+ return 1;
+
+ br = port->br;
+ mep = br_mep_find_ifindex(br, port->dev->ifindex);
+ if (unlikely(!mep))
+ /* No MEP on this port - must be forwarded */
+ return 0;
+
+ mdlevel = hdr->mdlevel_version >> 5;
+ if (mdlevel > mep->config.mdlevel)
+ /* The level is above this MEP level - must be forwarded */
+ return 0;
+
+ if ((hdr->mdlevel_version & 0x1F) != 0) {
+ /* Invalid version */
+ mep->status.version_unexp_seen = true;
+ return 1;
+ }
+
+ if (mdlevel < mep->config.mdlevel) {
+ /* The level is below this MEP level */
+ mep->status.rx_level_low_seen = true;
+ return 1;
+ }
+
+ if (hdr->opcode == BR_CFM_OPCODE_CCM) {
+ /* CCM PDU received. */
+ /* MA ID is after common header + sequence number + MEP ID */
+ maid = skb_header_pointer(skb,
+ CFM_CCM_PDU_MAID_OFFSET,
+ sizeof(_maid), &_maid);
+ if (!maid)
+ return 1;
+ if (memcmp(maid->data, mep->cc_config.exp_maid.data,
+ sizeof(maid->data)))
+ /* MA ID not as expected */
+ return 1;
+
+ /* MEP ID is after common header + sequence number */
+ mepid = skb_header_pointer(skb,
+ CFM_CCM_PDU_MEPID_OFFSET,
+ sizeof(_mepid), &_mepid);
+ if (!mepid)
+ return 1;
+ peer_mep = br_peer_mep_find(mep, (u32)ntohs(*mepid));
+ if (!peer_mep)
+ return 1;
+
+ /* Interval is in common header flags */
+ interval = hdr->flags & 0x07;
+ if (mep->cc_config.exp_interval != pdu_to_interval(interval))
+ /* Interval not as expected */
+ return 1;
+
+ /* A valid CCM frame is received */
+ if (peer_mep->cc_status.ccm_defect) {
+ peer_mep->cc_status.ccm_defect = false;
+
+ /* Change in CCM defect status - notify */
+
+ /* Start CCM RX timer */
+ ccm_rx_timer_start(peer_mep);
+ }
+
+ peer_mep->cc_status.seen = true;
+ peer_mep->ccm_rx_count_miss = 0;
+
+ /* RDI is in common header flags */
+ peer_mep->cc_status.rdi = (hdr->flags & 0x80) ? true : false;
+
+ /* Sequence number is after common header */
+ snumber = skb_header_pointer(skb,
+ CFM_CCM_PDU_SEQNR_OFFSET,
+ sizeof(_snumber), &_snumber);
+ if (!snumber)
+ return 1;
+ if (ntohl(*snumber) != (mep->ccm_rx_snumber + 1))
+ /* Unexpected sequence number */
+ peer_mep->cc_status.seq_unexp_seen = true;
+
+ mep->ccm_rx_snumber = ntohl(*snumber);
+
+ /* TLV end is after common header + sequence number + MEP ID +
+ * MA ID + ITU reserved
+ */
+ index = CFM_CCM_PDU_TLV_OFFSET;
+ max = 0;
+ do { /* Handle all TLVs */
+ size = ccm_tlv_extract(skb, index, peer_mep);
+ index += size;
+ max += 1;
+ } while (size != 0 && max < 4); /* Max four TLVs possible */
+
+ return 1;
+ }
+
+ mep->status.opcode_unexp_seen = true;
+
+ return 1;
+}
+
+static struct br_frame_type cfm_frame_type __read_mostly = {
+ .type = cpu_to_be16(ETH_P_CFM),
+ .func = br_cfm_frame_rx,
+};
+
+/* note: already called with rtnl_lock */
+int br_cfm_mep_create(struct net_bridge *br,
+ const u32 instance,
+ struct br_cfm_mep_create *const create,
+ struct netlink_ext_ack *extack)
+{
+ struct net_bridge_port *p;
+ struct br_cfm_mep *mep;
+
+ if (create->domain == BR_CFM_VLAN) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "VLAN domain not supported");
+ return -EINVAL;
+ }
+ if (create->domain != BR_CFM_PORT) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Invalid domain value");
+ return -EINVAL;
+ }
+ if (create->direction == BR_CFM_MEP_DIRECTION_UP) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Up-MEP not supported");
+ return -EINVAL;
+ }
+ if (create->direction != BR_CFM_MEP_DIRECTION_DOWN) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Invalid direction value");
+ return -EINVAL;
+ }
+ if (!br_mep_get_port(br, create->ifindex))
+ return -EINVAL;
+
+ mep = br_mep_find(br, instance);
+ if (mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance already created");
+ return -EINVAL;
+ }
+
+ /* In PORT domain only one instance can be created per port */
+ if (create->domain == BR_CFM_PORT) {
+ mep = br_mep_find_ifindex(br, create->ifindex);
+ if (mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Only one Port MEP on a port allowed");
+ return -EINVAL;
+ }
+ }
+
+ mep = kzalloc(sizeof(*mep), GFP_KERNEL);
+ if (!mep)
+ return -ENOMEM;
+
+ /* Save create parameters */
+ mep->create = *create;
+ mep->instance = instance;
+ p = br_mep_get_port(br, mep->create.ifindex);
+ rcu_assign_pointer(mep->b_port, p);
+
+ INIT_LIST_HEAD(&mep->peer_mep_list);
+ INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired);
+
+ if (list_empty(&br->mep_list))
+ br_add_frame(br, &cfm_frame_type);
+
+ /* Add instance to list */
+ list_add_tail_rcu(&mep->head, &br->mep_list);
+
+ return 0;
+}
+
+static void mep_delete_implementation(struct net_bridge *br,
+ struct br_cfm_mep *mep)
+{
+ struct br_cfm_peer_mep *peer_mep;
+
+ /* Empty and free peer MEP list */
+ list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
+ lockdep_rtnl_is_held()) {
+ cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork);
+ list_del_rcu(&peer_mep->head);
+ kfree_rcu(peer_mep, rcu);
+ }
+
+ /* Stop transmitting */
+ cancel_delayed_work_sync(&mep->ccm_tx_dwork);
+
+ /* Free the MEP instance */
+ rcu_assign_pointer(mep->b_port, NULL);
+ list_del_rcu(&mep->head);
+ kfree_rcu(mep, rcu);
+
+ if (list_empty(&br->mep_list))
+ br_del_frame(br, &cfm_frame_type);
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_mep_delete(struct net_bridge *br,
+ const u32 instance,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_mep *mep;
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+
+ mep_delete_implementation(br, mep);
+
+ return 0;
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_mep_config_set(struct net_bridge *br,
+ const u32 instance,
+ const struct br_cfm_mep_config *const config,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_mep *mep;
+
+ if (config->mdlevel > 7) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "mdlevel is invalid");
+ return -EINVAL;
+ }
+ if (config->mepid > 0x1FFF) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "mepid is invalid");
+ return -EINVAL;
+ }
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+
+ /* Save config parameters */
+ mep->config = *config;
+
+ return 0;
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_mep_counters_clear(struct net_bridge *br,
+ const u32 instance,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_mep *mep;
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+
+ memset(&mep->counters, 0, sizeof(mep->counters));
+
+ return 0;
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_cc_config_set(struct net_bridge *br,
+ const u32 instance,
+ const struct br_cfm_cc_config *const config,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_peer_mep *peer_mep;
+ struct br_cfm_mep *mep;
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+
+ /* Check for no change in configuration */
+ if (memcmp(config, &mep->cc_config, sizeof(*config)) == 0)
+ return 0;
+
+ if (config->enable && !mep->cc_config.enable)
+ /* CC is enabled */
+ list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
+ lockdep_rtnl_is_held())
+ cc_peer_enable(peer_mep);
+
+ if (!config->enable && mep->cc_config.enable)
+ /* CC is disabled */
+ list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
+ lockdep_rtnl_is_held())
+ cc_peer_disable(peer_mep);
+
+ /* Save new cc_config parameters */
+ mep->cc_config = *config;
+
+ return 0;
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
+ u32 mepid,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_peer_mep *peer_mep;
+ struct br_cfm_mep *mep;
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+ if (mepid > 0x1FFF) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "mepid is invalid");
+ return -EINVAL;
+ }
+
+ peer_mep = br_peer_mep_find(mep, mepid);
+ if (peer_mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Peer MEP-ID already added");
+ return -EINVAL;
+ }
+
+ peer_mep = kzalloc(sizeof(*peer_mep), GFP_KERNEL);
+ if (!peer_mep)
+ return -ENOMEM;
+
+ peer_mep->mepid = mepid;
+ peer_mep->mep = mep;
+ INIT_DELAYED_WORK(&peer_mep->ccm_rx_dwork, ccm_rx_work_expired);
+
+ if (mep->cc_config.enable)
+ /* CC is enabled */
+ cc_peer_enable(peer_mep);
+
+ /* Add Peer MEP to list */
+ list_add_tail_rcu(&peer_mep->head, &mep->peer_mep_list);
+
+ return 0;
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
+ u32 mepid,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_peer_mep *peer_mep;
+ struct br_cfm_mep *mep;
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+
+ peer_mep = br_peer_mep_find(mep, mepid);
+ if (!peer_mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Peer MEP-ID already deleted");
+ return -EINVAL;
+ }
+
+ cc_peer_disable(peer_mep);
+
+ list_del_rcu(&peer_mep->head);
+ kfree_rcu(peer_mep, rcu);
+
+ return 0;
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
+ const bool rdi, struct netlink_ext_ack *extack)
+{
+ struct br_cfm_mep *mep;
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+
+ /* Save rdi parameters */
+ mep->rdi = rdi;
+
+ return 0;
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
+ const struct br_cfm_cc_ccm_tx_info *const tx_info,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_mep *mep;
+ struct sk_buff *skb;
+ u32 interval_us;
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+
+ if (memcmp(tx_info, &mep->cc_ccm_tx_info, sizeof(*tx_info)) == 0) {
+ /* No change in tx_info. */
+ if (mep->cc_ccm_tx_info.period == 0)
+ /* Transmission is not enabled - just return */
+ return 0;
+
+ /* Transmission is ongoing, the end time is recalculated */
+ mep->ccm_tx_end = jiffies +
+ usecs_to_jiffies(tx_info->period * 1000000);
+ return 0;
+ }
+
+ if (tx_info->period == 0 && mep->cc_ccm_tx_info.period == 0)
+ /* Some change in info and transmission is not ongoing */
+ goto save;
+
+ if (tx_info->period != 0 && mep->cc_ccm_tx_info.period != 0) {
+ /* Some change in info and transmission is ongoing */
+ /* The end time is recalculated */
+ mep->ccm_tx_end = jiffies +
+ usecs_to_jiffies(tx_info->period * 1000000);
+
+ goto save;
+ }
+
+ if (tx_info->period == 0 && mep->cc_ccm_tx_info.period != 0) {
+ /* CCM transmission stop */
+ cancel_delayed_work_sync(&mep->ccm_tx_dwork);
+ goto save;
+ }
+
+ /* Start delayed work to transmit CCM frames */
+ mep->ccm_tx_end = jiffies + usecs_to_jiffies(tx_info->period * 1000000);
+ interval_us = interval_to_us(mep->cc_config.exp_interval);
+ queue_delayed_work(system_wq, &mep->ccm_tx_dwork,
+ usecs_to_jiffies(interval_us));
+
+ /* Send first CCM frame now */
+ mep->ccm_tx_snumber = 0;
+ skb = ccm_frame_build(mep, tx_info);
+ if (skb)
+ ccm_frame_tx(skb);
+
+save:
+ /* Save tx_info parameters */
+ mep->cc_ccm_tx_info = *tx_info;
+
+ return 0;
+}
+
+/* note: already called with rtnl_lock */
+int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_mep *mep;
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance not created");
+ return -EINVAL;
+ }
+
+ memset(&mep->cc_counters, 0, sizeof(mep->cc_counters));
+
+ return 0;
+}
+
+bool br_cfm_created(struct net_bridge *br)
+{
+ return !list_empty(&br->mep_list);
+}
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 6294a3e51a33..385a6a6aa17e 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -1363,6 +1363,22 @@ static inline int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br)

#endif

+/* br_cfm.c */
+#if IS_ENABLED(CONFIG_BRIDGE_CFM)
+int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
+bool br_cfm_created(struct net_bridge *br);
+#else
+static inline int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline bool br_cfm_created(struct net_bridge *br)
+{
+ return false;
+}
+#endif
+
/* br_netlink.c */
extern struct rtnl_link_ops br_link_ops;
int br_netlink_init(void);
diff --git a/net/bridge/br_private_cfm.h b/net/bridge/br_private_cfm.h
new file mode 100644
index 000000000000..379334a42667
--- /dev/null
+++ b/net/bridge/br_private_cfm.h
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _BR_PRIVATE_CFM_H_
+#define _BR_PRIVATE_CFM_H_
+
+#include "br_private.h"
+#include <uapi/linux/cfm_bridge.h>
+
+/* MEP create parameters */
+struct br_cfm_mep_create {
+ enum br_cfm_domain domain; /* Domain for this MEP */
+ enum br_cfm_mep_direction direction; /* Up or Down MEP direction */
+ u32 ifindex; /* Residence port */
+ u16 vid; /* Classified VID in VLAN domain */
+};
+
+/* Create/Delete a MEP. */
+int br_cfm_mep_create(struct net_bridge *br,
+ const u32 instance,
+ struct br_cfm_mep_create *const create,
+ struct netlink_ext_ack *extack);
+int br_cfm_mep_delete(struct net_bridge *br,
+ const u32 instance,
+ struct netlink_ext_ack *extack);
+
+/* MEP configuration parameters */
+struct br_cfm_mep_config {
+ u32 mdlevel;
+ u32 mepid; /* MEPID for this MEP */
+ struct mac_addr unicast_mac; /* The MEP unicast MAC */
+ /* Added tag VID in case of tagged PORT domain. Untagged if zero. */
+ u16 vid;
+};
+
+/* Set general configuration of MEP */
+int br_cfm_mep_config_set(struct net_bridge *br,
+ const u32 instance,
+ const struct br_cfm_mep_config *const config,
+ struct netlink_ext_ack *extack);
+
+/* MEP status. */
+struct br_cfm_mep_status {
+ /* Indications that an OAM PDU has been seen. */
+ /* Are cleared during call to br_cfm_status_get(). */
+ bool opcode_unexp_seen; /* RX of OAM PDU with unexpected opcode */
+ bool dmac_unexp_seen; /* RX of OAM Frame with unexpected DMAC */
+ bool tx_level_low_seen; /* TX of OAM PDU with level low blocked */
+ bool version_unexp_seen; /* RX of OAM PDU with unexpected version */
+ bool rx_level_low_seen; /* Rx of OAM PDU with level low */
+};
+
+/* MEP status get. */
+int br_cfm_mep_status_get(const u32 instance, struct br_cfm_mep_status *const status);
+
+/* MEP counters */
+struct br_cfm_mep_counters {
+ /* All the following counters are cleared during call to */
+ /* br_cfm_counters_clear(). */
+
+ /* OAM PDU Rx and Tx counters. */
+ u64 rx_counter;
+ u64 tx_counter;
+
+ /* Rx/Tx PDUs that are discarded due to filtering */
+ u64 rx_discard_counter; /* Check of MEL or DMAC or Version or CCM */
+ u64 tx_discard_counter; /* Check of MEL */
+};
+
+/* Get MEP counters. */
+int br_cfm_mep_counters_get(const u32 instance, struct br_cfm_mep_counters *counters);
+
+/* MEP counter clear. */
+int br_cfm_mep_counters_clear(struct net_bridge *br,
+ const u32 instance,
+ struct netlink_ext_ack *extack);
+
+struct br_cfm_maid {
+ u8 data[CFM_MAID_LENGTH];
+};
+
+struct br_cfm_cc_config {
+ /* Expected received CCM PDU MAID. */
+ struct br_cfm_maid exp_maid;
+
+ /* Expected received CCM PDU interval. */
+ /* Transmitting CCM PDU interval when CCM tx is enabled. */
+ enum br_cfm_ccm_interval exp_interval;
+
+ /* Expected received CCM PDU Priority */
+ u8 exp_priority;
+
+ bool enable; /* Enable/disable CCM PDU handling */
+};
+
+/* Set CC configuration of a MEP */
+int br_cfm_cc_config_set(struct net_bridge *br,
+ const u32 instance,
+ const struct br_cfm_cc_config *const config,
+ struct netlink_ext_ack *extack);
+
+/* Set CC Peer MEP ID add/remove */
+int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
+ u32 peer_mep_id,
+ struct netlink_ext_ack *extack);
+int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
+ u32 peer_mep_id,
+ struct netlink_ext_ack *extack);
+
+/* Transmitted CCM Remote Defect Indication status set.
+ * This RDI is inserted in transmitted CCM PDUs if CCM transmission is enabled.
+ * See br_cfm_cc_ccm_tx() with interval != BR_CFM_CCM_INTERVAL_NONE
+ */
+int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
+ const bool rdi, struct netlink_ext_ack *extack);
+
+/* OAM PDU Tx information */
+struct br_cfm_cc_ccm_tx_info {
+ struct mac_addr dmac;
+ u8 priority;
+ bool dei;
+ /* The CCM will be transmitted for this period in seconds.
+ * Call br_cfm_cc_ccm_tx before timeout to keep transmission alive.
+ * When period is zero any ongoing transmission will be stopped.
+ */
+ u32 period;
+
+ bool seq_no_update; /* Update Tx CCM sequence number */
+ bool if_tlv; /* Insert Interface Status TLV */
+ u8 if_tlv_value; /* Interface Status TLV value */
+ bool port_tlv; /* Insert Port Status TLV */
+ u8 port_tlv_value; /* Port Status TLV value */
+ /* Sender ID TLV ??
+ * Organization-Specific TLV ??
+ */
+};
+
+/* Transmit CCM PDU */
+int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
+ const struct br_cfm_cc_ccm_tx_info *const tx_info,
+ struct netlink_ext_ack *extack);
+
+struct br_cfm_cc_status {
+ /* (interval == 0) for last received CCM PDU */
+ bool zero_interval;
+
+ /* Unexpected MD Level received. Cleared after 3.5 interval */
+ bool mdlevel_unexp;
+
+ /* Unexpected MAID received. Cleared after 3.5 interval */
+ bool maid_unexp;
+
+ /* Unexpected MEPID received. Cleared after 3.5 interval */
+ bool mepid_unexp;
+};
+
+/* Get CC status. */
+int br_cfm_cc_status_get(const u32 instance, struct br_cfm_cc_status *const status);
+
+struct br_cfm_cc_counters {
+ /* CCM PDU RX and TX counters
+ * Are cleared during call to br_cfm_cc_counters_clear()
+ */
+ u64 rx_valid_counter;
+ u64 rx_invalid_counter;
+ u64 rx_oo_counter; /* Out of Order sequence numbers counter */
+ u64 tx_counter;
+};
+
+/* Get CC counters. */
+int br_cfm_cc_counters_get(const u32 instance, struct br_cfm_cc_counters *counters);
+
+/* CC counter clear. */
+int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
+ struct netlink_ext_ack *extack);
+
+struct br_cfm_cc_peer_status {
+ struct mac_addr unicast_mac; /* The Peer MEP unicast MAC */
+
+ /* Unexpected Interval. Cleared after 3.5 interval */
+ bool interval_unexp;
+
+ /* Unexpected Priority received. Cleared after 3.5 interval */
+ bool priority_unexp;
+
+ /* This CCM related status is based on the latest received CCM PDU. */
+ u32 rx_ifindex; /* The ingress port */
+ u8 port_tlv_value; /* Port Status TLV value */
+ u8 if_tlv_value; /* Interface Status TLV value */
+
+ /* CCM has not been received for 3.25 intervals */
+ bool ccm_defect;
+
+ /* (RDI == 1) for last received CCM PDU */
+ bool rdi;
+
+ /* Indications that a CCM PDU has been seen.
+ * Are cleared during call to br_cfm_cc_status_get()
+ */
+ bool seen; /* CCM PDU received */
+ bool tlv_seen; /* CCM PDU with TLV received */
+ /* CCM PDU with unexpected sequence number received */
+ bool seq_unexp_seen;
+};
+
+/* Get CC peer MEP status. */
+int br_cfm_cc_peer_status_get(const u32 instance, const u32 mepid,
+ struct br_cfm_cc_peer_status *const status);
+
+struct br_cfm_mep {
+ /* list header of MEP instances */
+ struct list_head head;
+ u32 instance;
+ struct br_cfm_mep_create create;
+ struct br_cfm_mep_config config;
+ struct br_cfm_cc_config cc_config;
+ struct br_cfm_cc_ccm_tx_info cc_ccm_tx_info;
+ struct br_cfm_mep_counters counters;
+ struct br_cfm_cc_counters cc_counters;
+ /* List of multiple peer MEPs */
+ struct list_head peer_mep_list;
+ struct rcu_head rcu;
+ struct net_bridge_port __rcu *b_port;
+ unsigned long ccm_tx_end;
+ struct delayed_work ccm_tx_dwork;
+ struct br_cfm_cc_status cc_status;
+ u32 ccm_tx_snumber;
+ u32 ccm_rx_snumber;
+ struct br_cfm_mep_status status;
+ bool rdi;
+};
+
+struct br_cfm_peer_mep {
+ struct list_head head;
+ struct br_cfm_mep *mep;
+ struct rcu_head rcu;
+ struct delayed_work ccm_rx_dwork;
+ u32 mepid;
+ struct br_cfm_cc_peer_status cc_status;
+ u32 ccm_rx_count_miss;
+};
+
+#endif /* _BR_PRIVATE_CFM_H_ */
--
2.28.0

2020-09-04 09:21:01

by Henrik Bjoernlund

[permalink] [raw]
Subject: [PATCH RFC 3/7] bridge: uapi: cfm: Added EtherType used by the CFM protocol.

This EtherType is used by all CFM protocal frames transmitted
according to 802.1Q section 12.14.

Signed-off-by: Henrik Bjoernlund <[email protected]>
---
include/uapi/linux/if_ether.h | 1 +
1 file changed, 1 insertion(+)

diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index d6de2b167448..a0b637911d3c 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -99,6 +99,7 @@
#define ETH_P_1588 0x88F7 /* IEEE 1588 Timesync */
#define ETH_P_NCSI 0x88F8 /* NCSI protocol */
#define ETH_P_PRP 0x88FB /* IEC 62439-3 PRP/HSRv0 */
+#define ETH_P_CFM 0x8902 /* Connectivity Fault Management */
#define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */
#define ETH_P_IBOE 0x8915 /* Infiniband over Ethernet */
#define ETH_P_TDLS 0x890D /* TDLS */
--
2.28.0

2020-09-04 09:21:12

by Henrik Bjoernlund

[permalink] [raw]
Subject: [PATCH RFC 7/7] bridge: cfm: Bridge port remove.

This is addition of CFM functionality to delete MEP instances
on a port that is removed from the bridge.
A MEP can only exist on a port that is related to a bridge.

Signed-off-by: Henrik Bjoernlund <[email protected]>
---
net/bridge/br_cfm.c | 13 +++++++++++++
net/bridge/br_if.c | 1 +
net/bridge/br_private.h | 6 ++++++
3 files changed, 20 insertions(+)

diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
index b7fed2c1d8ec..c724ce020ce3 100644
--- a/net/bridge/br_cfm.c
+++ b/net/bridge/br_cfm.c
@@ -921,3 +921,16 @@ bool br_cfm_created(struct net_bridge *br)
{
return !list_empty(&br->mep_list);
}
+
+/* Deletes the CFM instances on a specific bridge port
+ * note: called under rtnl_lock
+ */
+void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *port)
+{
+ struct br_cfm_mep *mep;
+
+ list_for_each_entry_rcu(mep, &br->mep_list, head,
+ lockdep_rtnl_is_held())
+ if (mep->create.ifindex == port->dev->ifindex)
+ mep_delete_implementation(br, mep);
+}
diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c
index a0e9a7937412..f7d2f472ae24 100644
--- a/net/bridge/br_if.c
+++ b/net/bridge/br_if.c
@@ -334,6 +334,7 @@ static void del_nbp(struct net_bridge_port *p)
spin_unlock_bh(&br->lock);

br_mrp_port_del(br, p);
+ br_cfm_port_del(br, p);

br_ifinfo_notify(RTM_DELLINK, NULL, p);

diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 53bcbdd21f34..5617255f0c0c 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -1369,6 +1369,7 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
bool br_cfm_created(struct net_bridge *br);
+void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *p);
int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
int br_cfm_status_fill_info(struct sk_buff *skb,
struct net_bridge *br,
@@ -1393,6 +1394,11 @@ static inline bool br_cfm_created(struct net_bridge *br)
return false;
}

+static inline void br_cfm_port_del(struct net_bridge *br,
+ struct net_bridge_port *p)
+{
+}
+
static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
{
return -EOPNOTSUPP;
--
2.28.0

2020-09-04 09:21:45

by Henrik Bjoernlund

[permalink] [raw]
Subject: [PATCH RFC 6/7] bridge: cfm: Netlink Notifications.

This is the implementation of Netlink notifications out of CFM.

Notifications are initiated whenever a state change happens in CFM.

IFLA_BRIDGE_CFM:
Points to the CFM information.

IFLA_BRIDGE_CFM_MEP_STATUS_INFO:
This indicate that the MEP instance status are following.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO:
This indicate that the peer MEP status are following.

CFM nested attribute has the following attributes in next level.

IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE:
The MEP instance number of the delivered status.
The type is NLA_U32.
IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN:
The MEP instance received CFM PDU with unexpected Opcode.
The type is NLA_U32 (bool).
IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN:
The MEP instance received CFM PDU with unexpected version.
The type is NLA_U32 (bool).
IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN:
The MEP instance received CCM PDU with MD level lower than
configured level. This frame is discarded.
The type is NLA_U32 (bool).

IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE:
The MEP instance number of the delivered status.
The type is NLA_U32.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID:
The added Peer MEP ID of the delivered status.
The type is NLA_U32.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT:
The CCM defect status.
The type is NLA_U32 (bool).
True means no CCM frame is received for 3.25 intervals.
IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI:
The last received CCM PDU RDI.
The type is NLA_U32 (bool).
IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE:
The last received CCM PDU Port Status TLV value field.
The type is NLA_U8.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE:
The last received CCM PDU Interface Status TLV value field.
The type is NLA_U8.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN:
A CCM frame has been received from Peer MEP.
The type is NLA_U32 (bool).
This is cleared after GETLINK IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN:
A CCM frame with TLV has been received from Peer MEP.
The type is NLA_U32 (bool).
This is cleared after GETLINK IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN:
A CCM frame with unexpected sequence number has been received
from Peer MEP.
The type is NLA_U32 (bool).
When a sequence number is not one higher than previously received
then it is unexpected.
This is cleared after GETLINK IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO.

Signed-off-by: Henrik Bjoernlund <[email protected]>
---
net/bridge/br_cfm.c | 43 +++++++++++++++++++++
net/bridge/br_cfm_netlink.c | 24 +++++++-----
net/bridge/br_netlink.c | 76 ++++++++++++++++++++++++++++++++-----
net/bridge/br_private.h | 22 ++++++++++-
4 files changed, 144 insertions(+), 21 deletions(-)

diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
index e38cc3e8f262..b7fed2c1d8ec 100644
--- a/net/bridge/br_cfm.c
+++ b/net/bridge/br_cfm.c
@@ -155,6 +155,13 @@ static void ccm_rx_timer_start(struct br_cfm_peer_mep *peer_mep)
usecs_to_jiffies(interval_us / 4));
}

+static void br_cfm_notify(int event, const struct net_bridge_port *port)
+{
+ u32 filter = RTEXT_FILTER_CFM_STATUS;
+
+ return br_info_notify(event, port->br, NULL, filter);
+}
+
static void cc_peer_enable(struct br_cfm_peer_mep *peer_mep)
{
memset(&peer_mep->cc_status, 0, sizeof(peer_mep->cc_status));
@@ -299,6 +306,7 @@ static void ccm_tx_work_expired(struct work_struct *work)
static void ccm_rx_work_expired(struct work_struct *work)
{
struct br_cfm_peer_mep *peer_mep;
+ struct net_bridge_port *b_port;
struct delayed_work *del_work;

del_work = to_delayed_work(work);
@@ -318,6 +326,11 @@ static void ccm_rx_work_expired(struct work_struct *work)
peer_mep->cc_status.ccm_defect = true;

/* Change in CCM defect status - notify */
+ rcu_read_lock();
+ b_port = rcu_dereference(peer_mep->mep->b_port);
+ if (b_port)
+ br_cfm_notify(RTM_NEWLINK, b_port);
+ rcu_read_unlock();
}
}

@@ -445,6 +458,7 @@ static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb)
peer_mep->cc_status.ccm_defect = false;

/* Change in CCM defect status - notify */
+ br_cfm_notify(RTM_NEWLINK, port);

/* Start CCM RX timer */
ccm_rx_timer_start(peer_mep);
@@ -874,6 +888,35 @@ int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
return 0;
}

+int br_cfm_mep_count(struct net_bridge *br, u32 *count)
+{
+ struct br_cfm_mep *mep;
+ *count = 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(mep, &br->mep_list, head)
+ * count += 1;
+ rcu_read_unlock();
+
+ return 0;
+}
+
+int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count)
+{
+ struct br_cfm_peer_mep *peer_mep;
+ struct br_cfm_mep *mep;
+ *count = 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(mep, &br->mep_list, head) {
+ list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head)
+ * count += 1;
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
bool br_cfm_created(struct net_bridge *br)
{
return !list_empty(&br->mep_list);
diff --git a/net/bridge/br_cfm_netlink.c b/net/bridge/br_cfm_netlink.c
index 4e39aab1cd0b..13664ac8608a 100644
--- a/net/bridge/br_cfm_netlink.c
+++ b/net/bridge/br_cfm_netlink.c
@@ -582,7 +582,9 @@ int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
return -EMSGSIZE;
}

-int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
+int br_cfm_status_fill_info(struct sk_buff *skb,
+ struct net_bridge *br,
+ bool getlink)
{
struct nlattr *tb, *cfm_tb;
struct br_cfm_mep *mep;
@@ -613,10 +615,12 @@ int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
mep->status.rx_level_low_seen))
goto nla_put_failure;

- /* Clear all 'seen' indications */
- mep->status.opcode_unexp_seen = false;
- mep->status.version_unexp_seen = false;
- mep->status.rx_level_low_seen = false;
+ if (getlink) { /* Only clear if this is a GETLINK */
+ /* Clear all 'seen' indications */
+ mep->status.opcode_unexp_seen = false;
+ mep->status.version_unexp_seen = false;
+ mep->status.rx_level_low_seen = false;
+ }

nla_nest_end(skb, tb);

@@ -662,10 +666,12 @@ int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
peer_mep->cc_status.seq_unexp_seen))
goto nla_put_failure;

- /* Clear all 'seen' indications */
- peer_mep->cc_status.seen = false;
- peer_mep->cc_status.tlv_seen = false;
- peer_mep->cc_status.seq_unexp_seen = false;
+ if (getlink) { /* Only clear if this is a GETLINK */
+ /* Clear all 'seen' indications */
+ peer_mep->cc_status.seen = false;
+ peer_mep->cc_status.tlv_seen = false;
+ peer_mep->cc_status.seq_unexp_seen = false;
+ }

nla_nest_end(skb, tb);
}
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
index 6de5cb1295f6..f2e885521f4f 100644
--- a/net/bridge/br_netlink.c
+++ b/net/bridge/br_netlink.c
@@ -94,9 +94,11 @@ static size_t br_get_link_af_size_filtered(const struct net_device *dev,
{
struct net_bridge_vlan_group *vg = NULL;
struct net_bridge_port *p = NULL;
- struct net_bridge *br;
- int num_vlan_infos;
+ struct net_bridge *br = NULL;
+ u32 num_cfm_peer_mep_infos;
+ u32 num_cfm_mep_infos;
size_t vinfo_sz = 0;
+ int num_vlan_infos;

rcu_read_lock();
if (netif_is_bridge_port(dev)) {
@@ -115,6 +117,52 @@ static size_t br_get_link_af_size_filtered(const struct net_device *dev,
/* Each VLAN is returned in bridge_vlan_info along with flags */
vinfo_sz += num_vlan_infos * nla_total_size(sizeof(struct bridge_vlan_info));

+ if (!(filter_mask & RTEXT_FILTER_CFM_STATUS))
+ return vinfo_sz;
+
+ if (!br)
+ return vinfo_sz;
+
+ /* CFM status info must be added */
+ if (br_cfm_mep_count(br, &num_cfm_mep_infos))
+ return vinfo_sz;
+
+ if (br_cfm_peer_mep_count(br, &num_cfm_peer_mep_infos))
+ return vinfo_sz;
+
+ vinfo_sz += nla_total_size(0); /* IFLA_BRIDGE_CFM */
+ /* For each status struct the MEP instance (u32) is added */
+ /* MEP instance (u32) + br_cfm_mep_status */
+ vinfo_sz += num_cfm_mep_infos *
+ /*IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE */
+ (nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN */
+ + nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN */
+ + nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN */
+ + nla_total_size(sizeof(u32)));
+ /* MEP instance (u32) + br_cfm_cc_peer_status */
+ vinfo_sz += num_cfm_peer_mep_infos *
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE */
+ (nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID */
+ + nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT */
+ + nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI */
+ + nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE */
+ + nla_total_size(sizeof(u8))
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE */
+ + nla_total_size(sizeof(u8))
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN */
+ + nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN */
+ + nla_total_size(sizeof(u32))
+ /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN */
+ + nla_total_size(sizeof(u32)));
+
return vinfo_sz;
}

@@ -378,7 +426,8 @@ static int br_fill_ifvlaninfo(struct sk_buff *skb,
static int br_fill_ifinfo(struct sk_buff *skb,
const struct net_bridge_port *port,
u32 pid, u32 seq, int event, unsigned int flags,
- u32 filter_mask, const struct net_device *dev)
+ u32 filter_mask, const struct net_device *dev,
+ bool getlink)
{
u8 operstate = netif_running(dev) ? dev->operstate : IF_OPER_DOWN;
struct net_bridge *br;
@@ -515,7 +564,7 @@ static int br_fill_ifinfo(struct sk_buff *skb,
goto nla_put_failure;

rcu_read_lock();
- err = br_cfm_status_fill_info(skb, br);
+ err = br_cfm_status_fill_info(skb, br, getlink);
rcu_read_unlock();

if (err)
@@ -533,11 +582,9 @@ static int br_fill_ifinfo(struct sk_buff *skb,
return -EMSGSIZE;
}

-/* Notify listeners of a change in bridge or port information */
-void br_ifinfo_notify(int event, const struct net_bridge *br,
- const struct net_bridge_port *port)
+void br_info_notify(int event, const struct net_bridge *br,
+ const struct net_bridge_port *port, u32 filter)
{
- u32 filter = RTEXT_FILTER_BRVLAN_COMPRESSED;
struct net_device *dev;
struct sk_buff *skb;
int err = -ENOBUFS;
@@ -562,7 +609,7 @@ void br_ifinfo_notify(int event, const struct net_bridge *br,
if (skb == NULL)
goto errout;

- err = br_fill_ifinfo(skb, port, 0, 0, event, 0, filter, dev);
+ err = br_fill_ifinfo(skb, port, 0, 0, event, 0, filter, dev, false);
if (err < 0) {
/* -EMSGSIZE implies BUG in br_nlmsg_size() */
WARN_ON(err == -EMSGSIZE);
@@ -575,6 +622,15 @@ void br_ifinfo_notify(int event, const struct net_bridge *br,
rtnl_set_sk_err(net, RTNLGRP_LINK, err);
}

+/* Notify listeners of a change in bridge or port information */
+void br_ifinfo_notify(int event, const struct net_bridge *br,
+ const struct net_bridge_port *port)
+{
+ u32 filter = RTEXT_FILTER_BRVLAN_COMPRESSED;
+
+ return br_info_notify(event, br, port, filter);
+}
+
/*
* Dump information about all ports, in response to GETLINK
*/
@@ -591,7 +647,7 @@ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq,
return 0;

return br_fill_ifinfo(skb, port, pid, seq, RTM_NEWLINK, nlflags,
- filter_mask, dev);
+ filter_mask, dev, true);
}

static int br_vlan_info(struct net_bridge *br, struct net_bridge_port *p,
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index fe36592f7525..53bcbdd21f34 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -1370,7 +1370,11 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
bool br_cfm_created(struct net_bridge *br);
int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
-int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br);
+int br_cfm_status_fill_info(struct sk_buff *skb,
+ struct net_bridge *br,
+ bool getlink);
+int br_cfm_mep_count(struct net_bridge *br, u32 *count);
+int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count);
#else
static inline int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
struct nlattr *attr, int cmd,
@@ -1394,7 +1398,19 @@ static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge
return -EOPNOTSUPP;
}

-static inline int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
+static inline int br_cfm_status_fill_info(struct sk_buff *skb,
+ struct net_bridge *br,
+ bool getlink)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int br_cfm_mep_count(struct net_bridge *br, u32 *count)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count)
{
return -EOPNOTSUPP;
}
@@ -1406,6 +1422,8 @@ int br_netlink_init(void);
void br_netlink_fini(void);
void br_ifinfo_notify(int event, const struct net_bridge *br,
const struct net_bridge_port *port);
+void br_info_notify(int event, const struct net_bridge *br,
+ const struct net_bridge_port *port, u32 filter);
int br_setlink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags,
struct netlink_ext_ack *extack);
int br_dellink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags);
--
2.28.0

2020-09-04 09:23:02

by Henrik Bjoernlund

[permalink] [raw]
Subject: [PATCH RFC 5/7] bridge: cfm: Netlink Interface.

This is the implementation of CFM netlink configuration
and status information interface.

Add new nested netlink attributes. These attributes are used by the
user space to create/delete/configure CFM instances and get status.
Also they are used by the kernel to notify the user space when changes
in any status happens.

SETLINK:
IFLA_BRIDGE_CFM:
Indicate that the following attributes are CFM.

IFLA_BRIDGE_CFM_MEP_CREATE:
This indicate that a MEP instance must be created.
IFLA_BRIDGE_CFM_MEP_DELETE:
This indicate that a MEP instance must be deleted.
IFLA_BRIDGE_CFM_MEP_CONFIG:
This indicate that a MEP instance must be configured.
IFLA_BRIDGE_CFM_CC_CONFIG:
This indicate that a MEP instance Continuity Check (CC)
functionality must be configured.
IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD:
This indicate that a CC Peer MEP must be added.
IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE:
This indicate that a CC Peer MEP must be removed.
IFLA_BRIDGE_CFM_CC_CCM_TX:
This indicate that the CC transmitted CCM PDU must be configured.
IFLA_BRIDGE_CFM_CC_RDI:
This indicate that the CC transmitted CCM PDU RDI must be
configured.

GETLINK:
Request filter RTEXT_FILTER_CFM_CONFIG:
Indicating that CFM configuration information must be delivered.

IFLA_BRIDGE_CFM:
Points to the CFM information.

IFLA_BRIDGE_CFM_MEP_CREATE_INFO:
This indicate that MEP instance create parameters are following.
IFLA_BRIDGE_CFM_MEP_CONFIG_INFO:
This indicate that MEP instance config parameters are following.
IFLA_BRIDGE_CFM_CC_CONFIG_INFO:
This indicate that MEP instance CC functionality
parameters are following.
IFLA_BRIDGE_CFM_CC_RDI_INFO:
This indicate that CC transmitted CCM PDU RDI
parameters are following.
IFLA_BRIDGE_CFM_CC_CCM_TX_INFO:
This indicate that CC transmitted CCM PDU parameters are
following.
IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO:
This indicate that the added peer MEP IDs are following.

Request filter RTEXT_FILTER_CFM_STATUS:
Indicating that CFM status information must be delivered.

IFLA_BRIDGE_CFM:
Points to the CFM information.

IFLA_BRIDGE_CFM_MEP_STATUS_INFO:
This indicate that the MEP instance status are following.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO:
This indicate that the peer MEP status are following.

CFM nested attribute has the following attributes in next level.

SETLINK and GETLINK RTEXT_FILTER_CFM_CONFIG:
IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE:
The created MEP instance number.
The type is u32.
IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN:
The created MEP domain.
The type is u32 (br_cfm_domain).
It must be BR_CFM_PORT.
This means that CFM frames are transmitted and received
directly on the port - untagged. Not in a VLAN.
IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION:
The created MEP direction.
The type is u32 (br_cfm_mep_direction).
It must be BR_CFM_MEP_DIRECTION_DOWN.
This means that CFM frames are transmitted and received on
the port. Not in the bridge.
IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX:
The created MEP residence port ifindex.
The type is u32 (ifindex).

IFLA_BRIDGE_CFM_MEP_DELETE_INSTANCE:
The deleted MEP instance number.
The type is u32.

IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE:
The configured MEP instance number.
The type is u32.
IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC:
The configured MEP unicast MAC address.
The type is 6*u8 (array).
This is used as SMAC in all transmitted CFM frames.
IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL:
The configured MEP unicast MD level.
The type is u32.
It must be in the range 1-7.
No CFM frames are passing through this MEP on lower levels.
IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID:
The configured MEP ID.
The type is u32.
It must be in the range 0-0x1FFF.
This MEP ID is inserted in any transmitted CCM frame.

IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE:
The configured MEP instance number.
The type is u32.
IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE:
The Continuity Check (CC) functionality is enabled or disabled.
The type is u32 (bool).
IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL:
The CC expected receive interval of CCM frames.
The type is u32 (br_cfm_ccm_interval).
This is also the transmission interval of CCM frames when enabled.
IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID:
The CC expected receive MAID in CCM frames.
The type is CFM_MAID_LENGTH*u8.
This is MAID is also inserted in transmitted CCM frames.

IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE:
The configured MEP instance number.
The type is u32.
IFLA_BRIDGE_CFM_CC_PEER_MEPID:
The CC Peer MEP ID added.
The type is u32.
When a Peer MEP ID is added and CC is enabled it is expected to
receive CCM frames from that Peer MEP.

IFLA_BRIDGE_CFM_CC_RDI_INSTANCE:
The configured MEP instance number.
The type is u32.
IFLA_BRIDGE_CFM_CC_RDI_RDI:
The RDI that is inserted in transmitted CCM PDU.
The type is u32 (bool).

IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE:
The configured MEP instance number.
The type is u32.
IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC:
The transmitted CCM frame destination MAC address.
The type is 6*u8 (array).
This is used as DMAC in all transmitted CFM frames.
IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE:
The transmitted CCM frame update (increment) of sequence
number is enabled or disabled.
The type is u32 (bool).
IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD:
The period of time where CCM frame are transmitted.
The type is u32.
The time is given in seconds. SETLINK IFLA_BRIDGE_CFM_CC_CCM_TX
must be done before timeout to keep transmission alive.
When period is zero any ongoing CCM frame transmission
will be stopped.
IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV:
The transmitted CCM frame update with Interface Status TLV
is enabled or disabled.
The type is u32 (bool).
IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE:
The transmitted Interface Status TLV value field.
The type is u8.
IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV:
The transmitted CCM frame update with Port Status TLV is enabled
or disabled.
The type is u32 (bool).
IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE:
The transmitted Port Status TLV value field.
The type is u8.

GETLINK RTEXT_FILTER_CFM_STATUS:
IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE:
The MEP instance number of the delivered status.
The type is u32.
IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN:
The MEP instance received CFM PDU with unexpected Opcode.
The type is u32 (bool).
IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN:
The MEP instance received CFM PDU with unexpected version.
The type is u32 (bool).
IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN:
The MEP instance received CCM PDU with MD level lower than
configured level. This frame is discarded.
The type is u32 (bool).

IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE:
The MEP instance number of the delivered status.
The type is u32.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID:
The added Peer MEP ID of the delivered status.
The type is u32.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT:
The CCM defect status.
The type is u32 (bool).
True means no CCM frame is received for 3.25 intervals.
IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI:
The last received CCM PDU RDI.
The type is u32 (bool).
IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE:
The last received CCM PDU Port Status TLV value field.
The type is u8.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE:
The last received CCM PDU Interface Status TLV value field.
The type is u8.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN:
A CCM frame has been received from Peer MEP.
The type is u32 (bool).
This is cleared after GETLINK IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN:
A CCM frame with TLV has been received from Peer MEP.
The type is u32 (bool).
This is cleared after GETLINK IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO.
IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN:
A CCM frame with unexpected sequence number has been received
from Peer MEP.
The type is u32 (bool).
When a sequence number is not one higher than previously received
then it is unexpected.
This is cleared after GETLINK IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO.

Signed-off-by: Henrik Bjoernlund <[email protected]>
---
include/uapi/linux/if_bridge.h | 125 ++++++
include/uapi/linux/rtnetlink.h | 2 +
net/bridge/Makefile | 2 +-
net/bridge/br_cfm_netlink.c | 684 +++++++++++++++++++++++++++++++++
net/bridge/br_netlink.c | 52 ++-
net/bridge/br_private.h | 21 +
6 files changed, 884 insertions(+), 2 deletions(-)
create mode 100644 net/bridge/br_cfm_netlink.c

diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
index c1227aecd38f..4e40c04d73b8 100644
--- a/include/uapi/linux/if_bridge.h
+++ b/include/uapi/linux/if_bridge.h
@@ -121,6 +121,7 @@ enum {
IFLA_BRIDGE_VLAN_INFO,
IFLA_BRIDGE_VLAN_TUNNEL_INFO,
IFLA_BRIDGE_MRP,
+ IFLA_BRIDGE_CFM,
__IFLA_BRIDGE_MAX,
};
#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
@@ -328,6 +329,130 @@ struct br_mrp_start_in_test {
__u16 in_id;
};

+enum {
+ IFLA_BRIDGE_CFM_UNSPEC,
+ IFLA_BRIDGE_CFM_MEP_CREATE,
+ IFLA_BRIDGE_CFM_MEP_DELETE,
+ IFLA_BRIDGE_CFM_MEP_CONFIG,
+ IFLA_BRIDGE_CFM_CC_CONFIG,
+ IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD,
+ IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE,
+ IFLA_BRIDGE_CFM_CC_RDI,
+ IFLA_BRIDGE_CFM_CC_CCM_TX,
+ IFLA_BRIDGE_CFM_MEP_CREATE_INFO,
+ IFLA_BRIDGE_CFM_MEP_CONFIG_INFO,
+ IFLA_BRIDGE_CFM_CC_CONFIG_INFO,
+ IFLA_BRIDGE_CFM_CC_RDI_INFO,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_INFO,
+ IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO,
+ IFLA_BRIDGE_CFM_MEP_STATUS_INFO,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO,
+ __IFLA_BRIDGE_CFM_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_MAX (__IFLA_BRIDGE_CFM_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_MEP_CREATE_UNSPEC,
+ IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE,
+ IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN,
+ IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION,
+ IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX,
+ __IFLA_BRIDGE_CFM_MEP_CREATE_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_MEP_CREATE_MAX (__IFLA_BRIDGE_CFM_MEP_CREATE_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_MEP_DELETE_UNSPEC,
+ IFLA_BRIDGE_CFM_MEP_DELETE_INSTANCE,
+ __IFLA_BRIDGE_CFM_MEP_DELETE_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_MEP_DELETE_MAX (__IFLA_BRIDGE_CFM_MEP_DELETE_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_MEP_CONFIG_UNSPEC,
+ IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE,
+ IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC,
+ IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL,
+ IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID,
+ __IFLA_BRIDGE_CFM_MEP_CONFIG_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_MEP_CONFIG_MAX (__IFLA_BRIDGE_CFM_MEP_CONFIG_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_MEP_STATUS_UNSPEC,
+ IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE,
+ IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN,
+ IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN,
+ IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN,
+ __IFLA_BRIDGE_CFM_MEP_STATUS_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_MEP_STATUS_MAX (__IFLA_BRIDGE_CFM_MEP_STATUS_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_CC_CONFIG_UNSPEC,
+ IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE,
+ IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE,
+ IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL,
+ IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID,
+ __IFLA_BRIDGE_CFM_CC_CONFIG_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_CC_CONFIG_MAX (__IFLA_BRIDGE_CFM_CC_CONFIG_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_CC_PEER_MEP_UNSPEC,
+ IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE,
+ IFLA_BRIDGE_CFM_CC_PEER_MEPID,
+ __IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX (__IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_CC_RDI_UNSPEC,
+ IFLA_BRIDGE_CFM_CC_RDI_INSTANCE,
+ IFLA_BRIDGE_CFM_CC_RDI_RDI,
+ __IFLA_BRIDGE_CFM_CC_RDI_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_CC_RDI_MAX (__IFLA_BRIDGE_CFM_CC_RDI_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_CC_CCM_TX_UNSPEC,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV,
+ IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE,
+ __IFLA_BRIDGE_CFM_CC_CCM_TX_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_CC_CCM_TX_MAX (__IFLA_BRIDGE_CFM_CC_CCM_TX_MAX - 1)
+
+enum {
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_UNSPEC,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN,
+ IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN,
+ __IFLA_BRIDGE_CFM_CC_PEER_STATUS_MAX,
+};
+
+#define IFLA_BRIDGE_CFM_CC_PEER_STATUS_MAX (__IFLA_BRIDGE_CFM_CC_PEER_STATUS_MAX - 1)
+
struct bridge_stp_xstats {
__u64 transition_blk;
__u64 transition_fwd;
diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
index 9b814c92de12..fdd408f6a5d2 100644
--- a/include/uapi/linux/rtnetlink.h
+++ b/include/uapi/linux/rtnetlink.h
@@ -779,6 +779,8 @@ enum {
#define RTEXT_FILTER_BRVLAN_COMPRESSED (1 << 2)
#define RTEXT_FILTER_SKIP_STATS (1 << 3)
#define RTEXT_FILTER_MRP (1 << 4)
+#define RTEXT_FILTER_CFM_CONFIG (1 << 5)
+#define RTEXT_FILTER_CFM_STATUS (1 << 6)

/* End of information exported to user level */

diff --git a/net/bridge/Makefile b/net/bridge/Makefile
index ddc0a9192348..4702702a74d3 100644
--- a/net/bridge/Makefile
+++ b/net/bridge/Makefile
@@ -28,4 +28,4 @@ obj-$(CONFIG_NETFILTER) += netfilter/

bridge-$(CONFIG_BRIDGE_MRP) += br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o

-bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o
+bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o br_cfm_netlink.o
diff --git a/net/bridge/br_cfm_netlink.c b/net/bridge/br_cfm_netlink.c
new file mode 100644
index 000000000000..4e39aab1cd0b
--- /dev/null
+++ b/net/bridge/br_cfm_netlink.c
@@ -0,0 +1,684 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <net/genetlink.h>
+
+#include "br_private.h"
+#include "br_private_cfm.h"
+
+static inline struct mac_addr nla_get_mac(const struct nlattr *nla)
+{
+ struct mac_addr mac;
+
+ nla_memcpy(&mac.addr, nla, sizeof(mac.addr));
+
+ return mac;
+}
+
+static inline struct br_cfm_maid nla_get_maid(const struct nlattr *nla)
+{
+ struct br_cfm_maid maid;
+
+ nla_memcpy(&maid.data, nla, sizeof(maid.data));
+
+ return maid;
+}
+
+static inline int nla_put_u64(struct sk_buff *skb, int attrtype, u64 value)
+{
+ u64 tmp = value;
+
+ return nla_put(skb, attrtype, sizeof(u64), &tmp);
+}
+
+static const struct nla_policy
+br_cfm_policy[IFLA_BRIDGE_CFM_MAX + 1] = {
+ [IFLA_BRIDGE_CFM_UNSPEC] = { .type = NLA_REJECT },
+ [IFLA_BRIDGE_CFM_MEP_CREATE] = { .type = NLA_NESTED },
+ [IFLA_BRIDGE_CFM_MEP_DELETE] = { .type = NLA_NESTED },
+ [IFLA_BRIDGE_CFM_MEP_CONFIG] = { .type = NLA_NESTED },
+ [IFLA_BRIDGE_CFM_CC_CONFIG] = { .type = NLA_NESTED },
+ [IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD] = { .type = NLA_NESTED },
+ [IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE] = { .type = NLA_NESTED },
+ [IFLA_BRIDGE_CFM_CC_RDI] = { .type = NLA_NESTED },
+ [IFLA_BRIDGE_CFM_CC_CCM_TX] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy
+br_cfm_mep_create_policy[IFLA_BRIDGE_CFM_MEP_CREATE_MAX + 1] = {
+ [IFLA_BRIDGE_CFM_MEP_CREATE_UNSPEC] = { .type = NLA_REJECT },
+ [IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX] = { .type = NLA_U32 },
+};
+
+static const struct nla_policy
+br_cfm_mep_delete_policy[IFLA_BRIDGE_CFM_MEP_DELETE_MAX + 1] = {
+ [IFLA_BRIDGE_CFM_MEP_DELETE_UNSPEC] = { .type = NLA_REJECT },
+ [IFLA_BRIDGE_CFM_MEP_DELETE_INSTANCE] = { .type = NLA_U32 },
+};
+
+static const struct nla_policy
+br_cfm_mep_config_policy[IFLA_BRIDGE_CFM_MEP_CONFIG_MAX + 1] = {
+ [IFLA_BRIDGE_CFM_MEP_CONFIG_UNSPEC] = { .type = NLA_REJECT },
+ [IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC] = NLA_POLICY_ETH_ADDR,
+ [IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID] = { .type = NLA_U32 },
+};
+
+static const struct nla_policy
+br_cfm_cc_config_policy[IFLA_BRIDGE_CFM_CC_CONFIG_MAX + 1] = {
+ [IFLA_BRIDGE_CFM_CC_CONFIG_UNSPEC] = { .type = NLA_REJECT },
+ [IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID] = {
+ .type = NLA_BINARY, .len = CFM_MAID_LENGTH },
+};
+
+static const struct nla_policy
+br_cfm_cc_peer_mep_policy[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1] = {
+ [IFLA_BRIDGE_CFM_CC_PEER_MEP_UNSPEC] = { .type = NLA_REJECT },
+ [IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_PEER_MEPID] = { .type = NLA_U32 },
+};
+
+static const struct nla_policy
+br_cfm_cc_rdi_policy[IFLA_BRIDGE_CFM_CC_RDI_MAX + 1] = {
+ [IFLA_BRIDGE_CFM_CC_RDI_UNSPEC] = { .type = NLA_REJECT },
+ [IFLA_BRIDGE_CFM_CC_RDI_INSTANCE] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_RDI_RDI] = { .type = NLA_U32 },
+};
+
+static const struct nla_policy
+br_cfm_cc_ccm_tx_policy[IFLA_BRIDGE_CFM_CC_CCM_TX_MAX + 1] = {
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_UNSPEC] = { .type = NLA_REJECT },
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC] = NLA_POLICY_ETH_ADDR,
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE] = { .type = NLA_U8 },
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV] = { .type = NLA_U32 },
+ [IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE] = { .type = NLA_U8 },
+};
+
+static int br_mep_create_parse(struct net_bridge *br, struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_CREATE_MAX + 1];
+ struct br_cfm_mep_create create;
+ u32 instance;
+ int err;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_CREATE_MAX, attr,
+ br_cfm_mep_create_policy, extack);
+ if (err)
+ return err;
+
+ if (!tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE] ||
+ !tb[IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN] ||
+ !tb[IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION] ||
+ !tb[IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Missing attribute: INSTANCE or DOMAIN or DIRECTION or VID or IFINDEX");
+ return -EINVAL;
+ }
+
+ memset(&create, 0, sizeof(create));
+
+ instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]);
+ create.domain = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN]);
+ create.direction = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION]);
+ create.ifindex = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX]);
+
+ return br_cfm_mep_create(br, instance, &create, extack);
+}
+
+static int br_mep_delete_parse(struct net_bridge *br, struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_DELETE_MAX + 1];
+ u32 instance;
+ int err;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_DELETE_MAX, attr,
+ br_cfm_mep_delete_policy, extack);
+ if (err)
+ return err;
+
+ if (!tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Missing attribute: INSTANCE");
+ return -EINVAL;
+ }
+
+ instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]);
+
+ return br_cfm_mep_delete(br, instance, extack);
+}
+
+static int br_mep_config_parse(struct net_bridge *br, struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MAX + 1];
+ struct br_cfm_mep_config config;
+ u32 instance;
+ int err;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_CONFIG_MAX, attr,
+ br_cfm_mep_config_policy, extack);
+ if (err)
+ return err;
+
+ if (!tb[IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE] ||
+ !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC] ||
+ !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL] ||
+ !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Missing attribute: INSTANCE or UNICAST_MAC or MDLEVEL or MEPID or VID");
+ return -EINVAL;
+ }
+
+ memset(&config, 0, sizeof(config));
+
+ instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE]);
+ config.unicast_mac = nla_get_mac(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC]);
+ config.mdlevel = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL]);
+ config.mepid = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID]);
+ config.vid = 0;
+
+ return br_cfm_mep_config_set(br, instance, &config, extack);
+}
+
+static int br_cc_config_parse(struct net_bridge *br, struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_CC_CONFIG_MAX + 1];
+ struct br_cfm_cc_config config;
+ u32 instance;
+ int err;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_CONFIG_MAX, attr,
+ br_cfm_cc_config_policy, extack);
+ if (err)
+ return err;
+
+ if (!tb[IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Missing attribute: INSTANCE or ENABLE or INTERVAL or PRIORITY or MAID");
+ return -EINVAL;
+ }
+
+ memset(&config, 0, sizeof(config));
+
+ instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE]);
+ config.enable = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE]);
+ config.exp_interval = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL]);
+ config.exp_priority = 0;
+ config.exp_maid = nla_get_maid(tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID]);
+
+ return br_cfm_cc_config_set(br, instance, &config, extack);
+}
+
+static int br_cc_peer_mep_add_parse(struct net_bridge *br, struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1];
+ u32 instance, peer_mep_id;
+ int err;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX, attr,
+ br_cfm_cc_peer_mep_policy, extack);
+ if (err)
+ return err;
+
+ if (!tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] ||
+ !tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Missing attribute: INSTANCE or PEER_MEP_ID");
+ return -EINVAL;
+ }
+
+ instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE]);
+ peer_mep_id = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]);
+
+ return br_cfm_cc_peer_mep_add(br, instance, peer_mep_id, extack);
+}
+
+static int br_cc_peer_mep_remove_parse(struct net_bridge *br, struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1];
+ u32 instance, peer_mep_id;
+ int err;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX, attr,
+ br_cfm_cc_peer_mep_policy, extack);
+ if (err)
+ return err;
+
+ if (!tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] ||
+ !tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Missing attribute: INSTANCE or PEER_MEP_ID");
+ return -EINVAL;
+ }
+
+ instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE]);
+ peer_mep_id = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]);
+
+ return br_cfm_cc_peer_mep_remove(br, instance, peer_mep_id, extack);
+}
+
+static int br_cc_rdi_parse(struct net_bridge *br, struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_CC_RDI_MAX + 1];
+ u32 instance, rdi;
+ int err;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_RDI_MAX, attr,
+ br_cfm_cc_rdi_policy, extack);
+ if (err)
+ return err;
+
+ if (!tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE] ||
+ !tb[IFLA_BRIDGE_CFM_CC_RDI_RDI]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Missing attribute: INSTANCE or RDI");
+ return -EINVAL;
+ }
+
+ instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE]);
+ rdi = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_RDI]);
+
+ return br_cfm_cc_rdi_set(br, instance, rdi, extack);
+}
+
+static int br_cc_ccm_tx_parse(struct net_bridge *br, struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_CC_CCM_TX_MAX + 1];
+ u32 instance;
+ struct br_cfm_cc_ccm_tx_info tx_info;
+ int err;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_MAX, attr,
+ br_cfm_cc_ccm_tx_policy, extack);
+ if (err)
+ return err;
+
+ if (!tb[IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV] ||
+ !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Missing attribute: INSTANCE or PRIORITY or DEI or DMAC or SEQ_NO_UPDATE or PERIOD or IF_TLV or IF_TLV_VALUE or PORT_TLV or PORT_TLV_VALUE");
+ return -EINVAL;
+ }
+
+ instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE]);
+ tx_info.priority = 0;
+ tx_info.dei = 0;
+ tx_info.dmac = nla_get_mac(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC]);
+ tx_info.seq_no_update = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE]);
+ tx_info.period = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD]);
+ tx_info.if_tlv = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV]);
+ tx_info.if_tlv_value = nla_get_u8(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE]);
+ tx_info.port_tlv = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV]);
+ tx_info.port_tlv_value = nla_get_u8(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE]);
+
+ return br_cfm_cc_ccm_tx(br, instance, &tx_info, extack);
+}
+
+int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
+ struct nlattr *attr, int cmd, struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[IFLA_BRIDGE_CFM_MAX + 1];
+ int err;
+
+ /* When this function is called for a port then the br pointer is
+ * invalid, therefor set the br to point correctly
+ */
+ if (p)
+ br = p->br;
+
+ err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MAX, attr,
+ br_cfm_policy, extack);
+ if (err)
+ return err;
+
+ if (tb[IFLA_BRIDGE_CFM_MEP_CREATE]) {
+ err = br_mep_create_parse(br, tb[IFLA_BRIDGE_CFM_MEP_CREATE],
+ extack);
+ if (err)
+ return err;
+ }
+
+ if (tb[IFLA_BRIDGE_CFM_MEP_DELETE]) {
+ err = br_mep_delete_parse(br, tb[IFLA_BRIDGE_CFM_MEP_DELETE],
+ extack);
+ if (err)
+ return err;
+ }
+
+ if (tb[IFLA_BRIDGE_CFM_MEP_CONFIG]) {
+ err = br_mep_config_parse(br, tb[IFLA_BRIDGE_CFM_MEP_CONFIG],
+ extack);
+ if (err)
+ return err;
+ }
+
+ if (tb[IFLA_BRIDGE_CFM_CC_CONFIG]) {
+ err = br_cc_config_parse(br, tb[IFLA_BRIDGE_CFM_CC_CONFIG],
+ extack);
+ if (err)
+ return err;
+ }
+
+ if (tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD]) {
+ err = br_cc_peer_mep_add_parse(br, tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD],
+ extack);
+ if (err)
+ return err;
+ }
+
+ if (tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE]) {
+ err = br_cc_peer_mep_remove_parse(br, tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE],
+ extack);
+ if (err)
+ return err;
+ }
+
+ if (tb[IFLA_BRIDGE_CFM_CC_RDI]) {
+ err = br_cc_rdi_parse(br, tb[IFLA_BRIDGE_CFM_CC_RDI],
+ extack);
+ if (err)
+ return err;
+ }
+
+ if (tb[IFLA_BRIDGE_CFM_CC_CCM_TX]) {
+ err = br_cc_ccm_tx_parse(br, tb[IFLA_BRIDGE_CFM_CC_CCM_TX],
+ extack);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
+{
+ struct nlattr *tb, *cfm_tb;
+ struct br_cfm_mep *mep;
+ struct br_cfm_peer_mep *peer_mep;
+
+ cfm_tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM);
+ if (!cfm_tb)
+ return -EMSGSIZE;
+
+ list_for_each_entry_rcu(mep, &br->mep_list, head) {
+ tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_CREATE_INFO);
+ if (!tb)
+ goto nla_info_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE,
+ mep->instance))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN,
+ mep->create.domain))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION,
+ mep->create.direction))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX,
+ mep->create.ifindex))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, tb);
+
+ tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_INFO);
+
+ if (!tb)
+ goto nla_info_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE,
+ mep->instance))
+ goto nla_put_failure;
+
+ if (nla_put(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC,
+ sizeof(mep->config.unicast_mac.addr),
+ mep->config.unicast_mac.addr))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL,
+ mep->config.mdlevel))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID,
+ mep->config.mepid))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, tb);
+
+ tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_CONFIG_INFO);
+
+ if (!tb)
+ goto nla_info_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE,
+ mep->instance))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE,
+ mep->cc_config.enable))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL,
+ mep->cc_config.exp_interval))
+ goto nla_put_failure;
+
+ if (nla_put(skb, IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID,
+ sizeof(mep->cc_config.exp_maid.data),
+ mep->cc_config.exp_maid.data))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, tb);
+
+ tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_RDI_INFO);
+
+ if (!tb)
+ goto nla_info_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_RDI_INSTANCE,
+ mep->instance))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_RDI_RDI,
+ mep->rdi))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, tb);
+
+ tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_INFO);
+
+ if (!tb)
+ goto nla_info_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE,
+ mep->instance))
+ goto nla_put_failure;
+
+ if (nla_put(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC,
+ sizeof(mep->cc_ccm_tx_info.dmac),
+ mep->cc_ccm_tx_info.dmac.addr))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE,
+ mep->cc_ccm_tx_info.seq_no_update))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD,
+ mep->cc_ccm_tx_info.period))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV,
+ mep->cc_ccm_tx_info.if_tlv))
+ goto nla_put_failure;
+
+ if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE,
+ mep->cc_ccm_tx_info.if_tlv_value))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV,
+ mep->cc_ccm_tx_info.port_tlv))
+ goto nla_put_failure;
+
+ if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE,
+ mep->cc_ccm_tx_info.port_tlv_value))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, tb);
+
+ list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head) {
+ tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO);
+
+ if (!tb)
+ goto nla_info_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE,
+ mep->instance))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_MEPID,
+ peer_mep->mepid))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, tb);
+ }
+ }
+ nla_nest_end(skb, cfm_tb);
+
+ return 0;
+
+nla_put_failure:
+ nla_nest_cancel(skb, tb);
+
+nla_info_failure:
+ nla_nest_cancel(skb, cfm_tb);
+
+ return -EMSGSIZE;
+}
+
+int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
+{
+ struct nlattr *tb, *cfm_tb;
+ struct br_cfm_mep *mep;
+ struct br_cfm_peer_mep *peer_mep;
+
+ cfm_tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM);
+ if (!cfm_tb)
+ return -EMSGSIZE;
+
+ list_for_each_entry_rcu(mep, &br->mep_list, head) {
+ tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_STATUS_INFO);
+ if (!tb)
+ goto nla_info_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE,
+ mep->instance))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN,
+ mep->status.opcode_unexp_seen))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN,
+ mep->status.version_unexp_seen))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN,
+ mep->status.rx_level_low_seen))
+ goto nla_put_failure;
+
+ /* Clear all 'seen' indications */
+ mep->status.opcode_unexp_seen = false;
+ mep->status.version_unexp_seen = false;
+ mep->status.rx_level_low_seen = false;
+
+ nla_nest_end(skb, tb);
+
+ list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head) {
+ tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO);
+
+ if (!tb)
+ goto nla_info_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE,
+ mep->instance))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID,
+ peer_mep->mepid))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT,
+ peer_mep->cc_status.ccm_defect))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI,
+ peer_mep->cc_status.rdi))
+ goto nla_put_failure;
+
+ if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE,
+ peer_mep->cc_status.port_tlv_value))
+ goto nla_put_failure;
+
+ if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE,
+ peer_mep->cc_status.if_tlv_value))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN,
+ peer_mep->cc_status.seen))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN,
+ peer_mep->cc_status.tlv_seen))
+ goto nla_put_failure;
+
+ if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN,
+ peer_mep->cc_status.seq_unexp_seen))
+ goto nla_put_failure;
+
+ /* Clear all 'seen' indications */
+ peer_mep->cc_status.seen = false;
+ peer_mep->cc_status.tlv_seen = false;
+ peer_mep->cc_status.seq_unexp_seen = false;
+
+ nla_nest_end(skb, tb);
+ }
+ }
+ nla_nest_end(skb, cfm_tb);
+
+ return 0;
+
+nla_put_failure:
+ nla_nest_cancel(skb, tb);
+
+nla_info_failure:
+ nla_nest_cancel(skb, cfm_tb);
+
+ return -EMSGSIZE;
+}
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
index 8a71c60fa357..6de5cb1295f6 100644
--- a/net/bridge/br_netlink.c
+++ b/net/bridge/br_netlink.c
@@ -16,6 +16,7 @@

#include "br_private.h"
#include "br_private_stp.h"
+#include "br_private_cfm.h"
#include "br_private_tunnel.h"

static int __get_num_vlan_infos(struct net_bridge_vlan_group *vg,
@@ -481,6 +482,48 @@ static int br_fill_ifinfo(struct sk_buff *skb,
nla_nest_end(skb, af);
}

+ if (filter_mask & RTEXT_FILTER_CFM_CONFIG) {
+ struct nlattr *af;
+ int err;
+
+ if (!br_cfm_created(br) || port)
+ goto done;
+
+ af = nla_nest_start_noflag(skb, IFLA_AF_SPEC);
+ if (!af)
+ goto nla_put_failure;
+
+ rcu_read_lock();
+ err = br_cfm_config_fill_info(skb, br);
+ rcu_read_unlock();
+
+ if (err)
+ goto nla_put_failure;
+
+ nla_nest_end(skb, af);
+ }
+
+ if (filter_mask & RTEXT_FILTER_CFM_STATUS) {
+ struct nlattr *af;
+ int err;
+
+ if (!br_cfm_created(br) || port)
+ goto done;
+
+ af = nla_nest_start_noflag(skb, IFLA_AF_SPEC);
+ if (!af)
+ goto nla_put_failure;
+
+ rcu_read_lock();
+ err = br_cfm_status_fill_info(skb, br);
+ rcu_read_unlock();
+
+ if (err)
+ goto nla_put_failure;
+
+ nla_nest_end(skb, af);
+ }
+
done:
nlmsg_end(skb, nlh);
return 0;
@@ -542,7 +585,9 @@ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq,

if (!port && !(filter_mask & RTEXT_FILTER_BRVLAN) &&
!(filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED) &&
- !(filter_mask & RTEXT_FILTER_MRP))
+ !(filter_mask & RTEXT_FILTER_MRP) &&
+ !(filter_mask & RTEXT_FILTER_CFM_CONFIG) &&
+ !(filter_mask & RTEXT_FILTER_CFM_STATUS))
return 0;

return br_fill_ifinfo(skb, port, pid, seq, RTM_NEWLINK, nlflags,
@@ -704,6 +749,11 @@ static int br_afspec(struct net_bridge *br,
if (err)
return err;
break;
+ case IFLA_BRIDGE_CFM:
+ err = br_cfm_parse(br, p, attr, cmd, extack);
+ if (err)
+ return err;
+ break;
}
}

diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 385a6a6aa17e..fe36592f7525 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -1365,9 +1365,20 @@ static inline int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br)

/* br_cfm.c */
#if IS_ENABLED(CONFIG_BRIDGE_CFM)
+int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
+ struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
bool br_cfm_created(struct net_bridge *br);
+int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
+int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br);
#else
+static inline int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
+ struct nlattr *attr, int cmd,
+ struct netlink_ext_ack *extack)
+{
+ return -EOPNOTSUPP;
+}
+
static inline int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb)
{
return -EOPNOTSUPP;
@@ -1377,6 +1388,16 @@ static inline bool br_cfm_created(struct net_bridge *br)
{
return false;
}
+
+static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
+{
+ return -EOPNOTSUPP;
+}
#endif

/* br_netlink.c */
--
2.28.0

2020-09-04 15:04:17

by Jakub Kicinski

[permalink] [raw]
Subject: Re: [PATCH RFC 4/7] bridge: cfm: Kernel space implementation of CFM.

On Fri, 4 Sep 2020 09:15:24 +0000 Henrik Bjoernlund wrote:
> + rcu_read_lock();
> + b_port = rcu_dereference(mep->b_port);
> + if (!b_port)
> + return NULL;
> + skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH);
> + if (!skb)
> + return NULL;

net/bridge/br_cfm.c:171:23: warning: context imbalance in 'ccm_frame_build' - different lock contexts for basic block

2020-09-04 22:45:25

by Stephen Hemminger

[permalink] [raw]
Subject: Re: [PATCH RFC 0/7] net: bridge: cfm: Add support for Connectivity Fault Management(CFM)

On Fri, 4 Sep 2020 09:15:20 +0000
Henrik Bjoernlund <[email protected]> wrote:

> Connectivity Fault Management (CFM) is defined in 802.1Q section 12.14.
>
> Connectivity Fault Management (CFM) comprises capabilities for
> detecting, verifying, and isolating connectivity failures in
> Virtual Bridged Networks. These capabilities can be used in
> networks operated by multiple independent organizations, each
> with restricted management access to each other’s equipment.
>
> CFM functions are partitioned as follows:
> — Path discovery
> — Fault detection
> — Fault verification and isolation
> — Fault notification
> — Fault recovery
>
> The primary CFM protocol shims are called Maintenance Points (MPs).
> A MP can be either a MEP or a MHF.
> The MEP:
> -It is the Maintenance association End Point
> described in 802.1Q section 19.2.
> -It is created on a specific level (1-7) and is assuring
> that no CFM frames are passing through this MEP on lower levels.
> -It initiates and terminates/validates CFM frames on its level.
> -It can only exist on a port that is related to a bridge.
> The MHF:
> -It is the Maintenance Domain Intermediate Point
> (MIP) Half Function (MHF) described in 802.1Q section 19.3.
> -It is created on a specific level (1-7).
> -It is extracting/injecting certain CFM frame on this level.
> -It can only exist on a port that is related to a bridge.
> -Currently not supported.
>
> There are defined the following CFM protocol functions:
> -Continuity Check
> -Loopback. Currently not supported.
> -Linktrace. Currently not supported.
>
> This CFM component supports create/delete of MEP instances and
> configuration of the different CFM protocols. Also status information
> can be fetched and delivered through notification due to defect status
> change.
>
> The user interacts with CFM using the 'cfm' user space client program, the
> client talks with the kernel using netlink. The kernel will try to offload
> the requests to the HW via switchdev API (not implemented yet).
>
> Any notification emitted by CFM from the kernel can be monitored in user
> space by starting 'cfm_server' program.
>
> Currently this 'cfm' and 'cfm_server' programs are standalone placed in a
> cfm repository https://github.com/microchip-ung/cfm but it is considered
> to integrate this into 'iproute2'.
>
> Reviewed-by: Horatiu Vultur <[email protected]>
> Signed-off-by: Henrik Bjoernlund <[email protected]>

Could this be done in userspace? It is a control plane protocol.
Could it be done by using eBPF?

Adding more code in bridge impacts a large number of users of Linux distros.
It creates bloat and potential security vulnerabilities.

2020-09-06 18:22:53

by Horatiu Vultur

[permalink] [raw]
Subject: Re: [PATCH RFC 0/7] net: bridge: cfm: Add support for Connectivity Fault Management(CFM)

The 09/04/2020 15:44, Stephen Hemminger wrote:
>
> On Fri, 4 Sep 2020 09:15:20 +0000
> Henrik Bjoernlund <[email protected]> wrote:
>
> > Connectivity Fault Management (CFM) is defined in 802.1Q section 12.14.
> >
> > Connectivity Fault Management (CFM) comprises capabilities for
> > detecting, verifying, and isolating connectivity failures in
> > Virtual Bridged Networks. These capabilities can be used in
> > networks operated by multiple independent organizations, each
> > with restricted management access to each other’s equipment.
> >
> > CFM functions are partitioned as follows:
> > — Path discovery
> > — Fault detection
> > — Fault verification and isolation
> > — Fault notification
> > — Fault recovery
> >
> > The primary CFM protocol shims are called Maintenance Points (MPs).
> > A MP can be either a MEP or a MHF.
> > The MEP:
> > -It is the Maintenance association End Point
> > described in 802.1Q section 19.2.
> > -It is created on a specific level (1-7) and is assuring
> > that no CFM frames are passing through this MEP on lower levels.
> > -It initiates and terminates/validates CFM frames on its level.
> > -It can only exist on a port that is related to a bridge.
> > The MHF:
> > -It is the Maintenance Domain Intermediate Point
> > (MIP) Half Function (MHF) described in 802.1Q section 19.3.
> > -It is created on a specific level (1-7).
> > -It is extracting/injecting certain CFM frame on this level.
> > -It can only exist on a port that is related to a bridge.
> > -Currently not supported.
> >
> > There are defined the following CFM protocol functions:
> > -Continuity Check
> > -Loopback. Currently not supported.
> > -Linktrace. Currently not supported.
> >
> > This CFM component supports create/delete of MEP instances and
> > configuration of the different CFM protocols. Also status information
> > can be fetched and delivered through notification due to defect status
> > change.
> >
> > The user interacts with CFM using the 'cfm' user space client program, the
> > client talks with the kernel using netlink. The kernel will try to offload
> > the requests to the HW via switchdev API (not implemented yet).
> >
> > Any notification emitted by CFM from the kernel can be monitored in user
> > space by starting 'cfm_server' program.
> >
> > Currently this 'cfm' and 'cfm_server' programs are standalone placed in a
> > cfm repository https://github.com/microchip-ung/cfm but it is considered
> > to integrate this into 'iproute2'.
> >
> > Reviewed-by: Horatiu Vultur <[email protected]>
> > Signed-off-by: Henrik Bjoernlund <[email protected]>

Hi Stephen,

>
> Could this be done in userspace? It is a control plane protocol.
> Could it be done by using eBPF?

I might be able to answer this. We have not considered this approach of
using eBPF. Because we want actually to push this in HW extending
switchdev API. I know that this series doesn't cover the switchdev part
but we posted like this because we wanted to get some feedback from
community. We had a similar approach for MRP, where we extended the
bridge and switchdev API, so we tought that is the way to go forward.

Regarding eBPF, I can't say that it would work or not because I lack
knowledge in this.

>
> Adding more code in bridge impacts a large number of users of Linux distros.
> It creates bloat and potential security vulnerabilities.

--
/Horatiu

2020-09-07 17:20:34

by Nikolay Aleksandrov

[permalink] [raw]
Subject: Re: [PATCH RFC 0/7] net: bridge: cfm: Add support for Connectivity Fault Management(CFM)

On Sun, 2020-09-06 at 20:21 +0200, Horatiu Vultur wrote:
> The 09/04/2020 15:44, Stephen Hemminger wrote:
> > On Fri, 4 Sep 2020 09:15:20 +0000
> > Henrik Bjoernlund <[email protected]> wrote:
> >
> > > Connectivity Fault Management (CFM) is defined in 802.1Q section 12.14.
> > >
> > >
[snip]
> > > Currently this 'cfm' and 'cfm_server' programs are standalone placed in a
> > > cfm repository https://github.com/microchip-ung/cfm but it is considered
> > > to integrate this into 'iproute2'.
> > >
> > > Reviewed-by: Horatiu Vultur <[email protected]>
> > > Signed-off-by: Henrik Bjoernlund <[email protected]>
>
> Hi Stephen,
>
> > Could this be done in userspace? It is a control plane protocol.
> > Could it be done by using eBPF?
>
> I might be able to answer this. We have not considered this approach of
> using eBPF. Because we want actually to push this in HW extending
> switchdev API. I know that this series doesn't cover the switchdev part
> but we posted like this because we wanted to get some feedback from
> community. We had a similar approach for MRP, where we extended the
> bridge and switchdev API, so we tought that is the way to go forward.
>
> Regarding eBPF, I can't say that it would work or not because I lack
> knowledge in this.
>
> > Adding more code in bridge impacts a large number of users of Linux distros.
> > It creates bloat and potential security vulnerabilities.

Hi,
I also had the same initial thought - this really doesn't seem to affect the
bridge in any way, it's only collecting and transmitting information. I get
that you'd like to use the bridge as a passthrough device to switchdev to
program your hw, could you share what would be offloaded more specifically ?

All you do - snooping and blocking these packets can easily be done from user-
space with the help of ebtables, but since we need to have a software
implementation/fallback of anything being offloaded via switchdev we might need
this after all, I'd just prefer to push as much as possible to user-space.

I plan to review the individual patches tomorrow.

Thanks,
Nik

2020-09-08 11:09:19

by Henrik Bjoernlund

[permalink] [raw]
Subject: RE: [PATCH RFC 0/7] net: bridge: cfm: Add support for Connectivity Fault Management(CFM)

Hi Nik,

Thanks a lot for reviewing.

-----Original Message-----
From: Nikolay Aleksandrov <[email protected]>
Sent: 7. september 2020 15:56
To: [email protected]; Horatiu Vultur - M31836 <[email protected]>
Cc: [email protected]; Henrik Bjoernlund - M31679 <[email protected]>; [email protected]; [email protected]; [email protected]; [email protected]; Roopa Prabhu <[email protected]>; [email protected]; [email protected]; UNGLinuxDriver <[email protected]>
Subject: Re: [PATCH RFC 0/7] net: bridge: cfm: Add support for Connectivity Fault Management(CFM)

EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe

>On Sun, 2020-09-06 at 20:21 +0200, Horatiu Vultur wrote:
>> The 09/04/2020 15:44, Stephen Hemminger wrote:
>> > On Fri, 4 Sep 2020 09:15:20 +0000
>> > Henrik Bjoernlund <[email protected]> wrote:
>> >
>> > > Connectivity Fault Management (CFM) is defined in 802.1Q section 12.14.
>> > >
>> > >
>[snip]
>> > > Currently this 'cfm' and 'cfm_server' programs are standalone
>> > > placed in a cfm repository https://github.com/microchip-ung/cfm
>> > > but it is considered to integrate this into 'iproute2'.
>> > >
>> > > Reviewed-by: Horatiu Vultur <[email protected]>
>> > > Signed-off-by: Henrik Bjoernlund
>> > > <[email protected]>
>>
>> Hi Stephen,
>>
>> > Could this be done in userspace? It is a control plane protocol.
>> > Could it be done by using eBPF?
>>
>> I might be able to answer this. We have not considered this approach
>> of using eBPF. Because we want actually to push this in HW extending
>> switchdev API. I know that this series doesn't cover the switchdev
>> part but we posted like this because we wanted to get some feedback
>> from community. We had a similar approach for MRP, where we extended
>> the bridge and switchdev API, so we tought that is the way to go forward.
>>
>> Regarding eBPF, I can't say that it would work or not because I lack
>> knowledge in this.
>>
>> > Adding more code in bridge impacts a large number of users of Linux distros.
>> > It creates bloat and potential security vulnerabilities.
>
>Hi,
>I also had the same initial thought - this really doesn't seem to affect the bridge in any way, it's only collecting and transmitting information. I get that you'd like to use the bridge as a passthrough device to switchdev to program your hw, could you share what would be offloaded more specifically ?
>

Yes.

The HW will offload the periodic sending of CCM frames, and the reception.

If CCM frames are not received as expected, it will raise an interrupt.

This means that all the functionality provided in this series will be offloaded to HW.

The offloading is very important on our HW where we have a small CPU, serving many ports, with a high frequency of CFM frames.

The HW also support Link-Trace and Loop-back, which we may come back to later.

>All you do - snooping and blocking these packets can easily be done from user- space with the help of ebtables, but since we need to have a software implementation/fallback of anything being offloaded via switchdev we might need this after all, I'd just prefer to push as much as possible to user-space.
>
>I plan to review the individual patches tomorrow.
>
>Thanks,
> Nik

2020-09-08 11:52:04

by [email protected]

[permalink] [raw]
Subject: Re: [PATCH RFC 0/7] net: bridge: cfm: Add support for Connectivity Fault Management(CFM)

Hi,

On 08.09.2020 11:04, Henrik Bjoernlund - M31679 wrote:
>>On Sun, 2020-09-06 at 20:21 +0200, Horatiu Vultur wrote:
>>> The 09/04/2020 15:44, Stephen Hemminger wrote:
>>> > On Fri, 4 Sep 2020 09:15:20 +0000 Henrik Bjoernlund
>>> > <[email protected]> wrote:
>>Hi, I also had the same initial thought - this really doesn't seem to
>>affect the bridge in any way, it's only collecting and transmitting
>>information. I get that you'd like to use the bridge as a passthrough
>>device to switchdev to program your hw, could you share what would be
>>offloaded more specifically ?
>Yes.
>
>The HW will offload the periodic sending of CCM frames, and the
>reception.
>
>If CCM frames are not received as expected, it will raise an interrupt.
>
>This means that all the functionality provided in this series will be
>offloaded to HW.
>
>The offloading is very important on our HW where we have a small CPU,
>serving many ports, with a high frequency of CFM frames.
>
>The HW also support Link-Trace and Loop-back, which we may come back to
>later.
>
>>All you do - snooping and blocking these packets can easily be done
>>from user- space with the help of ebtables, but since we need to have
>>a software implementation/fallback of anything being offloaded via
>>switchdev we might need this after all, I'd just prefer to push as
>>much as possible to user-space.
In addition to Henriks comment, it is worth mentioning that we are
trying to push as much of the functionallity to user-space (learnings
from the MRP discussions).

This is why there are currently no in-kernel users of the CCM-lose
singnal. When a CCM-defect is happening the network typically needs to
be re-configured. This we are trying to keep in user-space.

>>I plan to review the individual patches tomorrow.
Thanks.

/Allan

2020-09-08 14:15:06

by Nikolay Aleksandrov

[permalink] [raw]
Subject: Re: [PATCH RFC 6/7] bridge: cfm: Netlink Notifications.

On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> This is the implementation of Netlink notifications out of CFM.
>
> Notifications are initiated whenever a state change happens in CFM.
>
[snip]
> @@ -445,6 +458,7 @@ static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb)
> peer_mep->cc_status.ccm_defect = false;
>
> /* Change in CCM defect status - notify */
> + br_cfm_notify(RTM_NEWLINK, port);
>
> /* Start CCM RX timer */
> ccm_rx_timer_start(peer_mep);
> @@ -874,6 +888,35 @@ int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
> return 0;
> }
>
> +int br_cfm_mep_count(struct net_bridge *br, u32 *count)
> +{
> + struct br_cfm_mep *mep;

Leave a blank line between local variable definitions and code.

> + *count = 0;
> +
> + rcu_read_lock();
> + list_for_each_entry_rcu(mep, &br->mep_list, head)
> + * count += 1;

please remove the extra space

> + rcu_read_unlock();
> +
> + return 0;
> +}
> +
> +int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count)
> +{
> + struct br_cfm_peer_mep *peer_mep;
> + struct br_cfm_mep *mep;

Leave a blank line between local variable definitions and code.

> + *count = 0;
> +
> + rcu_read_lock();
> + list_for_each_entry_rcu(mep, &br->mep_list, head) {
> + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head)
> + * count += 1;

please remove the extra space

> + }
> + rcu_read_unlock();
> +
> + return 0;
> +}
> +
> bool br_cfm_created(struct net_bridge *br)
> {
> return !list_empty(&br->mep_list);
> diff --git a/net/bridge/br_cfm_netlink.c b/net/bridge/br_cfm_netlink.c
> index 4e39aab1cd0b..13664ac8608a 100644
> --- a/net/bridge/br_cfm_netlink.c
> +++ b/net/bridge/br_cfm_netlink.c
> @@ -582,7 +582,9 @@ int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
> return -EMSGSIZE;
> }
>
> -int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> +int br_cfm_status_fill_info(struct sk_buff *skb,
> + struct net_bridge *br,
> + bool getlink)
> {
> struct nlattr *tb, *cfm_tb;
> struct br_cfm_mep *mep;
> @@ -613,10 +615,12 @@ int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> mep->status.rx_level_low_seen))
> goto nla_put_failure;
>
> - /* Clear all 'seen' indications */
> - mep->status.opcode_unexp_seen = false;
> - mep->status.version_unexp_seen = false;
> - mep->status.rx_level_low_seen = false;
> + if (getlink) { /* Only clear if this is a GETLINK */
> + /* Clear all 'seen' indications */
> + mep->status.opcode_unexp_seen = false;
> + mep->status.version_unexp_seen = false;
> + mep->status.rx_level_low_seen = false;
> + }
>
> nla_nest_end(skb, tb);
>
> @@ -662,10 +666,12 @@ int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> peer_mep->cc_status.seq_unexp_seen))
> goto nla_put_failure;
>
> - /* Clear all 'seen' indications */
> - peer_mep->cc_status.seen = false;
> - peer_mep->cc_status.tlv_seen = false;
> - peer_mep->cc_status.seq_unexp_seen = false;
> + if (getlink) { /* Only clear if this is a GETLINK */
> + /* Clear all 'seen' indications */
> + peer_mep->cc_status.seen = false;
> + peer_mep->cc_status.tlv_seen = false;
> + peer_mep->cc_status.seq_unexp_seen = false;

Why clear these on GETLINK? This sounds like it should be a set op.

> + }
>
> nla_nest_end(skb, tb);
> }
> diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> index 6de5cb1295f6..f2e885521f4f 100644
> --- a/net/bridge/br_netlink.c
> +++ b/net/bridge/br_netlink.c
> @@ -94,9 +94,11 @@ static size_t br_get_link_af_size_filtered(const struct net_device *dev,
> {
> struct net_bridge_vlan_group *vg = NULL;
> struct net_bridge_port *p = NULL;
> - struct net_bridge *br;
> - int num_vlan_infos;
> + struct net_bridge *br = NULL;
> + u32 num_cfm_peer_mep_infos;
> + u32 num_cfm_mep_infos;
> size_t vinfo_sz = 0;
> + int num_vlan_infos;
>
> rcu_read_lock();
> if (netif_is_bridge_port(dev)) {
> @@ -115,6 +117,52 @@ static size_t br_get_link_af_size_filtered(const struct net_device *dev,
> /* Each VLAN is returned in bridge_vlan_info along with flags */
> vinfo_sz += num_vlan_infos * nla_total_size(sizeof(struct bridge_vlan_info));
>
> + if (!(filter_mask & RTEXT_FILTER_CFM_STATUS))
> + return vinfo_sz;
> +
> + if (!br)
> + return vinfo_sz;
> +
> + /* CFM status info must be added */
> + if (br_cfm_mep_count(br, &num_cfm_mep_infos))
> + return vinfo_sz;
> +
> + if (br_cfm_peer_mep_count(br, &num_cfm_peer_mep_infos))
> + return vinfo_sz;
> +

Can these return non-0 at all?

> + vinfo_sz += nla_total_size(0); /* IFLA_BRIDGE_CFM */
> + /* For each status struct the MEP instance (u32) is added */
> + /* MEP instance (u32) + br_cfm_mep_status */
> + vinfo_sz += num_cfm_mep_infos *
> + /*IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE */
> + (nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN */
> + + nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN */
> + + nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN */
> + + nla_total_size(sizeof(u32)));
> + /* MEP instance (u32) + br_cfm_cc_peer_status */
> + vinfo_sz += num_cfm_peer_mep_infos *
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE */
> + (nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID */
> + + nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT */
> + + nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI */
> + + nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE */
> + + nla_total_size(sizeof(u8))
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE */
> + + nla_total_size(sizeof(u8))
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN */
> + + nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN */
> + + nla_total_size(sizeof(u32))
> + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN */
> + + nla_total_size(sizeof(u32)));
> +
> return vinfo_sz;
> }
>
> @@ -378,7 +426,8 @@ static int br_fill_ifvlaninfo(struct sk_buff *skb,
> static int br_fill_ifinfo(struct sk_buff *skb,
> const struct net_bridge_port *port,
> u32 pid, u32 seq, int event, unsigned int flags,
> - u32 filter_mask, const struct net_device *dev)
> + u32 filter_mask, const struct net_device *dev,
> + bool getlink)
> {
> u8 operstate = netif_running(dev) ? dev->operstate : IF_OPER_DOWN;
> struct net_bridge *br;
> @@ -515,7 +564,7 @@ static int br_fill_ifinfo(struct sk_buff *skb,
> goto nla_put_failure;
>
> rcu_read_lock();
> - err = br_cfm_status_fill_info(skb, br);
> + err = br_cfm_status_fill_info(skb, br, getlink);
> rcu_read_unlock();
>
> if (err)
> @@ -533,11 +582,9 @@ static int br_fill_ifinfo(struct sk_buff *skb,
> return -EMSGSIZE;
> }
>
> -/* Notify listeners of a change in bridge or port information */
> -void br_ifinfo_notify(int event, const struct net_bridge *br,
> - const struct net_bridge_port *port)
> +void br_info_notify(int event, const struct net_bridge *br,
> + const struct net_bridge_port *port, u32 filter)
> {
> - u32 filter = RTEXT_FILTER_BRVLAN_COMPRESSED;
> struct net_device *dev;
> struct sk_buff *skb;
> int err = -ENOBUFS;
> @@ -562,7 +609,7 @@ void br_ifinfo_notify(int event, const struct net_bridge *br,
> if (skb == NULL)
> goto errout;
>
> - err = br_fill_ifinfo(skb, port, 0, 0, event, 0, filter, dev);
> + err = br_fill_ifinfo(skb, port, 0, 0, event, 0, filter, dev, false);
> if (err < 0) {
> /* -EMSGSIZE implies BUG in br_nlmsg_size() */
> WARN_ON(err == -EMSGSIZE);
> @@ -575,6 +622,15 @@ void br_ifinfo_notify(int event, const struct net_bridge *br,
> rtnl_set_sk_err(net, RTNLGRP_LINK, err);
> }
>
> +/* Notify listeners of a change in bridge or port information */
> +void br_ifinfo_notify(int event, const struct net_bridge *br,
> + const struct net_bridge_port *port)
> +{
> + u32 filter = RTEXT_FILTER_BRVLAN_COMPRESSED;
> +
> + return br_info_notify(event, br, port, filter);
> +}
> +
> /*
> * Dump information about all ports, in response to GETLINK
> */
> @@ -591,7 +647,7 @@ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq,
> return 0;
>
> return br_fill_ifinfo(skb, port, pid, seq, RTM_NEWLINK, nlflags,
> - filter_mask, dev);
> + filter_mask, dev, true);
> }
>
> static int br_vlan_info(struct net_bridge *br, struct net_bridge_port *p,
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index fe36592f7525..53bcbdd21f34 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -1370,7 +1370,11 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
> bool br_cfm_created(struct net_bridge *br);
> int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
> -int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br);
> +int br_cfm_status_fill_info(struct sk_buff *skb,
> + struct net_bridge *br,
> + bool getlink);
> +int br_cfm_mep_count(struct net_bridge *br, u32 *count);
> +int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count);
> #else
> static inline int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> struct nlattr *attr, int cmd,
> @@ -1394,7 +1398,19 @@ static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge
> return -EOPNOTSUPP;
> }
>
> -static inline int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> +static inline int br_cfm_status_fill_info(struct sk_buff *skb,
> + struct net_bridge *br,
> + bool getlink)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static inline int br_cfm_mep_count(struct net_bridge *br, u32 *count)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static inline int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count)
> {
> return -EOPNOTSUPP;
> }
> @@ -1406,6 +1422,8 @@ int br_netlink_init(void);
> void br_netlink_fini(void);
> void br_ifinfo_notify(int event, const struct net_bridge *br,
> const struct net_bridge_port *port);
> +void br_info_notify(int event, const struct net_bridge *br,
> + const struct net_bridge_port *port, u32 filter);
> int br_setlink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags,
> struct netlink_ext_ack *extack);
> int br_dellink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags);

2020-09-08 14:45:49

by Nikolay Aleksandrov

[permalink] [raw]
Subject: Re: [PATCH RFC 5/7] bridge: cfm: Netlink Interface.

On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> This is the implementation of CFM netlink configuration
> and status information interface.
>
> Add new nested netlink attributes. These attributes are used by the
> user space to create/delete/configure CFM instances and get status.
> Also they are used by the kernel to notify the user space when changes
> in any status happens.
[snip]
> __u64 transition_fwd;
> diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
> index 9b814c92de12..fdd408f6a5d2 100644
> --- a/include/uapi/linux/rtnetlink.h
> +++ b/include/uapi/linux/rtnetlink.h
> @@ -779,6 +779,8 @@ enum {
> #define RTEXT_FILTER_BRVLAN_COMPRESSED (1 << 2)
> #define RTEXT_FILTER_SKIP_STATS (1 << 3)
> #define RTEXT_FILTER_MRP (1 << 4)
> +#define RTEXT_FILTER_CFM_CONFIG (1 << 5)
> +#define RTEXT_FILTER_CFM_STATUS (1 << 6)
>
> /* End of information exported to user level */
>
> diff --git a/net/bridge/Makefile b/net/bridge/Makefile
> index ddc0a9192348..4702702a74d3 100644
> --- a/net/bridge/Makefile
> +++ b/net/bridge/Makefile
> @@ -28,4 +28,4 @@ obj-$(CONFIG_NETFILTER) += netfilter/
>
> bridge-$(CONFIG_BRIDGE_MRP) += br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o
>
> -bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o
> +bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o br_cfm_netlink.o
> diff --git a/net/bridge/br_cfm_netlink.c b/net/bridge/br_cfm_netlink.c
> new file mode 100644
> index 000000000000..4e39aab1cd0b
> --- /dev/null
> +++ b/net/bridge/br_cfm_netlink.c
> @@ -0,0 +1,684 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <net/genetlink.h>
> +
> +#include "br_private.h"
> +#include "br_private_cfm.h"
> +
> +static inline struct mac_addr nla_get_mac(const struct nlattr *nla)
> +{
> + struct mac_addr mac;
> +
> + nla_memcpy(&mac.addr, nla, sizeof(mac.addr));
> +
> + return mac;
> +}
> +
> +static inline struct br_cfm_maid nla_get_maid(const struct nlattr *nla)
> +{
> + struct br_cfm_maid maid;
> +
> + nla_memcpy(&maid.data, nla, sizeof(maid.data));
> +
> + return maid;
> +}

IMO, these 1-line helpers don't really help readability.

> +
> +static inline int nla_put_u64(struct sk_buff *skb, int attrtype, u64 value)
> +{
> + u64 tmp = value;
> +
> + return nla_put(skb, attrtype, sizeof(u64), &tmp);
> +}

What?! Read include/net/netlink.h

> +
> +static const struct nla_policy
> +br_cfm_policy[IFLA_BRIDGE_CFM_MAX + 1] = {
> + [IFLA_BRIDGE_CFM_UNSPEC] = { .type = NLA_REJECT },
> + [IFLA_BRIDGE_CFM_MEP_CREATE] = { .type = NLA_NESTED },
> + [IFLA_BRIDGE_CFM_MEP_DELETE] = { .type = NLA_NESTED },
> + [IFLA_BRIDGE_CFM_MEP_CONFIG] = { .type = NLA_NESTED },
> + [IFLA_BRIDGE_CFM_CC_CONFIG] = { .type = NLA_NESTED },
> + [IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD] = { .type = NLA_NESTED },
> + [IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE] = { .type = NLA_NESTED },
> + [IFLA_BRIDGE_CFM_CC_RDI] = { .type = NLA_NESTED },
> + [IFLA_BRIDGE_CFM_CC_CCM_TX] = { .type = NLA_NESTED },
> +};
> +
> +static const struct nla_policy
> +br_cfm_mep_create_policy[IFLA_BRIDGE_CFM_MEP_CREATE_MAX + 1] = {
> + [IFLA_BRIDGE_CFM_MEP_CREATE_UNSPEC] = { .type = NLA_REJECT },
> + [IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX] = { .type = NLA_U32 },
> +};
> +
> +static const struct nla_policy
> +br_cfm_mep_delete_policy[IFLA_BRIDGE_CFM_MEP_DELETE_MAX + 1] = {
> + [IFLA_BRIDGE_CFM_MEP_DELETE_UNSPEC] = { .type = NLA_REJECT },
> + [IFLA_BRIDGE_CFM_MEP_DELETE_INSTANCE] = { .type = NLA_U32 },
> +};
> +
> +static const struct nla_policy
> +br_cfm_mep_config_policy[IFLA_BRIDGE_CFM_MEP_CONFIG_MAX + 1] = {
> + [IFLA_BRIDGE_CFM_MEP_CONFIG_UNSPEC] = { .type = NLA_REJECT },
> + [IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC] = NLA_POLICY_ETH_ADDR,
> + [IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID] = { .type = NLA_U32 },
> +};
> +
> +static const struct nla_policy
> +br_cfm_cc_config_policy[IFLA_BRIDGE_CFM_CC_CONFIG_MAX + 1] = {
> + [IFLA_BRIDGE_CFM_CC_CONFIG_UNSPEC] = { .type = NLA_REJECT },
> + [IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID] = {
> + .type = NLA_BINARY, .len = CFM_MAID_LENGTH },
> +};
> +
> +static const struct nla_policy
> +br_cfm_cc_peer_mep_policy[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1] = {
> + [IFLA_BRIDGE_CFM_CC_PEER_MEP_UNSPEC] = { .type = NLA_REJECT },
> + [IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_PEER_MEPID] = { .type = NLA_U32 },
> +};
> +
> +static const struct nla_policy
> +br_cfm_cc_rdi_policy[IFLA_BRIDGE_CFM_CC_RDI_MAX + 1] = {
> + [IFLA_BRIDGE_CFM_CC_RDI_UNSPEC] = { .type = NLA_REJECT },
> + [IFLA_BRIDGE_CFM_CC_RDI_INSTANCE] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_RDI_RDI] = { .type = NLA_U32 },
> +};
> +
> +static const struct nla_policy
> +br_cfm_cc_ccm_tx_policy[IFLA_BRIDGE_CFM_CC_CCM_TX_MAX + 1] = {
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_UNSPEC] = { .type = NLA_REJECT },
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC] = NLA_POLICY_ETH_ADDR,
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE] = { .type = NLA_U8 },
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV] = { .type = NLA_U32 },
> + [IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE] = { .type = NLA_U8 },
> +};
> +
> +static int br_mep_create_parse(struct net_bridge *br, struct nlattr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_CREATE_MAX + 1];
> + struct br_cfm_mep_create create;
> + u32 instance;
> + int err;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_CREATE_MAX, attr,
> + br_cfm_mep_create_policy, extack);
> + if (err)
> + return err;
> +
> + if (!tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE] ||
> + !tb[IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN] ||
> + !tb[IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION] ||
> + !tb[IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX]) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Missing attribute: INSTANCE or DOMAIN or DIRECTION or VID or IFINDEX");

Break these into separate errors.

> + return -EINVAL;
> + }
> +
> + memset(&create, 0, sizeof(create));
> +
> + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]);
> + create.domain = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN]);
> + create.direction = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION]);
> + create.ifindex = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX]);
> +
> + return br_cfm_mep_create(br, instance, &create, extack);
> +}
> +
> +static int br_mep_delete_parse(struct net_bridge *br, struct nlattr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_DELETE_MAX + 1];
> + u32 instance;
> + int err;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_DELETE_MAX, attr,
> + br_cfm_mep_delete_policy, extack);
> + if (err)
> + return err;
> +
> + if (!tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Missing attribute: INSTANCE");

"Missing instance attribute". Same for all similar messages.

> + return -EINVAL;
> + }
> +
> + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]);
> +
> + return br_cfm_mep_delete(br, instance, extack);
> +}
> +
> +static int br_mep_config_parse(struct net_bridge *br, struct nlattr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MAX + 1];
> + struct br_cfm_mep_config config;
> + u32 instance;
> + int err;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_CONFIG_MAX, attr,
> + br_cfm_mep_config_policy, extack);
> + if (err)
> + return err;
> +
> + if (!tb[IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE] ||
> + !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC] ||
> + !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL] ||
> + !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID]) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Missing attribute: INSTANCE or UNICAST_MAC or MDLEVEL or MEPID or VID");

Break these into separate errors.

> + return -EINVAL;
> + }
> +
> + memset(&config, 0, sizeof(config));
> +
> + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE]);
> + config.unicast_mac = nla_get_mac(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC]);
> + config.mdlevel = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL]);
> + config.mepid = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID]);
> + config.vid = 0;
> +
> + return br_cfm_mep_config_set(br, instance, &config, extack);
> +}
> +
> +static int br_cc_config_parse(struct net_bridge *br, struct nlattr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_CONFIG_MAX + 1];
> + struct br_cfm_cc_config config;
> + u32 instance;
> + int err;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_CONFIG_MAX, attr,
> + br_cfm_cc_config_policy, extack);
> + if (err)
> + return err;
> +
> + if (!tb[IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID]) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Missing attribute: INSTANCE or ENABLE or INTERVAL or PRIORITY or MAID");

Break these into separate errors.

> + return -EINVAL;
> + }
> +
> + memset(&config, 0, sizeof(config));
> +
> + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE]);
> + config.enable = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE]);
> + config.exp_interval = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL]);
> + config.exp_priority = 0;
> + config.exp_maid = nla_get_maid(tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID]);
> +
> + return br_cfm_cc_config_set(br, instance, &config, extack);
> +}
> +
> +static int br_cc_peer_mep_add_parse(struct net_bridge *br, struct nlattr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1];
> + u32 instance, peer_mep_id;
> + int err;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX, attr,
> + br_cfm_cc_peer_mep_policy, extack);
> + if (err)
> + return err;
> +
> + if (!tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] ||
> + !tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Missing attribute: INSTANCE or PEER_MEP_ID");

Break these into separate errors.

> + return -EINVAL;
> + }
> +
> + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE]);
> + peer_mep_id = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]);
> +
> + return br_cfm_cc_peer_mep_add(br, instance, peer_mep_id, extack);
> +}
> +
> +static int br_cc_peer_mep_remove_parse(struct net_bridge *br, struct nlattr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1];
> + u32 instance, peer_mep_id;
> + int err;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX, attr,
> + br_cfm_cc_peer_mep_policy, extack);
> + if (err)
> + return err;
> +
> + if (!tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] ||
> + !tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Missing attribute: INSTANCE or PEER_MEP_ID");

Break these into separate errors.

> + return -EINVAL;
> + }
> +
> + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE]);
> + peer_mep_id = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]);
> +
> + return br_cfm_cc_peer_mep_remove(br, instance, peer_mep_id, extack);
> +}
> +
> +static int br_cc_rdi_parse(struct net_bridge *br, struct nlattr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_RDI_MAX + 1];
> + u32 instance, rdi;
> + int err;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_RDI_MAX, attr,
> + br_cfm_cc_rdi_policy, extack);
> + if (err)
> + return err;
> +
> + if (!tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE] ||
> + !tb[IFLA_BRIDGE_CFM_CC_RDI_RDI]) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Missing attribute: INSTANCE or RDI");

Break these into separate errors.

> + return -EINVAL;
> + }
> +
> + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE]);
> + rdi = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_RDI]);
> +
> + return br_cfm_cc_rdi_set(br, instance, rdi, extack);
> +}
> +
> +static int br_cc_ccm_tx_parse(struct net_bridge *br, struct nlattr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_CCM_TX_MAX + 1];
> + u32 instance;
> + struct br_cfm_cc_ccm_tx_info tx_info;
> + int err;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_MAX, attr,
> + br_cfm_cc_ccm_tx_policy, extack);
> + if (err)
> + return err;
> +
> + if (!tb[IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV] ||
> + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE]) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Missing attribute: INSTANCE or PRIORITY or DEI or DMAC or SEQ_NO_UPDATE or PERIOD or IF_TLV or IF_TLV_VALUE or PORT_TLV or PORT_TLV_VALUE");

Break these into separate errors.

> + return -EINVAL;
> + }
> +
> + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE]);
> + tx_info.priority = 0;
> + tx_info.dei = 0;
> + tx_info.dmac = nla_get_mac(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC]);
> + tx_info.seq_no_update = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE]);
> + tx_info.period = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD]);
> + tx_info.if_tlv = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV]);
> + tx_info.if_tlv_value = nla_get_u8(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE]);
> + tx_info.port_tlv = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV]);
> + tx_info.port_tlv_value = nla_get_u8(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE]);
> +
> + return br_cfm_cc_ccm_tx(br, instance, &tx_info, extack);
> +}
> +
> +int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> + struct nlattr *attr, int cmd, struct netlink_ext_ack *extack)
> +{
> + struct nlattr *tb[IFLA_BRIDGE_CFM_MAX + 1];
> + int err;
> +
> + /* When this function is called for a port then the br pointer is
> + * invalid, therefor set the br to point correctly
> + */
> + if (p)
> + br = p->br;
> +
> + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MAX, attr,
> + br_cfm_policy, extack);
> + if (err)
> + return err;
> +
> + if (tb[IFLA_BRIDGE_CFM_MEP_CREATE]) {
> + err = br_mep_create_parse(br, tb[IFLA_BRIDGE_CFM_MEP_CREATE],
> + extack);
> + if (err)
> + return err;
> + }
> +
> + if (tb[IFLA_BRIDGE_CFM_MEP_DELETE]) {
> + err = br_mep_delete_parse(br, tb[IFLA_BRIDGE_CFM_MEP_DELETE],
> + extack);
> + if (err)
> + return err;
> + }
> +
> + if (tb[IFLA_BRIDGE_CFM_MEP_CONFIG]) {
> + err = br_mep_config_parse(br, tb[IFLA_BRIDGE_CFM_MEP_CONFIG],
> + extack);
> + if (err)
> + return err;
> + }
> +
> + if (tb[IFLA_BRIDGE_CFM_CC_CONFIG]) {
> + err = br_cc_config_parse(br, tb[IFLA_BRIDGE_CFM_CC_CONFIG],
> + extack);
> + if (err)
> + return err;
> + }
> +
> + if (tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD]) {
> + err = br_cc_peer_mep_add_parse(br, tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD],
> + extack);
> + if (err)
> + return err;
> + }
> +
> + if (tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE]) {
> + err = br_cc_peer_mep_remove_parse(br, tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE],
> + extack);
> + if (err)
> + return err;
> + }
> +
> + if (tb[IFLA_BRIDGE_CFM_CC_RDI]) {
> + err = br_cc_rdi_parse(br, tb[IFLA_BRIDGE_CFM_CC_RDI],
> + extack);
> + if (err)
> + return err;
> + }
> +
> + if (tb[IFLA_BRIDGE_CFM_CC_CCM_TX]) {
> + err = br_cc_ccm_tx_parse(br, tb[IFLA_BRIDGE_CFM_CC_CCM_TX],
> + extack);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
> +{
> + struct nlattr *tb, *cfm_tb;
> + struct br_cfm_mep *mep;
> + struct br_cfm_peer_mep *peer_mep;
> +
> + cfm_tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM);

Why _noflag everywhere?

> + if (!cfm_tb)
> + return -EMSGSIZE;
> +
> + list_for_each_entry_rcu(mep, &br->mep_list, head) {
> + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_CREATE_INFO);
> + if (!tb)
> + goto nla_info_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE,
> + mep->instance))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN,
> + mep->create.domain))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION,
> + mep->create.direction))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX,
> + mep->create.ifindex))
> + goto nla_put_failure;
> +
> + nla_nest_end(skb, tb);
> +
> + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_INFO);
> +
> + if (!tb)
> + goto nla_info_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE,
> + mep->instance))
> + goto nla_put_failure;
> +
> + if (nla_put(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC,
> + sizeof(mep->config.unicast_mac.addr),
> + mep->config.unicast_mac.addr))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL,
> + mep->config.mdlevel))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID,
> + mep->config.mepid))
> + goto nla_put_failure;
> +
> + nla_nest_end(skb, tb);
> +
> + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_CONFIG_INFO);
> +
> + if (!tb)
> + goto nla_info_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE,
> + mep->instance))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE,
> + mep->cc_config.enable))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL,
> + mep->cc_config.exp_interval))
> + goto nla_put_failure;
> +
> + if (nla_put(skb, IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID,
> + sizeof(mep->cc_config.exp_maid.data),
> + mep->cc_config.exp_maid.data))
> + goto nla_put_failure;
> +
> + nla_nest_end(skb, tb);
> +
> + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_RDI_INFO);
> +
> + if (!tb)
> + goto nla_info_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_RDI_INSTANCE,
> + mep->instance))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_RDI_RDI,
> + mep->rdi))
> + goto nla_put_failure;
> +
> + nla_nest_end(skb, tb);
> +
> + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_INFO);
> +
> + if (!tb)
> + goto nla_info_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE,
> + mep->instance))
> + goto nla_put_failure;
> +
> + if (nla_put(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC,
> + sizeof(mep->cc_ccm_tx_info.dmac),
> + mep->cc_ccm_tx_info.dmac.addr))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE,
> + mep->cc_ccm_tx_info.seq_no_update))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD,
> + mep->cc_ccm_tx_info.period))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV,
> + mep->cc_ccm_tx_info.if_tlv))
> + goto nla_put_failure;
> +
> + if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE,
> + mep->cc_ccm_tx_info.if_tlv_value))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV,
> + mep->cc_ccm_tx_info.port_tlv))
> + goto nla_put_failure;
> +
> + if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE,
> + mep->cc_ccm_tx_info.port_tlv_value))
> + goto nla_put_failure;
> +
> + nla_nest_end(skb, tb);
> +
> + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head) {
> + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO);
> +
> + if (!tb)
> + goto nla_info_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE,
> + mep->instance))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_MEPID,
> + peer_mep->mepid))
> + goto nla_put_failure;
> +
> + nla_nest_end(skb, tb);
> + }
> + }
> + nla_nest_end(skb, cfm_tb);
> +
> + return 0;
> +
> +nla_put_failure:
> + nla_nest_cancel(skb, tb);
> +
> +nla_info_failure:
> + nla_nest_cancel(skb, cfm_tb);
> +
> + return -EMSGSIZE;
> +}
> +
> +int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> +{
> + struct nlattr *tb, *cfm_tb;
> + struct br_cfm_mep *mep;
> + struct br_cfm_peer_mep *peer_mep;
> +
> + cfm_tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM);
> + if (!cfm_tb)
> + return -EMSGSIZE;
> +
> + list_for_each_entry_rcu(mep, &br->mep_list, head) {
> + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_STATUS_INFO);
> + if (!tb)
> + goto nla_info_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE,
> + mep->instance))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN,
> + mep->status.opcode_unexp_seen))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN,
> + mep->status.version_unexp_seen))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN,
> + mep->status.rx_level_low_seen))
> + goto nla_put_failure;
> +
> + /* Clear all 'seen' indications */
> + mep->status.opcode_unexp_seen = false;
> + mep->status.version_unexp_seen = false;
> + mep->status.rx_level_low_seen = false;
> +
> + nla_nest_end(skb, tb);
> +
> + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head) {
> + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO);
> +
> + if (!tb)
> + goto nla_info_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE,
> + mep->instance))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID,
> + peer_mep->mepid))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT,
> + peer_mep->cc_status.ccm_defect))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI,
> + peer_mep->cc_status.rdi))
> + goto nla_put_failure;
> +
> + if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE,
> + peer_mep->cc_status.port_tlv_value))
> + goto nla_put_failure;
> +
> + if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE,
> + peer_mep->cc_status.if_tlv_value))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN,
> + peer_mep->cc_status.seen))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN,
> + peer_mep->cc_status.tlv_seen))
> + goto nla_put_failure;
> +
> + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN,
> + peer_mep->cc_status.seq_unexp_seen))
> + goto nla_put_failure;
> +
> + /* Clear all 'seen' indications */
> + peer_mep->cc_status.seen = false;
> + peer_mep->cc_status.tlv_seen = false;
> + peer_mep->cc_status.seq_unexp_seen = false;
> +
> + nla_nest_end(skb, tb);
> + }
> + }
> + nla_nest_end(skb, cfm_tb);
> +
> + return 0;
> +
> +nla_put_failure:
> + nla_nest_cancel(skb, tb);
> +
> +nla_info_failure:
> + nla_nest_cancel(skb, cfm_tb);
> +
> + return -EMSGSIZE;
> +}
> diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> index 8a71c60fa357..6de5cb1295f6 100644
> --- a/net/bridge/br_netlink.c
> +++ b/net/bridge/br_netlink.c
> @@ -16,6 +16,7 @@
>
> #include "br_private.h"
> #include "br_private_stp.h"
> +#include "br_private_cfm.h"
> #include "br_private_tunnel.h"
>
> static int __get_num_vlan_infos(struct net_bridge_vlan_group *vg,
> @@ -481,6 +482,48 @@ static int br_fill_ifinfo(struct sk_buff *skb,
> nla_nest_end(skb, af);
> }
>
> + if (filter_mask & RTEXT_FILTER_CFM_CONFIG) {
> + struct nlattr *af;
> + int err;
> +
> + if (!br_cfm_created(br) || port)
> + goto done;
> +
> + af = nla_nest_start_noflag(skb, IFLA_AF_SPEC);
> + if (!af)
> + goto nla_put_failure;
> +
> + rcu_read_lock();
> + err = br_cfm_config_fill_info(skb, br);
> + rcu_read_unlock();
> +
> + if (err)
> + goto nla_put_failure;
> +
> + nla_nest_end(skb, af);
> + }
> +
> + if (filter_mask & RTEXT_FILTER_CFM_STATUS) {
> + struct nlattr *af;
> + int err;
> +
> + if (!br_cfm_created(br) || port)
> + goto done;
> +
> + af = nla_nest_start_noflag(skb, IFLA_AF_SPEC);
> + if (!af)
> + goto nla_put_failure;
> +
> + rcu_read_lock();
> + err = br_cfm_status_fill_info(skb, br);
> + rcu_read_unlock();
> +
> + if (err)
> + goto nla_put_failure;
> +
> + nla_nest_end(skb, af);
> + }

I actually noticed this just now, you can't have multiple IFLA_AF_SPEC
attributes. It seems we already have that problem with MRP.
Since filter_mask is a mask one could request multiple rtext attributes.

> +
> done:
> nlmsg_end(skb, nlh);
> return 0;
> @@ -542,7 +585,9 @@ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq,
>
> if (!port && !(filter_mask & RTEXT_FILTER_BRVLAN) &&
> !(filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED) &&
> - !(filter_mask & RTEXT_FILTER_MRP))
> + !(filter_mask & RTEXT_FILTER_MRP) &&
> + !(filter_mask & RTEXT_FILTER_CFM_CONFIG) &&
> + !(filter_mask & RTEXT_FILTER_CFM_STATUS))
> return 0;
>
> return br_fill_ifinfo(skb, port, pid, seq, RTM_NEWLINK, nlflags,
> @@ -704,6 +749,11 @@ static int br_afspec(struct net_bridge *br,
> if (err)
> return err;
> break;
> + case IFLA_BRIDGE_CFM:
> + err = br_cfm_parse(br, p, attr, cmd, extack);
> + if (err)
> + return err;
> + break;
> }
> }
>
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index 385a6a6aa17e..fe36592f7525 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -1365,9 +1365,20 @@ static inline int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br)
>
> /* br_cfm.c */
> #if IS_ENABLED(CONFIG_BRIDGE_CFM)
> +int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> + struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
> int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
> bool br_cfm_created(struct net_bridge *br);
> +int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
> +int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br);
> #else
> +static inline int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> + struct nlattr *attr, int cmd,
> + struct netlink_ext_ack *extack)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> static inline int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb)
> {
> return -EOPNOTSUPP;
> @@ -1377,6 +1388,16 @@ static inline bool br_cfm_created(struct net_bridge *br)
> {
> return false;
> }
> +
> +static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static inline int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> +{
> + return -EOPNOTSUPP;
> +}
> #endif
>
> /* br_netlink.c */

2020-09-08 16:28:35

by Nikolay Aleksandrov

[permalink] [raw]
Subject: Re: [PATCH RFC 1/7] net: bridge: extend the process of special frames

On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> This patch extends the processing of frames in the bridge. Currently MRP
> frames needs special processing and the current implementation doesn't
> allow a nice way to process different frame types. Therefore try to
> improve this by adding a list that contains frame types that need
> special processing. This list is iterated for each input frame and if
> there is a match based on frame type then these functions will be called
> and decide what to do with the frame. It can process the frame then the
> bridge doesn't need to do anything or don't process so then the bridge
> will do normal forwarding.
>
> Signed-off-by: Henrik Bjoernlund <[email protected]>
> ---
> net/bridge/br_device.c | 1 +
> net/bridge/br_input.c | 31 ++++++++++++++++++++++++++++++-
> net/bridge/br_mrp.c | 19 +++++++++++++++----
> net/bridge/br_private.h | 18 ++++++++++++------
> 4 files changed, 58 insertions(+), 11 deletions(-)
>

Hi,
First I must say I do like this approach, thanks for generalizing it.
You can switch to a hlist so that there's just 1 pointer in the head.
I don't think you need list when you're walking only in one direction.
A few more minor comments below.

> diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> index 9a2fb4aa1a10..a9232db03108 100644
> --- a/net/bridge/br_device.c
> +++ b/net/bridge/br_device.c
> @@ -473,6 +473,7 @@ void br_dev_setup(struct net_device *dev)
> spin_lock_init(&br->lock);
> INIT_LIST_HEAD(&br->port_list);
> INIT_HLIST_HEAD(&br->fdb_list);
> + INIT_LIST_HEAD(&br->ftype_list);
> #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> INIT_LIST_HEAD(&br->mrp_list);
> #endif
> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> index 59a318b9f646..0f475b21094c 100644
> --- a/net/bridge/br_input.c
> +++ b/net/bridge/br_input.c
> @@ -254,6 +254,21 @@ static int nf_hook_bridge_pre(struct sk_buff *skb, struct sk_buff **pskb)
> return RX_HANDLER_CONSUMED;
> }
>
> +/* Return 0 if the frame was not processed otherwise 1
> + * note: already called with rcu_read_lock
> + */
> +static int br_process_frame_type(struct net_bridge_port *p,
> + struct sk_buff *skb)
> +{
> + struct br_frame_type *tmp;
> +
> + list_for_each_entry_rcu(tmp, &p->br->ftype_list, list) {
> + if (unlikely(tmp->type == skb->protocol))
> + return tmp->func(p, skb);
> + }

Nit: you can drop the {}.

> + return 0;
> +}
> +
> /*
> * Return NULL if skb is handled
> * note: already called with rcu_read_lock
> @@ -343,7 +358,7 @@ static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
> }
> }
>
> - if (unlikely(br_mrp_process(p, skb)))
> + if (unlikely(br_process_frame_type(p, skb)))
> return RX_HANDLER_PASS;
>
> forward:
> @@ -380,3 +395,17 @@ rx_handler_func_t *br_get_rx_handler(const struct net_device *dev)
>
> return br_handle_frame;
> }
> +
> +void br_add_frame(struct net_bridge *br, struct br_frame_type *ft)
> +{
> + list_add_rcu(&ft->list, &br->ftype_list);
> +}
> +
> +void br_del_frame(struct net_bridge *br, struct br_frame_type *ft)
> +{
> + struct br_frame_type *tmp;
> +
> + list_for_each_entry(tmp, &br->ftype_list, list)
> + if (ft == tmp)
> + list_del_rcu(&ft->list);
> +}
> diff --git a/net/bridge/br_mrp.c b/net/bridge/br_mrp.c
> index b36689e6e7cb..0428e1785041 100644
> --- a/net/bridge/br_mrp.c
> +++ b/net/bridge/br_mrp.c
> @@ -6,6 +6,13 @@
> static const u8 mrp_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x1 };
> static const u8 mrp_in_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x3 };
>
> +static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb);
> +
> +static struct br_frame_type mrp_frame_type __read_mostly = {
> + .type = cpu_to_be16(ETH_P_MRP),
> + .func = br_mrp_process,
> +};
> +
> static bool br_mrp_is_ring_port(struct net_bridge_port *p_port,
> struct net_bridge_port *s_port,
> struct net_bridge_port *port)
> @@ -445,6 +452,9 @@ static void br_mrp_del_impl(struct net_bridge *br, struct br_mrp *mrp)
>
> list_del_rcu(&mrp->list);
> kfree_rcu(mrp, rcu);
> +
> + if (list_empty(&br->mrp_list))
> + br_del_frame(br, &mrp_frame_type);
> }
>
> /* Adds a new MRP instance.
> @@ -493,6 +503,9 @@ int br_mrp_add(struct net_bridge *br, struct br_mrp_instance *instance)
> spin_unlock_bh(&br->lock);
> rcu_assign_pointer(mrp->s_port, p);
>
> + if (list_empty(&br->mrp_list))
> + br_add_frame(br, &mrp_frame_type);
> +
> INIT_DELAYED_WORK(&mrp->test_work, br_mrp_test_work_expired);
> INIT_DELAYED_WORK(&mrp->in_test_work, br_mrp_in_test_work_expired);
> list_add_tail_rcu(&mrp->list, &br->mrp_list);
> @@ -1172,15 +1185,13 @@ static int br_mrp_rcv(struct net_bridge_port *p,
> * normal forwarding.
> * note: already called with rcu_read_lock
> */
> -int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
> +static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
> {
> /* If there is no MRP instance do normal forwarding */
> if (likely(!(p->flags & BR_MRP_AWARE)))
> goto out;
>
> - if (unlikely(skb->protocol == htons(ETH_P_MRP)))
> - return br_mrp_rcv(p, skb, p->dev);
> -
> + return br_mrp_rcv(p, skb, p->dev);
> out:
> return 0;
> }
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index baa1500f384f..e67c6d9e8bea 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -89,6 +89,13 @@ struct bridge_mcast_stats {
> };
> #endif
>
> +struct br_frame_type {
> + __be16 type;
> + int (*func)(struct net_bridge_port *port,
> + struct sk_buff *skb);

Perhaps frame_handler or something similar would be a better name for the
callback ?

> + struct list_head list;
> +};
> +
> struct br_vlan_stats {
> u64 rx_bytes;
> u64 rx_packets;
> @@ -433,6 +440,8 @@ struct net_bridge {
> #endif
> struct hlist_head fdb_list;
>
> + struct list_head ftype_list;

Could you please expand this to frame_type_list ?

> +
> #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> struct list_head mrp_list;
> #endif
> @@ -708,6 +717,9 @@ int nbp_backup_change(struct net_bridge_port *p, struct net_device *backup_dev);
> int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb);
> rx_handler_func_t *br_get_rx_handler(const struct net_device *dev);
>
> +void br_add_frame(struct net_bridge *br, struct br_frame_type *ft);
> +void br_del_frame(struct net_bridge *br, struct br_frame_type *ft);
> +
> static inline bool br_rx_handler_check_rcu(const struct net_device *dev)
> {
> return rcu_dereference(dev->rx_handler) == br_get_rx_handler(dev);
> @@ -1320,7 +1332,6 @@ extern int (*br_fdb_test_addr_hook)(struct net_device *dev, unsigned char *addr)
> #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p,
> struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
> -int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb);
> bool br_mrp_enabled(struct net_bridge *br);
> void br_mrp_port_del(struct net_bridge *br, struct net_bridge_port *p);
> int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br);
> @@ -1332,11 +1343,6 @@ static inline int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p,
> return -EOPNOTSUPP;
> }
>
> -static inline int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
> -{
> - return 0;
> -}
> -
> static inline bool br_mrp_enabled(struct net_bridge *br)
> {
> return false;

2020-09-08 17:05:58

by Nikolay Aleksandrov

[permalink] [raw]
Subject: Re: [PATCH RFC 4/7] bridge: cfm: Kernel space implementation of CFM.

On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> This is the implementation of the CFM protocol according to
> 802.1Q section 12.14.
>
> Connectivity Fault Management (CFM) comprises capabilities for
> detecting, verifying, and isolating connectivity failures in
> Virtual Bridged Networks. These capabilities can be used in
> networks operated by multiple independent organizations, each
> with restricted management access to each other’s equipment.
>
> CFM functions are partitioned as follows:
> - Path discovery
> - Fault detection
> - Fault verification and isolation
> - Fault notification
> - Fault recovery
>
> Interface consists of these functions:
> br_cfm_mep_create()
> br_cfm_mep_delete()
> br_cfm_mep_config_set()
> br_cfm_mep_status_get()
> br_cfm_mep_counters_get()
> br_cfm_mep_counters_clear()
> br_cfm_cc_config_set()
> br_cfm_cc_peer_mep_add()
> br_cfm_cc_peer_mep_remove()
> br_cfm_cc_rdi_set()
> br_cfm_cc_ccm_tx()
> br_cfm_cc_status_get()
> br_cfm_cc_counters_get()
> br_cfm_cc_counters_clear()
> br_cfm_cc_peer_status_get()
>
> A MEP instance is created by br_cfm_mep_create()
> -It is the Maintenance association End Point
> described in 802.1Q section 19.2.
> -It is created on a specific level (1-7) and is assuring
> that no CFM frames are passing through this MEP on lower levels.
> -It initiates and validates CFM frames on its level.
> -It can only exist on a port that is related to a bridge.
> -Attributes given cannot be changed until the instance is
> deleted.
>
> A MEP instance can be deleted by br_cfm_mep_delete().
>
> A created MEP instance has attributes that can be
> configured by br_cfm_mep_config_set().
>
> A MEP contain status and counter information that can be
> retrieved by br_cfm_mep_status_get() and
> br_cfm_mep_counters_get().
>
> A MEP counters can be cleared by br_cfm_mep_counters_clear().
>
> A MEP Continuity Check feature can be configured by
> br_cfm_cc_config_set()
> The Continuity Check Receiver state machine can be
> enabled and disabled.
> According to 802.1Q section 19.2.8
>
> A MEP can have Peer MEPs added and removed by
> br_cfm_cc_peer_mep_add() and br_cfm_cc_peer_mep_remove()
> The Continuity Check feature can maintain connectivity
> status on each added Peer MEP.
>
> A MEP can be configured to start or stop transmission of CCM frames by
> br_cfm_cc_ccm_tx()
> The CCM will be transmitted for a selected period in seconds.
> Must call this function before timeout to keep transmission alive.
>
> A MEP transmitting CCM can be configured with inserted RDI in PDU by
> br_cfm_cc_rdi_set()
>
> A MEP contain Continuity Check status and counter information
> that can be retrieved by br_cfm_cc_status_get() and
> br_cfm_cc_counters_get().
>
> A MEP Continuity Check counters can be cleared
> by br_cfm_cc_counters_clear().
>
> A MEP contain Peer MEP Continuity Check status information that
> can be retrieved by br_cfm_cc_peer_status_get().
>
> Signed-off-by: Henrik Bjoernlund <[email protected]>
> ---
> include/uapi/linux/cfm_bridge.h | 75 +++
> net/bridge/Makefile | 2 +
> net/bridge/br_cfm.c | 880 ++++++++++++++++++++++++++++++++
> net/bridge/br_private.h | 16 +
> net/bridge/br_private_cfm.h | 242 +++++++++
> 5 files changed, 1215 insertions(+)
> create mode 100644 include/uapi/linux/cfm_bridge.h
> create mode 100644 net/bridge/br_cfm.c
> create mode 100644 net/bridge/br_private_cfm.h
>

This is a large single patch, do you think it can be broken down into pieces?
I'll review it like this now, but it would be much easier if it's in smaller
logical pieces.
In general are you sure there are no holes in the structs being assigned
directly? Since you use memcmp() and such, you could end up surprised. :)

> diff --git a/include/uapi/linux/cfm_bridge.h b/include/uapi/linux/cfm_bridge.h
> new file mode 100644
> index 000000000000..389ea1e1f68e
> --- /dev/null
> +++ b/include/uapi/linux/cfm_bridge.h
> @@ -0,0 +1,75 @@
> +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
> +
> +#ifndef _UAPI_LINUX_CFM_BRIDGE_H_
> +#define _UAPI_LINUX_CFM_BRIDGE_H_
> +
> +#include <linux/types.h>
> +#include <linux/if_ether.h>
> +
> +#define ETHER_HEADER_LENGTH (6+6+4+2)
> +#define CFM_MAID_LENGTH 48
> +#define CFM_CCM_PDU_LENGTH 75
> +#define CFM_PORT_STATUS_TLV_LENGTH 4
> +#define CFM_IF_STATUS_TLV_LENGTH 4
> +#define CFM_IF_STATUS_TLV_TYPE 4
> +#define CFM_PORT_STATUS_TLV_TYPE 2
> +#define CFM_ENDE_TLV_TYPE 0
> +#define CFM_CCM_MAX_FRAME_LENGTH (ETHER_HEADER_LENGTH+\
> + CFM_CCM_PDU_LENGTH+\
> + CFM_PORT_STATUS_TLV_LENGTH+\
> + CFM_IF_STATUS_TLV_LENGTH)
> +#define CFM_FRAME_PRIO 7
> +#define CFM_CCM_OPCODE 1
> +#define CFM_CCM_TLV_OFFSET 70
> +#define CFM_CCM_PDU_MAID_OFFSET 10
> +#define CFM_CCM_PDU_MEPID_OFFSET 8
> +#define CFM_CCM_PDU_SEQNR_OFFSET 4
> +#define CFM_CCM_PDU_TLV_OFFSET 74
> +#define CFM_CCM_ITU_RESERVED_SIZE 16
> +
> +struct br_cfm_common_hdr {
> + __u8 mdlevel_version;
> + __u8 opcode;
> + __u8 flags;
> + __u8 tlv_offset;
> +};
> +
> +struct br_cfm_status_tlv {
> + __u8 type;
> + __be16 length;
> + __u8 value;
> +};
> +
> +enum br_cfm_opcodes {
> + BR_CFM_OPCODE_CCM = 0x1,
> + BR_CFM_OPCODE_LBR = 0x2,
> + BR_CFM_OPCODE_LBM = 0x3,
> + BR_CFM_OPCODE_LTR = 0x4,
> + BR_CFM_OPCODE_LTM = 0x5,
> +};
> +
> +/* MEP domain */
> +enum br_cfm_domain {
> + BR_CFM_PORT,
> + BR_CFM_VLAN,
> +};
> +
> +/* MEP direction */
> +enum br_cfm_mep_direction {
> + BR_CFM_MEP_DIRECTION_DOWN,
> + BR_CFM_MEP_DIRECTION_UP,
> +};
> +
> +/* CCM interval supported. */
> +enum br_cfm_ccm_interval {
> + BR_CFM_CCM_INTERVAL_NONE,
> + BR_CFM_CCM_INTERVAL_3_3_MS,
> + BR_CFM_CCM_INTERVAL_10_MS,
> + BR_CFM_CCM_INTERVAL_100_MS,
> + BR_CFM_CCM_INTERVAL_1_SEC,
> + BR_CFM_CCM_INTERVAL_10_SEC,
> + BR_CFM_CCM_INTERVAL_1_MIN,
> + BR_CFM_CCM_INTERVAL_10_MIN,
> +};
> +
> +#endif
> diff --git a/net/bridge/Makefile b/net/bridge/Makefile
> index ccb394236fbd..ddc0a9192348 100644
> --- a/net/bridge/Makefile
> +++ b/net/bridge/Makefile
> @@ -27,3 +27,5 @@ bridge-$(CONFIG_NET_SWITCHDEV) += br_switchdev.o
> obj-$(CONFIG_NETFILTER) += netfilter/
>
> bridge-$(CONFIG_BRIDGE_MRP) += br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o
> +
> +bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o
> diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
> new file mode 100644
> index 000000000000..e38cc3e8f262
> --- /dev/null
> +++ b/net/bridge/br_cfm.c
> @@ -0,0 +1,880 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <linux/cfm_bridge.h>
> +#include <uapi/linux/cfm_bridge.h>
> +#include "br_private_cfm.h"
> +
> +static struct br_cfm_mep *br_mep_find(struct net_bridge *br, u32 instance)
> +{
> + struct br_cfm_mep *res = NULL;
> + struct br_cfm_mep *mep;
> +
> + list_for_each_entry_rcu(mep, &br->mep_list, head,
> + lockdep_rtnl_is_held()) {
> + if (mep->instance == instance) {
> + res = mep;
> + break;
> + }
> + }
> +
> + return res;
> +}

It seems br_mep_find() is called only from functions which rely on rtnl being
held so you can just use normal list traversing functions instead of the rcu
flavor.

> +
> +static struct br_cfm_mep *br_mep_find_ifindex(struct net_bridge *br,
> + u32 ifindex)
> +{
> + struct br_cfm_mep *res = NULL;
> + struct br_cfm_mep *mep;
> +
> + list_for_each_entry_rcu(mep, &br->mep_list, head,
> + lockdep_rtnl_is_held()) {
> + if (mep->create.ifindex == ifindex) {
> + res = mep;

No need for "res", just return "mep" when you find it and NULL below.

> + break;
> + }
> + }

Then you can delete these brackets and the break.

> +
> + return res;
> +}
> +
> +static struct br_cfm_peer_mep *br_peer_mep_find(struct br_cfm_mep *mep,
> + u32 mepid)
> +{
> + struct br_cfm_peer_mep *res = NULL;
> + struct br_cfm_peer_mep *peer_mep;
> +
> + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
> + lockdep_rtnl_is_held()) {
> + if (peer_mep->mepid == mepid) {
> + res = peer_mep;

No need for "res", just return "peer_mep" when you find it and NULL below.

> + break;
> + }
> + }

Then you can delete these brackets and the break.

> +
> + return res;
> +}
> +
> +static struct net_bridge_port *br_mep_get_port(struct net_bridge *br,
> + u32 ifindex)
> +{
> + struct net_bridge_port *res = NULL;
> + struct net_bridge_port *port;
> +
> + list_for_each_entry(port, &br->port_list, list) {
> + if (port->dev->ifindex == ifindex) {
> + res = port;

No need for "res", just return "port" when you find it and NULL below.

> + break;
> + }
> + }

Then you can delete these brackets and the break. :)

> +
> + return res;
> +}
> +
> +/* Calculate the CCM interval in us. */
> +static u32 interval_to_us(enum br_cfm_ccm_interval interval)
> +{
> + switch (interval) {
> + case BR_CFM_CCM_INTERVAL_NONE:
> + return 0;
> + case BR_CFM_CCM_INTERVAL_3_3_MS:
> + return 3300;
> + case BR_CFM_CCM_INTERVAL_10_MS:
> + return 10 * 1000;
> + case BR_CFM_CCM_INTERVAL_100_MS:
> + return 100 * 1000;
> + case BR_CFM_CCM_INTERVAL_1_SEC:
> + return 1000 * 1000;
> + case BR_CFM_CCM_INTERVAL_10_SEC:
> + return 10 * 1000 * 1000;
> + case BR_CFM_CCM_INTERVAL_1_MIN:
> + return 60 * 1000 * 1000;
> + case BR_CFM_CCM_INTERVAL_10_MIN:
> + return 10 * 60 * 1000 * 1000;
> + }
> + return 0;
> +}
> +
> +/* Convert the interface interval to CCM PDU value. */
> +static u32 interval_to_pdu(enum br_cfm_ccm_interval interval)
> +{
> + switch (interval) {
> + case BR_CFM_CCM_INTERVAL_NONE:
> + return 0;
> + case BR_CFM_CCM_INTERVAL_3_3_MS:
> + return 1;
> + case BR_CFM_CCM_INTERVAL_10_MS:
> + return 2;
> + case BR_CFM_CCM_INTERVAL_100_MS:
> + return 3;
> + case BR_CFM_CCM_INTERVAL_1_SEC:
> + return 4;
> + case BR_CFM_CCM_INTERVAL_10_SEC:
> + return 5;
> + case BR_CFM_CCM_INTERVAL_1_MIN:
> + return 6;
> + case BR_CFM_CCM_INTERVAL_10_MIN:
> + return 7;
> + }
> + return 0;
> +}
> +
> +/* Convert the CCM PDU value to interval on interface. */
> +static u32 pdu_to_interval(u32 value)
> +{
> + switch (value) {
> + case 0:
> + return BR_CFM_CCM_INTERVAL_NONE;
> + case 1:
> + return BR_CFM_CCM_INTERVAL_3_3_MS;
> + case 2:
> + return BR_CFM_CCM_INTERVAL_10_MS;
> + case 3:
> + return BR_CFM_CCM_INTERVAL_100_MS;
> + case 4:
> + return BR_CFM_CCM_INTERVAL_1_SEC;
> + case 5:
> + return BR_CFM_CCM_INTERVAL_10_SEC;
> + case 6:
> + return BR_CFM_CCM_INTERVAL_1_MIN;
> + case 7:
> + return BR_CFM_CCM_INTERVAL_10_MIN;
> + }
> + return BR_CFM_CCM_INTERVAL_NONE;
> +}
> +
> +static void ccm_rx_timer_start(struct br_cfm_peer_mep *peer_mep)
> +{
> + u32 interval_us;
> +
> + interval_us = interval_to_us(peer_mep->mep->cc_config.exp_interval);
> + /* Function ccm_rx_dwork must be called with 1/4
> + * of the configured CC 'expected_interval'
> + * in order to detect CCM defect after 3.25 interval.
> + */
> + queue_delayed_work(system_wq, &peer_mep->ccm_rx_dwork,
> + usecs_to_jiffies(interval_us / 4));
> +}
> +
> +static void cc_peer_enable(struct br_cfm_peer_mep *peer_mep)
> +{
> + memset(&peer_mep->cc_status, 0, sizeof(peer_mep->cc_status));
> + peer_mep->ccm_rx_count_miss = 0;
> +
> + ccm_rx_timer_start(peer_mep);
> +}
> +
> +static void cc_peer_disable(struct br_cfm_peer_mep *peer_mep)
> +{
> + cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork);
> +}
> +
> +static struct sk_buff *ccm_frame_build(struct br_cfm_mep *mep,
> + const struct br_cfm_cc_ccm_tx_info *const tx_info)
> +
> +{
> + struct br_cfm_common_hdr *common_hdr;
> + struct net_bridge_port *b_port;
> + struct br_cfm_maid *maid;
> + u8 *itu_reserved, *e_tlv;
> + struct ethhdr *eth_hdr;
> + struct sk_buff *skb;
> + __be32 *status_tlv;
> + __be32 *snumber;
> + __be16 *mepid;
> +
> + rcu_read_lock();
> + b_port = rcu_dereference(mep->b_port);
> + if (!b_port)

You need to rcu_read_unlock() here, how was this tested?

> + return NULL;
> + skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH);
> + if (!skb)
> + return NULL;
> +
> + skb->dev = b_port->dev;
> + rcu_read_unlock();

You'll have to explain why the device will still exist past this point.
I get it, but it'd be nice to have it in the commit msg.

> + skb->protocol = htons(ETH_P_CFM);
> + skb->priority = CFM_FRAME_PRIO;
> +
> + /* Ethernet header */unnecessary comment
> + eth_hdr = skb_put(skb, sizeof(*eth_hdr));
> + ether_addr_copy(eth_hdr->h_dest, tx_info->dmac.addr);
> + ether_addr_copy(eth_hdr->h_source, mep->config.unicast_mac.addr);
> + eth_hdr->h_proto = htons(ETH_P_CFM);
> +
> + /* Common CFM Header */
> + common_hdr = skb_put(skb, sizeof(*common_hdr));
> + common_hdr->mdlevel_version = mep->config.mdlevel << 5;
> + common_hdr->opcode = CFM_CCM_OPCODE;
> + common_hdr->flags = (mep->rdi << 7) |
> + interval_to_pdu(mep->cc_config.exp_interval);
> + common_hdr->tlv_offset = CFM_CCM_TLV_OFFSET;
> +
> + /* Sequence number */
> + snumber = skb_put(skb, sizeof(*snumber));
> + if (tx_info->seq_no_update) {
> + *snumber = cpu_to_be32(mep->ccm_tx_snumber);
> + mep->ccm_tx_snumber += 1;
> + } else {
> + *snumber = 0;
> + }
> +
> + /* MEP ID */
unnecessary comment
> + mepid = skb_put(skb, sizeof(*mepid));
> + *mepid = cpu_to_be16((u16)mep->config.mepid);
> +
> + /* MA ID */
unnecessary comment
> + maid = skb_put(skb, sizeof(*maid));
> + memcpy(maid->data, mep->cc_config.exp_maid.data, sizeof(maid->data));
> +
> + /* ITU reserved (CFM_CCM_ITU_RESERVED_SIZE octets) */
> + itu_reserved = skb_put(skb, CFM_CCM_ITU_RESERVED_SIZE);
> + memset(itu_reserved, 0, CFM_CCM_ITU_RESERVED_SIZE);
> +
> + /* Generel CFM TLV format:
> + * TLV type: one byte
> + * TLV value length: two bytes
> + * TLV value: 'TLV value length' bytes
> + */
> +
> + /* Port status TLV. The value length is 1. Total of 4 bytes. */
> + if (tx_info->port_tlv) {
> + status_tlv = skb_put(skb, sizeof(*status_tlv));
> + *status_tlv = cpu_to_be32((CFM_PORT_STATUS_TLV_TYPE << 24) |
> + (1 << 8) | /* Value length */
> + (tx_info->port_tlv_value & 0xFF));
> + }
> +
> + /* Interface status TLV. The value length is 1. Total of 4 bytes. */
> + if (tx_info->if_tlv) {
> + status_tlv = skb_put(skb, sizeof(*status_tlv));
> + *status_tlv = cpu_to_be32((CFM_IF_STATUS_TLV_TYPE << 24) |
> + (1 << 8) | /* Value length */
> + (tx_info->if_tlv_value & 0xFF));
> + }
> +
> + /* End TLV */
> + e_tlv = skb_put(skb, sizeof(*e_tlv));
> + *e_tlv = CFM_ENDE_TLV_TYPE;
> +
> + return skb;
> +}
> +
> +static void ccm_frame_tx(struct sk_buff *skb)
> +{
> + skb_reset_network_header(skb);
> + dev_queue_xmit(skb);
> +}
> +
> +/* This function is called with the configured CC 'expected_interval'
> + * in order to drive CCM transmission when enabled.
> + */
> +static void ccm_tx_work_expired(struct work_struct *work)
> +{
> + struct delayed_work *del_work;
> + struct br_cfm_mep *mep;
> + struct sk_buff *skb;
> + u32 interval_us;
> +
> + del_work = to_delayed_work(work);
> + mep = container_of(del_work, struct br_cfm_mep, ccm_tx_dwork);
> +
> + if (time_before_eq(mep->ccm_tx_end, jiffies)) {
> + /* Transmission period has ended */
> + mep->cc_ccm_tx_info.period = 0;
> + return;
> + }
> +
> + skb = ccm_frame_build(mep, &mep->cc_ccm_tx_info);
> + if (skb)
> + ccm_frame_tx(skb);
> +
> + interval_us = interval_to_us(mep->cc_config.exp_interval);
> + queue_delayed_work(system_wq, &mep->ccm_tx_dwork,
> + usecs_to_jiffies(interval_us));
> +}
> +
> +/* This function is called with 1/4 of the configured CC 'expected_interval'
> + * in order to detect CCM defect after 3.25 interval.
> + */
> +static void ccm_rx_work_expired(struct work_struct *work)
> +{
> + struct br_cfm_peer_mep *peer_mep;
> + struct delayed_work *del_work;
> +
> + del_work = to_delayed_work(work);
> + peer_mep = container_of(del_work, struct br_cfm_peer_mep, ccm_rx_dwork);
> +
> + /* After 13 counts (4 * 3,25) then 3.25 intervals are expired */
> + if (peer_mep->ccm_rx_count_miss < 13) {
> + /* 3.25 intervals are NOT expired without CCM reception */
> + peer_mep->ccm_rx_count_miss++;
> +
> + /* Start timer again */
> + ccm_rx_timer_start(peer_mep);
> + } else {
> + /* 3.25 intervals are expired without CCM reception.
> + * CCM defect detected
> + */
> + peer_mep->cc_status.ccm_defect = true;
> +
> + /* Change in CCM defect status - notify */
> + }
> +}
> +
> +static u32 ccm_tlv_extract(struct sk_buff *skb, u32 index,
> + struct br_cfm_peer_mep *peer_mep)
> +{
> + __be32 *s_tlv;
> + __be32 _s_tlv;
> + u32 h_s_tlv;
> + u8 *e_tlv;
> + u8 _e_tlv;
> +
> + e_tlv = skb_header_pointer(skb, index, sizeof(_e_tlv), &_e_tlv);
> + if (!e_tlv)
> + return 0;
> +
> + /* TLV is present - get the status TLV */
> + s_tlv = skb_header_pointer(skb,
> + index,
> + sizeof(_s_tlv), &_s_tlv);
> + if (!s_tlv)
> + return 0;
> +
> + h_s_tlv = ntohl(*s_tlv);
> + if ((h_s_tlv >> 24) == CFM_IF_STATUS_TLV_TYPE) {
> + /* Interface status TLV */
> + peer_mep->cc_status.tlv_seen = true;
> + peer_mep->cc_status.if_tlv_value = (h_s_tlv & 0xFF);
> + }
> +
> + if ((h_s_tlv >> 24) == CFM_PORT_STATUS_TLV_TYPE) {
> + /* Port status TLV */
> + peer_mep->cc_status.tlv_seen = true;
> + peer_mep->cc_status.port_tlv_value = (h_s_tlv & 0xFF);
> + }
> +
> + /* The Sender ID TLV is not handled */
> + /* The Organization-Specific TLV is not handled */
> +
> + /* Return the length of this tlv.
> + * This is the length of the value field plus 3 bytes for size of type
> + * field and length field
> + */
> + return ((h_s_tlv >> 8) & 0xFFFF) + 3;
> +}
> +
> +/* note: already called with rcu_read_lock */
> +static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb)
> +{
> + u32 mdlevel, interval, size, index, max;
> + const struct br_cfm_common_hdr *hdr;
> + struct br_cfm_peer_mep *peer_mep;
> + const struct br_cfm_maid *maid;
> + struct br_cfm_common_hdr _hdr;
> + struct br_cfm_maid _maid;
> + struct br_cfm_mep *mep;
> + struct net_bridge *br;
> + __be32 *snumber;
> + __be32 _snumber;
> + __be16 *mepid;
> + __be16 _mepid;
> +
> + /* If port is disabled don't accept any frames */

Unnecessary comment, obvious from the code.

> + if (port->state == BR_STATE_DISABLED)
> + return 0;
> +
> + hdr = skb_header_pointer(skb, 0, sizeof(_hdr), &_hdr);
> + if (!hdr)
> + return 1;
> +
> + br = port->br;
> + mep = br_mep_find_ifindex(br, port->dev->ifindex);
> + if (unlikely(!mep))
> + /* No MEP on this port - must be forwarded */
> + return 0;
> +
> + mdlevel = hdr->mdlevel_version >> 5;
> + if (mdlevel > mep->config.mdlevel)
> + /* The level is above this MEP level - must be forwarded */
> + return 0;
> +
> + if ((hdr->mdlevel_version & 0x1F) != 0) {
> + /* Invalid version */
> + mep->status.version_unexp_seen = true;
> + return 1;
> + }
> +
> + if (mdlevel < mep->config.mdlevel) {
> + /* The level is below this MEP level */
> + mep->status.rx_level_low_seen = true;
> + return 1;
> + }
> +
> + if (hdr->opcode == BR_CFM_OPCODE_CCM) {
> + /* CCM PDU received. */
> + /* MA ID is after common header + sequence number + MEP ID */
> + maid = skb_header_pointer(skb,
> + CFM_CCM_PDU_MAID_OFFSET,
> + sizeof(_maid), &_maid);
> + if (!maid)
> + return 1;
> + if (memcmp(maid->data, mep->cc_config.exp_maid.data,
> + sizeof(maid->data)))
> + /* MA ID not as expected */
> + return 1;
> +
> + /* MEP ID is after common header + sequence number */
> + mepid = skb_header_pointer(skb,
> + CFM_CCM_PDU_MEPID_OFFSET,
> + sizeof(_mepid), &_mepid);
> + if (!mepid)
> + return 1;
> + peer_mep = br_peer_mep_find(mep, (u32)ntohs(*mepid));
> + if (!peer_mep)
> + return 1;
> +
> + /* Interval is in common header flags */
> + interval = hdr->flags & 0x07;
> + if (mep->cc_config.exp_interval != pdu_to_interval(interval))
> + /* Interval not as expected */
> + return 1;
> +
> + /* A valid CCM frame is received */
> + if (peer_mep->cc_status.ccm_defect) {
> + peer_mep->cc_status.ccm_defect = false;
> +
> + /* Change in CCM defect status - notify */
> +
> + /* Start CCM RX timer */
> + ccm_rx_timer_start(peer_mep);
> + }
> +
> + peer_mep->cc_status.seen = true;
> + peer_mep->ccm_rx_count_miss = 0;
> +
> + /* RDI is in common header flags */
> + peer_mep->cc_status.rdi = (hdr->flags & 0x80) ? true : false;
> +
> + /* Sequence number is after common header */
> + snumber = skb_header_pointer(skb,
> + CFM_CCM_PDU_SEQNR_OFFSET,
> + sizeof(_snumber), &_snumber);
> + if (!snumber)
> + return 1;
> + if (ntohl(*snumber) != (mep->ccm_rx_snumber + 1))
> + /* Unexpected sequence number */
> + peer_mep->cc_status.seq_unexp_seen = true;
> +
> + mep->ccm_rx_snumber = ntohl(*snumber);
> +
> + /* TLV end is after common header + sequence number + MEP ID +
> + * MA ID + ITU reserved
> + */
> + index = CFM_CCM_PDU_TLV_OFFSET;
> + max = 0;
> + do { /* Handle all TLVs */
> + size = ccm_tlv_extract(skb, index, peer_mep);
> + index += size;
> + max += 1;
> + } while (size != 0 && max < 4); /* Max four TLVs possible */
> +
> + return 1;
> + }
> +
> + mep->status.opcode_unexp_seen = true;
> +
> + return 1;
> +}
> +
> +static struct br_frame_type cfm_frame_type __read_mostly = {
> + .type = cpu_to_be16(ETH_P_CFM),
> + .func = br_cfm_frame_rx,
> +};
> +
> +/* note: already called with rtnl_lock */

Instead of these note comments you can just add ASSERT_RTNL()
in the beginning of
these functions if you want to show they expect
rtnl to be held.

> +int br_cfm_mep_create(struct net_bridge *br,
> + const u32 instance,
> + struct br_cfm_mep_create *const create,
> + struct netlink_ext_ack *extack)
> +{
> + struct net_bridge_port *p;
> + struct br_cfm_mep *mep;
> +
> + if (create->domain == BR_CFM_VLAN) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "VLAN domain not supported");
> + return -EINVAL;
> + }
> + if (create->domain != BR_CFM_PORT) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Invalid domain value");
> + return -EINVAL;
> + }
> + if (create->direction == BR_CFM_MEP_DIRECTION_UP) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Up-MEP not supported");
> + return -EINVAL;
> + }
> + if (create->direction != BR_CFM_MEP_DIRECTION_DOWN) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Invalid direction value");
> + return -EINVAL;
> + }
> + if (!br_mep_get_port(br, create->ifindex))

You can set extack here, too.

> + return -EINVAL;
> +
> + mep = br_mep_find(br, instance);
> + if (mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance already created");
> + return -EINVAL;
> + }
> +
> + /* In PORT domain only one instance can be created per port */
> + if (create->domain == BR_CFM_PORT) {
> + mep = br_mep_find_ifindex(br, create->ifindex);
> + if (mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Only one Port MEP on a port allowed");
> + return -EINVAL;
> + }
> + }
> +
> + mep = kzalloc(sizeof(*mep), GFP_KERNEL);
> + if (!mep)
> + return -ENOMEM;
> +
> + /* Save create parameters */
> + mep->create = *create;
> + mep->instance = instance;
> + p = br_mep_get_port(br, mep->create.ifindex);

Please do this only once in the beginning and use the local variable here.

> + rcu_assign_pointer(mep->b_port, p);

Can b_port be already assigned here?

> +
> + INIT_LIST_HEAD(&mep->peer_mep_list);
> + INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired);
> +
> + if (list_empty(&br->mep_list))
> + br_add_frame(br, &cfm_frame_type);
> +
> + /* Add instance to list */
unnecessary comment
> + list_add_tail_rcu(&mep->head, &br->mep_list);
> +
> + return 0;
> +}
> +
> +static void mep_delete_implementation(struct net_bridge *br,
> + struct br_cfm_mep *mep)
> +{
> + struct br_cfm_peer_mep *peer_mep;
> +
> + /* Empty and free peer MEP list */
> + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
> + lockdep_rtnl_is_held()) {

You can use normal list traversal
> + cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork);
> + list_del_rcu(&peer_mep->head);
> + kfree_rcu(peer_mep, rcu);
> + }
> +
> + /* Stop transmitting */

unnecessary comment

> + cancel_delayed_work_sync(&mep->ccm_tx_dwork);
> +
> + /* Free the MEP instance */

unnecessary comment, code is pretty clear

> + rcu_assign_pointer(mep->b_port, NULL);

RCU_INIT_POINTER

> + list_del_rcu(&mep->head);
> + kfree_rcu(mep, rcu);
> +
> + if (list_empty(&br->mep_list))
> + br_del_frame(br, &cfm_frame_type);
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_mep_delete(struct net_bridge *br,
> + const u32 instance,
> + struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_mep *mep;
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");
> + return -EINVAL;
> + }
> +
> + mep_delete_implementation(br, mep);
> +
> + return 0;
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_mep_config_set(struct net_bridge *br,
> + const u32 instance,
> + const struct br_cfm_mep_config *const config,
> + struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_mep *mep;
> +
> + if (config->mdlevel > 7) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "mdlevel is invalid");
> + return -EINVAL;
> + }
> + if (config->mepid > 0x1FFF) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "mepid is invalid");

MEP-ID ?

> + return -EINVAL;
> + }
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");
> + return -EINVAL;
> + }
> +
> + /* Save config parameters */

unnecessary comment

> + mep->config = *config;
> +
> + return 0;
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_mep_counters_clear(struct net_bridge *br,
> + const u32 instance,
> + struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_mep *mep;
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");
> + return -EINVAL;
> + }
> +
> + memset(&mep->counters, 0, sizeof(mep->counters));
> +
> + return 0;
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_cc_config_set(struct net_bridge *br,
> + const u32 instance,
> + const struct br_cfm_cc_config *const config,
> + struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_peer_mep *peer_mep;
> + struct br_cfm_mep *mep;
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");

"does not exist" somehow seems more fitting, but up to you. :)

> + return -EINVAL;
> + }
> +
> + /* Check for no change in configuration */
> + if (memcmp(config, &mep->cc_config, sizeof(*config)) == 0)
> + return 0;
> +
> + if (config->enable && !mep->cc_config.enable)
> + /* CC is enabled */
> + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
> + lockdep_rtnl_is_held())
> + cc_peer_enable(peer_mep);

Use normal list traversal when rtnl is being held.

> +
> + if (!config->enable && mep->cc_config.enable)
> + /* CC is disabled */
> + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
> + lockdep_rtnl_is_held())

Same here.

> + cc_peer_disable(peer_mep);
> +
> + /* Save new cc_config parameters */

unnecessary comment

> + mep->cc_config = *config;
> +
> + return 0;
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
> + u32 mepid,
> + struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_peer_mep *peer_mep;
> + struct br_cfm_mep *mep;
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");
> + return -EINVAL;
> + }
> + if (mepid > 0x1FFF) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "mepid is invalid");

Shouldn't this be MEP-ID as it's written out in other places?

> + return -EINVAL;
> + }
> +
> + peer_mep = br_peer_mep_find(mep, mepid);
> + if (peer_mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Peer MEP-ID already added");

perhaps "already exists" ?

> + return -EINVAL;

EEXIST ?

> + }
> +
> + peer_mep = kzalloc(sizeof(*peer_mep), GFP_KERNEL);
> + if (!peer_mep)
> + return -ENOMEM;
> +
> + peer_mep->mepid = mepid;
> + peer_mep->mep = mep;
> + INIT_DELAYED_WORK(&peer_mep->ccm_rx_dwork, ccm_rx_work_expired);
> +
> + if (mep->cc_config.enable)
> + /* CC is enabled */

Unnecessary comment, the function name is self-descriptive.

> + cc_peer_enable(peer_mep);
> +
> + /* Add Peer MEP to list */

This comment is unnecessary, the code is seems pretty clear.

> + list_add_tail_rcu(&peer_mep->head, &mep->peer_mep_list);
> +
> + return 0;
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
> + u32 mepid,
> + struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_peer_mep *peer_mep;
> + struct br_cfm_mep *mep;
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");
> + return -EINVAL;
> + }
> +
> + peer_mep = br_peer_mep_find(mep, mepid);
> + if (!peer_mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Peer MEP-ID already deleted");

Why already deleted ? Maybe it didn't exist at all ? :) "does not exist" sounds
more appropriate.

> + return -EINVAL;

in that spirit ENOENT here ?

> + }
> +
> + cc_peer_disable(peer_mep);
> +
> + list_del_rcu(&peer_mep->head);
> + kfree_rcu(peer_mep, rcu);
> +
> + return 0;
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
> + const bool rdi, struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_mep *mep;
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");
> + return -EINVAL;
> + }
> +
> + /* Save rdi parameters */

unnecessary comment

> + mep->rdi = rdi;
> +
> + return 0;
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
> + const struct br_cfm_cc_ccm_tx_info *const tx_info,
> + struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_mep *mep;
> + struct sk_buff *skb;
> + u32 interval_us;
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");
> + return -EINVAL;
> + }

All of these seem to start with finding the instance and checking if it exists.
Perhaps this can be factored out in the netlink handling code?

> +
> + if (memcmp(tx_info, &mep->cc_ccm_tx_info, sizeof(*tx_info)) == 0) {
> + /* No change in tx_info. */
> + if (mep->cc_ccm_tx_info.period == 0)
> + /* Transmission is not enabled - just return */
> + return 0;
> +
> + /* Transmission is ongoing, the end time is recalculated */
> + mep->ccm_tx_end = jiffies +
> + usecs_to_jiffies(tx_info->period * 1000000);
> + return 0;
> + }
> +
> + if (tx_info->period == 0 && mep->cc_ccm_tx_info.period == 0)
> + /* Some change in info and transmission is not ongoing */
> + goto save;
> +
> + if (tx_info->period != 0 && mep->cc_ccm_tx_info.period != 0) {
> + /* Some change in info and transmission is ongoing */
> + /* The end time is recalculated */

Multi-line comments format is:
/* comment1
* comment2
*/

> + mep->ccm_tx_end = jiffies +
> + usecs_to_jiffies(tx_info->period * 1000000);
> +
> + goto save;
> + }
> +
> + if (tx_info->period == 0 && mep->cc_ccm_tx_info.period != 0) {
> + /* CCM transmission stop */

This comment seems unnecessary, the code is pretty clear.

> + cancel_delayed_work_sync(&mep->ccm_tx_dwork);
> + goto save;
> + }
> +
> + /* Start delayed work to transmit CCM frames */

This comment seems unnecessary, the code is pretty clear.

> + mep->ccm_tx_end = jiffies + usecs_to_jiffies(tx_info->period * 1000000);
> + interval_us = interval_to_us(mep->cc_config.exp_interval);
> + queue_delayed_work(system_wq, &mep->ccm_tx_dwork,
> + usecs_to_jiffies(interval_us));
> +
> + /* Send first CCM frame now */
> + mep->ccm_tx_snumber = 0;
> + skb = ccm_frame_build(mep, tx_info);
> + if (skb)
> + ccm_frame_tx(skb);
> +
> +save:
> + /* Save tx_info parameters */
> + mep->cc_ccm_tx_info = *tx_info;
> +
> + return 0;
> +}
> +
> +/* note: already called with rtnl_lock */
> +int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
> + struct netlink_ext_ack *extack)
> +{
> + struct br_cfm_mep *mep;
> +
> + mep = br_mep_find(br, instance);
> + if (!mep) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "MEP instance not created");
> + return -EINVAL;
> + }
> +
> + memset(&mep->cc_counters, 0, sizeof(mep->cc_counters));
> +
> + return 0;
> +}
> +
> +bool br_cfm_created(struct net_bridge *br)
> +{
> + return !list_empty(&br->mep_list);
> +}
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index 6294a3e51a33..385a6a6aa17e 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -1363,6 +1363,22 @@ static inline int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br)
>
> #endif
>
> +/* br_cfm.c */
> +#if IS_ENABLED(CONFIG_BRIDGE_CFM)
> +int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
> +bool br_cfm_created(struct net_bridge *br);
> +#else
> +static inline int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static inline bool br_cfm_created(struct net_bridge *br)
> +{
> + return false;
> +}
> +#endif
> +
> /* br_netlink.c */
> extern struct rtnl_link_ops br_link_ops;
> int br_netlink_init(void);
> diff --git a/net/bridge/br_private_cfm.h b/net/bridge/br_private_cfm.h
> new file mode 100644
> index 000000000000..379334a42667
> --- /dev/null
> +++ b/net/bridge/br_private_cfm.h
> @@ -0,0 +1,242 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +#ifndef _BR_PRIVATE_CFM_H_
> +#define _BR_PRIVATE_CFM_H_
> +
> +#include "br_private.h"
> +#include <uapi/linux/cfm_bridge.h>
> +
> +/* MEP create parameters */
> +struct br_cfm_mep_create {
> + enum br_cfm_domain domain; /* Domain for this MEP */
> + enum br_cfm_mep_direction direction; /* Up or Down MEP direction */
> + u32 ifindex; /* Residence port */
> + u16 vid; /* Classified VID in VLAN domain */
> +};
> +
> +/* Create/Delete a MEP. */

This is clear.

> +int br_cfm_mep_create(struct net_bridge *br,
> + const u32 instance,
> + struct br_cfm_mep_create *const create,
> + struct netlink_ext_ack *extack);
> +int br_cfm_mep_delete(struct net_bridge *br,
> + const u32 instance,
> + struct netlink_ext_ack *extack);
> +
> +/* MEP configuration parameters */
> +struct br_cfm_mep_config {
> + u32 mdlevel;
> + u32 mepid; /* MEPID for this MEP */
> + struct mac_addr unicast_mac; /* The MEP unicast MAC */
> + /* Added tag VID in case of tagged PORT domain. Untagged if zero. */
> + u16 vid;
> +};
> +
> +/* Set general configuration of MEP */

The function name is self-descriptive.

> +int br_cfm_mep_config_set(struct net_bridge *br,
> + const u32 instance,
> + const struct br_cfm_mep_config *const config,
> + struct netlink_ext_ack *extack);
> +
> +/* MEP status. */
> +struct br_cfm_mep_status {
> + /* Indications that an OAM PDU has been seen. */
> + /* Are cleared during call to br_cfm_status_get(). */
> + bool opcode_unexp_seen; /* RX of OAM PDU with unexpected opcode */
> + bool dmac_unexp_seen; /* RX of OAM Frame with unexpected DMAC */
> + bool tx_level_low_seen; /* TX of OAM PDU with level low blocked */
> + bool version_unexp_seen; /* RX of OAM PDU with unexpected version */
> + bool rx_level_low_seen; /* Rx of OAM PDU with level low */
> +};
> +
> +/* MEP status get. */
unnecessary comment
> +int br_cfm_mep_status_get(const u32 instance, struct br_cfm_mep_status *const status);
> +
> +/* MEP counters */

unnecessary comment

> +struct br_cfm_mep_counters {
> + /* All the following counters are cleared during call to */
> + /* br_cfm_counters_clear(). */
> +
> + /* OAM PDU Rx and Tx counters. */
> + u64 rx_counter;
> + u64 tx_counter;
> +
> + /* Rx/Tx PDUs that are discarded due to filtering */
> + u64 rx_discard_counter; /* Check of MEL or DMAC or Version or CCM */
> + u64 tx_discard_counter; /* Check of MEL */
> +};
> +
> +/* Get MEP counters. */
unnecessary comment
> +int br_cfm_mep_counters_get(const u32 instance, struct br_cfm_mep_counters *counters);
> +
> +/* MEP counter clear. */
unnecessary comment
> +int br_cfm_mep_counters_clear(struct net_bridge *br,
> + const u32 instance,
> + struct netlink_ext_ack *extack);
> +
> +struct br_cfm_maid {
> + u8 data[CFM_MAID_LENGTH];
> +};
> +
> +struct br_cfm_cc_config {
> + /* Expected received CCM PDU MAID. */
> + struct br_cfm_maid exp_maid;
> +
> + /* Expected received CCM PDU interval. */
> + /* Transmitting CCM PDU interval when CCM tx is enabled. */
> + enum br_cfm_ccm_interval exp_interval;
> +
> + /* Expected received CCM PDU Priority */
> + u8 exp_priority;
> +
> + bool enable; /* Enable/disable CCM PDU handling */
> +};
> +
> +/* Set CC configuration of a MEP */
unnecessary comment
> +int br_cfm_cc_config_set(struct net_bridge *br,
> + const u32 instance,
> + const struct br_cfm_cc_config *const config,
> + struct netlink_ext_ack *extack);
> +
> +/* Set CC Peer MEP ID add/remove */
unnecessary comment
> +int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
> + u32 peer_mep_id,
> + struct netlink_ext_ack *extack);
> +int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
> + u32 peer_mep_id,
> + struct netlink_ext_ack *extack);
> +
> +/* Transmitted CCM Remote Defect Indication status set.
> + * This RDI is inserted in transmitted CCM PDUs if CCM transmission is enabled.
> + * See br_cfm_cc_ccm_tx() with interval != BR_CFM_CCM_INTERVAL_NONE
> + */
> +int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
> + const bool rdi, struct netlink_ext_ack *extack);
> +
> +/* OAM PDU Tx information */
> +struct br_cfm_cc_ccm_tx_info {
> + struct mac_addr dmac;
> + u8 priority;
> + bool dei;
> + /* The CCM will be transmitted for this period in seconds.
> + * Call br_cfm_cc_ccm_tx before timeout to keep transmission alive.
> + * When period is zero any ongoing transmission will be stopped.
> + */
> + u32 period;
> +
> + bool seq_no_update; /* Update Tx CCM sequence number */
> + bool if_tlv; /* Insert Interface Status TLV */
> + u8 if_tlv_value; /* Interface Status TLV value */
> + bool port_tlv; /* Insert Port Status TLV */
> + u8 port_tlv_value; /* Port Status TLV value */
> + /* Sender ID TLV ??
> + * Organization-Specific TLV ??
> + */
> +};
> +
> +/* Transmit CCM PDU */
unnecessary comment
> +int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
> + const struct br_cfm_cc_ccm_tx_info *const tx_info,
> + struct netlink_ext_ack *extack);
> +
> +struct br_cfm_cc_status {
> + /* (interval == 0) for last received CCM PDU */
> + bool zero_interval;
> +
> + /* Unexpected MD Level received. Cleared after 3.5 interval */
> + bool mdlevel_unexp;
> +
> + /* Unexpected MAID received. Cleared after 3.5 interval */
> + bool maid_unexp;
> +
> + /* Unexpected MEPID received. Cleared after 3.5 interval */
> + bool mepid_unexp;
> +};
> +
> +/* Get CC status. */
unnecessary comment
> +int br_cfm_cc_status_get(const u32 instance, struct br_cfm_cc_status *const status);
> +
> +struct br_cfm_cc_counters {
> + /* CCM PDU RX and TX counters
> + * Are cleared during call to br_cfm_cc_counters_clear()
> + */
> + u64 rx_valid_counter;
> + u64 rx_invalid_counter;
> + u64 rx_oo_counter; /* Out of Order sequence numbers counter */
> + u64 tx_counter;
> +};
> +
> +/* Get CC counters. */
unnecessary comment
> +int br_cfm_cc_counters_get(const u32 instance, struct br_cfm_cc_counters *counters);
> +
> +/* CC counter clear. */
unnecessary comment
> +int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
> + struct netlink_ext_ack *extack);
> +
> +struct br_cfm_cc_peer_status {
> + struct mac_addr unicast_mac; /* The Peer MEP unicast MAC */
> +
> + /* Unexpected Interval. Cleared after 3.5 interval */
> + bool interval_unexp;
> +
> + /* Unexpected Priority received. Cleared after 3.5 interval */
> + bool priority_unexp;
> +
> + /* This CCM related status is based on the latest received CCM PDU. */
> + u32 rx_ifindex; /* The ingress port */
> + u8 port_tlv_value; /* Port Status TLV value */
> + u8 if_tlv_value; /* Interface Status TLV value */
> +
> + /* CCM has not been received for 3.25 intervals */
> + bool ccm_defect;
> +
> + /* (RDI == 1) for last received CCM PDU */
> + bool rdi;
> +
> + /* Indications that a CCM PDU has been seen.
> + * Are cleared during call to br_cfm_cc_status_get()
> + */
> + bool seen; /* CCM PDU received */
> + bool tlv_seen; /* CCM PDU with TLV received */
> + /* CCM PDU with unexpected sequence number received */
> + bool seq_unexp_seen;
> +};
> +
> +/* Get CC peer MEP status. */
unnecessary comment
> +int br_cfm_cc_peer_status_get(const u32 instance, const u32 mepid,
> + struct br_cfm_cc_peer_status *const status);
> +
> +struct br_cfm_mep {
> + /* list header of MEP instances */
> + struct list_head head;
> + u32 instance;
> + struct br_cfm_mep_create create;
> + struct br_cfm_mep_config config;
> + struct br_cfm_cc_config cc_config;
> + struct br_cfm_cc_ccm_tx_info cc_ccm_tx_info;
> + struct br_cfm_mep_counters counters;
> + struct br_cfm_cc_counters cc_counters;
> + /* List of multiple peer MEPs */
> + struct list_head peer_mep_list;
> + struct rcu_head rcu;

Please move the rcu at the end, it's used only on deletion.

> + struct net_bridge_port __rcu *b_port;
> + unsigned long ccm_tx_end;
> + struct delayed_work ccm_tx_dwork;
> + struct br_cfm_cc_status cc_status;
> + u32 ccm_tx_snumber;
> + u32 ccm_rx_snumber;
> + struct br_cfm_mep_status status;
> + bool rdi;
> +};
> +
> +struct br_cfm_peer_mep {
> + struct list_head head;
> + struct br_cfm_mep *mep;
> + struct rcu_head rcu;
> + struct delayed_work ccm_rx_dwork;
> + u32 mepid;
> + struct br_cfm_cc_peer_status cc_status;
> + u32 ccm_rx_count_miss;
> +};
> +
> +#endif /* _BR_PRIVATE_CFM_H_ */

2020-09-08 17:17:36

by Nikolay Aleksandrov

[permalink] [raw]
Subject: Re: [PATCH RFC 3/7] bridge: uapi: cfm: Added EtherType used by the CFM protocol.

On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> This EtherType is used by all CFM protocal frames transmitted
> according to 802.1Q section 12.14.
>
> Signed-off-by: Henrik Bjoernlund <[email protected]>
> ---
> include/uapi/linux/if_ether.h | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
> index d6de2b167448..a0b637911d3c 100644
> --- a/include/uapi/linux/if_ether.h
> +++ b/include/uapi/linux/if_ether.h
> @@ -99,6 +99,7 @@
> #define ETH_P_1588 0x88F7 /* IEEE 1588 Timesync */
> #define ETH_P_NCSI 0x88F8 /* NCSI protocol */
> #define ETH_P_PRP 0x88FB /* IEC 62439-3 PRP/HSRv0 */
> +#define ETH_P_CFM 0x8902 /* Connectivity Fault Management */
> #define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */
> #define ETH_P_IBOE 0x8915 /* Infiniband over Ethernet */
> #define ETH_P_TDLS 0x890D /* TDLS */

Acked-by: Nikolay Aleksandrov <[email protected]>

2020-09-08 19:23:20

by Nikolay Aleksandrov

[permalink] [raw]
Subject: Re: [PATCH RFC 7/7] bridge: cfm: Bridge port remove.

On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> This is addition of CFM functionality to delete MEP instances
> on a port that is removed from the bridge.
> A MEP can only exist on a port that is related to a bridge.
>
> Signed-off-by: Henrik Bjoernlund <[email protected]>
> ---
> net/bridge/br_cfm.c | 13 +++++++++++++
> net/bridge/br_if.c | 1 +
> net/bridge/br_private.h | 6 ++++++
> 3 files changed, 20 insertions(+)
>
> diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
> index b7fed2c1d8ec..c724ce020ce3 100644
> --- a/net/bridge/br_cfm.c
> +++ b/net/bridge/br_cfm.c
> @@ -921,3 +921,16 @@ bool br_cfm_created(struct net_bridge *br)
> {
> return !list_empty(&br->mep_list);
> }
> +
> +/* Deletes the CFM instances on a specific bridge port
> + * note: called under rtnl_lock
> + */
> +void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *port)
> +{
> + struct br_cfm_mep *mep;
> +
> + list_for_each_entry_rcu(mep, &br->mep_list, head,
> + lockdep_rtnl_is_held())

Use standard/non-rcu list traversing, rtnl is already held.

> + if (mep->create.ifindex == port->dev->ifindex)
> + mep_delete_implementation(br, mep);
> +}
> diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c
> index a0e9a7937412..f7d2f472ae24 100644
> --- a/net/bridge/br_if.c
> +++ b/net/bridge/br_if.c
> @@ -334,6 +334,7 @@ static void del_nbp(struct net_bridge_port *p)
> spin_unlock_bh(&br->lock);
>
> br_mrp_port_del(br, p);
> + br_cfm_port_del(br, p);
>
> br_ifinfo_notify(RTM_DELLINK, NULL, p);
>
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index 53bcbdd21f34..5617255f0c0c 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -1369,6 +1369,7 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
> int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
> bool br_cfm_created(struct net_bridge *br);
> +void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *p);
> int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
> int br_cfm_status_fill_info(struct sk_buff *skb,
> struct net_bridge *br,
> @@ -1393,6 +1394,11 @@ static inline bool br_cfm_created(struct net_bridge *br)
> return false;
> }
>
> +static inline void br_cfm_port_del(struct net_bridge *br,
> + struct net_bridge_port *p)
> +{
> +}
> +
> static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
> {
> return -EOPNOTSUPP;

2020-09-08 19:27:39

by Nikolay Aleksandrov

[permalink] [raw]
Subject: Re: [PATCH RFC 2/7] bridge: cfm: Add BRIDGE_CFM to Kconfig.

On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> This makes it possible to include or exclude the CFM
> protocol according to 802.1Q section 12.14.
>
> Signed-off-by: Henrik Bjoernlund <[email protected]>
> ---
> net/bridge/Kconfig | 11 +++++++++++
> net/bridge/br_device.c | 3 +++
> net/bridge/br_private.h | 3 +++
> 3 files changed, 17 insertions(+)
>
> diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig
> index 80879196560c..3c8ded7d3e84 100644
> --- a/net/bridge/Kconfig
> +++ b/net/bridge/Kconfig
> @@ -73,3 +73,14 @@ config BRIDGE_MRP
> Say N to exclude this support and reduce the binary size.
>
> If unsure, say N.
> +
> +config BRIDGE_CFM
> + bool "CFM protocol"
> + depends on BRIDGE
> + help
> + If you say Y here, then the Ethernet bridge will be able to run CFM
> + protocol according to 802.1Q section 12.14
> +
> + Say N to exclude this support and reduce the binary size.
> +
> + If unsure, say N.
> diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> index a9232db03108..d12f5626a4b1 100644
> --- a/net/bridge/br_device.c
> +++ b/net/bridge/br_device.c
> @@ -476,6 +476,9 @@ void br_dev_setup(struct net_device *dev)
> INIT_LIST_HEAD(&br->ftype_list);
> #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> INIT_LIST_HEAD(&br->mrp_list);
> +#endif
> +#if IS_ENABLED(CONFIG_BRIDGE_CFM)
> + INIT_LIST_HEAD(&br->mep_list);
> #endif
> spin_lock_init(&br->hash_lock);
>
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index e67c6d9e8bea..6294a3e51a33 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -445,6 +445,9 @@ struct net_bridge {
> #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> struct list_head mrp_list;
> #endif
> +#if IS_ENABLED(CONFIG_BRIDGE_CFM)
> + struct list_head mep_list;
> +#endif
> };
>
> struct br_input_skb_cb {

Looks good, perhaps also can use hlist to reduce the head size in net_bridge.

2020-09-15 08:29:53

by Henrik Bjoernlund

[permalink] [raw]
Subject: Re: [PATCH RFC 1/7] net: bridge: extend the process of special frames

Thanks for your review. I will update in the next version as suggested.

Regards
Henrik


The 09/08/2020 12:12, Nikolay Aleksandrov wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> > This patch extends the processing of frames in the bridge. Currently MRP
> > frames needs special processing and the current implementation doesn't
> > allow a nice way to process different frame types. Therefore try to
> > improve this by adding a list that contains frame types that need
> > special processing. This list is iterated for each input frame and if
> > there is a match based on frame type then these functions will be called
> > and decide what to do with the frame. It can process the frame then the
> > bridge doesn't need to do anything or don't process so then the bridge
> > will do normal forwarding.
> >
> > Signed-off-by: Henrik Bjoernlund <[email protected]>
> > ---
> > net/bridge/br_device.c | 1 +
> > net/bridge/br_input.c | 31 ++++++++++++++++++++++++++++++-
> > net/bridge/br_mrp.c | 19 +++++++++++++++----
> > net/bridge/br_private.h | 18 ++++++++++++------
> > 4 files changed, 58 insertions(+), 11 deletions(-)
> >
>
> Hi,
> First I must say I do like this approach, thanks for generalizing it.
> You can switch to a hlist so that there's just 1 pointer in the head.
> I don't think you need list when you're walking only in one direction.
> A few more minor comments below.
>
> > diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> > index 9a2fb4aa1a10..a9232db03108 100644
> > --- a/net/bridge/br_device.c
> > +++ b/net/bridge/br_device.c
> > @@ -473,6 +473,7 @@ void br_dev_setup(struct net_device *dev)
> > spin_lock_init(&br->lock);
> > INIT_LIST_HEAD(&br->port_list);
> > INIT_HLIST_HEAD(&br->fdb_list);
> > + INIT_LIST_HEAD(&br->ftype_list);
> > #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> > INIT_LIST_HEAD(&br->mrp_list);
> > #endif
> > diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> > index 59a318b9f646..0f475b21094c 100644
> > --- a/net/bridge/br_input.c
> > +++ b/net/bridge/br_input.c
> > @@ -254,6 +254,21 @@ static int nf_hook_bridge_pre(struct sk_buff *skb, struct sk_buff **pskb)
> > return RX_HANDLER_CONSUMED;
> > }
> >
> > +/* Return 0 if the frame was not processed otherwise 1
> > + * note: already called with rcu_read_lock
> > + */
> > +static int br_process_frame_type(struct net_bridge_port *p,
> > + struct sk_buff *skb)
> > +{
> > + struct br_frame_type *tmp;
> > +
> > + list_for_each_entry_rcu(tmp, &p->br->ftype_list, list) {
> > + if (unlikely(tmp->type == skb->protocol))
> > + return tmp->func(p, skb);
> > + }
>
> Nit: you can drop the {}.
>
> > + return 0;
> > +}
> > +
> > /*
> > * Return NULL if skb is handled
> > * note: already called with rcu_read_lock
> > @@ -343,7 +358,7 @@ static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
> > }
> > }
> >
> > - if (unlikely(br_mrp_process(p, skb)))
> > + if (unlikely(br_process_frame_type(p, skb)))
> > return RX_HANDLER_PASS;
> >
> > forward:
> > @@ -380,3 +395,17 @@ rx_handler_func_t *br_get_rx_handler(const struct net_device *dev)
> >
> > return br_handle_frame;
> > }
> > +
> > +void br_add_frame(struct net_bridge *br, struct br_frame_type *ft)
> > +{
> > + list_add_rcu(&ft->list, &br->ftype_list);
> > +}
> > +
> > +void br_del_frame(struct net_bridge *br, struct br_frame_type *ft)
> > +{
> > + struct br_frame_type *tmp;
> > +
> > + list_for_each_entry(tmp, &br->ftype_list, list)
> > + if (ft == tmp)
> > + list_del_rcu(&ft->list);
> > +}
> > diff --git a/net/bridge/br_mrp.c b/net/bridge/br_mrp.c
> > index b36689e6e7cb..0428e1785041 100644
> > --- a/net/bridge/br_mrp.c
> > +++ b/net/bridge/br_mrp.c
> > @@ -6,6 +6,13 @@
> > static const u8 mrp_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x1 };
> > static const u8 mrp_in_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x3 };
> >
> > +static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb);
> > +
> > +static struct br_frame_type mrp_frame_type __read_mostly = {
> > + .type = cpu_to_be16(ETH_P_MRP),
> > + .func = br_mrp_process,
> > +};
> > +
> > static bool br_mrp_is_ring_port(struct net_bridge_port *p_port,
> > struct net_bridge_port *s_port,
> > struct net_bridge_port *port)
> > @@ -445,6 +452,9 @@ static void br_mrp_del_impl(struct net_bridge *br, struct br_mrp *mrp)
> >
> > list_del_rcu(&mrp->list);
> > kfree_rcu(mrp, rcu);
> > +
> > + if (list_empty(&br->mrp_list))
> > + br_del_frame(br, &mrp_frame_type);
> > }
> >
> > /* Adds a new MRP instance.
> > @@ -493,6 +503,9 @@ int br_mrp_add(struct net_bridge *br, struct br_mrp_instance *instance)
> > spin_unlock_bh(&br->lock);
> > rcu_assign_pointer(mrp->s_port, p);
> >
> > + if (list_empty(&br->mrp_list))
> > + br_add_frame(br, &mrp_frame_type);
> > +
> > INIT_DELAYED_WORK(&mrp->test_work, br_mrp_test_work_expired);
> > INIT_DELAYED_WORK(&mrp->in_test_work, br_mrp_in_test_work_expired);
> > list_add_tail_rcu(&mrp->list, &br->mrp_list);
> > @@ -1172,15 +1185,13 @@ static int br_mrp_rcv(struct net_bridge_port *p,
> > * normal forwarding.
> > * note: already called with rcu_read_lock
> > */
> > -int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
> > +static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
> > {
> > /* If there is no MRP instance do normal forwarding */
> > if (likely(!(p->flags & BR_MRP_AWARE)))
> > goto out;
> >
> > - if (unlikely(skb->protocol == htons(ETH_P_MRP)))
> > - return br_mrp_rcv(p, skb, p->dev);
> > -
> > + return br_mrp_rcv(p, skb, p->dev);
> > out:
> > return 0;
> > }
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index baa1500f384f..e67c6d9e8bea 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -89,6 +89,13 @@ struct bridge_mcast_stats {
> > };
> > #endif
> >
> > +struct br_frame_type {
> > + __be16 type;
> > + int (*func)(struct net_bridge_port *port,
> > + struct sk_buff *skb);
>
> Perhaps frame_handler or something similar would be a better name for the
> callback ?
>
> > + struct list_head list;
> > +};
> > +
> > struct br_vlan_stats {
> > u64 rx_bytes;
> > u64 rx_packets;
> > @@ -433,6 +440,8 @@ struct net_bridge {
> > #endif
> > struct hlist_head fdb_list;
> >
> > + struct list_head ftype_list;
>
> Could you please expand this to frame_type_list ?
>
> > +
> > #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> > struct list_head mrp_list;
> > #endif
> > @@ -708,6 +717,9 @@ int nbp_backup_change(struct net_bridge_port *p, struct net_device *backup_dev);
> > int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb);
> > rx_handler_func_t *br_get_rx_handler(const struct net_device *dev);
> >
> > +void br_add_frame(struct net_bridge *br, struct br_frame_type *ft);
> > +void br_del_frame(struct net_bridge *br, struct br_frame_type *ft);
> > +
> > static inline bool br_rx_handler_check_rcu(const struct net_device *dev)
> > {
> > return rcu_dereference(dev->rx_handler) == br_get_rx_handler(dev);
> > @@ -1320,7 +1332,6 @@ extern int (*br_fdb_test_addr_hook)(struct net_device *dev, unsigned char *addr)
> > #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> > int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p,
> > struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
> > -int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb);
> > bool br_mrp_enabled(struct net_bridge *br);
> > void br_mrp_port_del(struct net_bridge *br, struct net_bridge_port *p);
> > int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br);
> > @@ -1332,11 +1343,6 @@ static inline int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p,
> > return -EOPNOTSUPP;
> > }
> >
> > -static inline int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb)
> > -{
> > - return 0;
> > -}
> > -
> > static inline bool br_mrp_enabled(struct net_bridge *br)
> > {
> > return false;
>

--
/Henrik

2020-09-15 08:30:38

by Henrik Bjoernlund

[permalink] [raw]
Subject: Re: [PATCH RFC 2/7] bridge: cfm: Add BRIDGE_CFM to Kconfig.

Thanks for your review. I will update in the next version as suggested.

Regards
Henrik


The 09/08/2020 12:18, Nikolay Aleksandrov wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> > This makes it possible to include or exclude the CFM
> > protocol according to 802.1Q section 12.14.
> >
> > Signed-off-by: Henrik Bjoernlund <[email protected]>
> > ---
> > net/bridge/Kconfig | 11 +++++++++++
> > net/bridge/br_device.c | 3 +++
> > net/bridge/br_private.h | 3 +++
> > 3 files changed, 17 insertions(+)
> >
> > diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig
> > index 80879196560c..3c8ded7d3e84 100644
> > --- a/net/bridge/Kconfig
> > +++ b/net/bridge/Kconfig
> > @@ -73,3 +73,14 @@ config BRIDGE_MRP
> > Say N to exclude this support and reduce the binary size.
> >
> > If unsure, say N.
> > +
> > +config BRIDGE_CFM
> > + bool "CFM protocol"
> > + depends on BRIDGE
> > + help
> > + If you say Y here, then the Ethernet bridge will be able to run CFM
> > + protocol according to 802.1Q section 12.14
> > +
> > + Say N to exclude this support and reduce the binary size.
> > +
> > + If unsure, say N.
> > diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> > index a9232db03108..d12f5626a4b1 100644
> > --- a/net/bridge/br_device.c
> > +++ b/net/bridge/br_device.c
> > @@ -476,6 +476,9 @@ void br_dev_setup(struct net_device *dev)
> > INIT_LIST_HEAD(&br->ftype_list);
> > #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> > INIT_LIST_HEAD(&br->mrp_list);
> > +#endif
> > +#if IS_ENABLED(CONFIG_BRIDGE_CFM)
> > + INIT_LIST_HEAD(&br->mep_list);
> > #endif
> > spin_lock_init(&br->hash_lock);
> >
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index e67c6d9e8bea..6294a3e51a33 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -445,6 +445,9 @@ struct net_bridge {
> > #if IS_ENABLED(CONFIG_BRIDGE_MRP)
> > struct list_head mrp_list;
> > #endif
> > +#if IS_ENABLED(CONFIG_BRIDGE_CFM)
> > + struct list_head mep_list;
> > +#endif
> > };
> >
> > struct br_input_skb_cb {
>
> Looks good, perhaps also can use hlist to reduce the head size in net_bridge.
>

--
/Henrik

2020-09-15 08:55:35

by Henrik Bjoernlund

[permalink] [raw]
Subject: Re: [PATCH RFC 4/7] bridge: cfm: Kernel space implementation of CFM.

Thanks for the review. Comments below.

The 09/04/2020 08:02, Jakub Kicinski wrote:
>
> On Fri, 4 Sep 2020 09:15:24 +0000 Henrik Bjoernlund wrote:
> > + rcu_read_lock();
> > + b_port = rcu_dereference(mep->b_port);
> > + if (!b_port)
> > + return NULL;
> > + skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH);
> > + if (!skb)
> > + return NULL;
>
> net/bridge/br_cfm.c:171:23: warning: context imbalance in 'ccm_frame_build' - different lock contexts for basic block

I will assure that rcu_read_unlock() is called before any return.

--
/Henrik

2020-09-15 09:41:10

by Henrik Bjoernlund

[permalink] [raw]
Subject: Re: [PATCH RFC 4/7] bridge: cfm: Kernel space implementation of CFM.

Thanks for the review. Comments below.

The 09/08/2020 13:16, Nikolay Aleksandrov wrote:
>
> On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> > This is the implementation of the CFM protocol according to
> > 802.1Q section 12.14.
> >
> > Connectivity Fault Management (CFM) comprises capabilities for
> > detecting, verifying, and isolating connectivity failures in
> > Virtual Bridged Networks. These capabilities can be used in
> > networks operated by multiple independent organizations, each
> > with restricted management access to each other’s equipment.
> >
> > CFM functions are partitioned as follows:
> > - Path discovery
> > - Fault detection
> > - Fault verification and isolation
> > - Fault notification
> > - Fault recovery
> >
> > Interface consists of these functions:
> > br_cfm_mep_create()
> > br_cfm_mep_delete()
> > br_cfm_mep_config_set()
> > br_cfm_mep_status_get()
> > br_cfm_mep_counters_get()
> > br_cfm_mep_counters_clear()
> > br_cfm_cc_config_set()
> > br_cfm_cc_peer_mep_add()
> > br_cfm_cc_peer_mep_remove()
> > br_cfm_cc_rdi_set()
> > br_cfm_cc_ccm_tx()
> > br_cfm_cc_status_get()
> > br_cfm_cc_counters_get()
> > br_cfm_cc_counters_clear()
> > br_cfm_cc_peer_status_get()
> >
> > A MEP instance is created by br_cfm_mep_create()
> > -It is the Maintenance association End Point
> > described in 802.1Q section 19.2.
> > -It is created on a specific level (1-7) and is assuring
> > that no CFM frames are passing through this MEP on lower levels.
> > -It initiates and validates CFM frames on its level.
> > -It can only exist on a port that is related to a bridge.
> > -Attributes given cannot be changed until the instance is
> > deleted.
> >
> > A MEP instance can be deleted by br_cfm_mep_delete().
> >
> > A created MEP instance has attributes that can be
> > configured by br_cfm_mep_config_set().
> >
> > A MEP contain status and counter information that can be
> > retrieved by br_cfm_mep_status_get() and
> > br_cfm_mep_counters_get().
> >
> > A MEP counters can be cleared by br_cfm_mep_counters_clear().
> >
> > A MEP Continuity Check feature can be configured by
> > br_cfm_cc_config_set()
> > The Continuity Check Receiver state machine can be
> > enabled and disabled.
> > According to 802.1Q section 19.2.8
> >
> > A MEP can have Peer MEPs added and removed by
> > br_cfm_cc_peer_mep_add() and br_cfm_cc_peer_mep_remove()
> > The Continuity Check feature can maintain connectivity
> > status on each added Peer MEP.
> >
> > A MEP can be configured to start or stop transmission of CCM frames by
> > br_cfm_cc_ccm_tx()
> > The CCM will be transmitted for a selected period in seconds.
> > Must call this function before timeout to keep transmission alive.
> >
> > A MEP transmitting CCM can be configured with inserted RDI in PDU by
> > br_cfm_cc_rdi_set()
> >
> > A MEP contain Continuity Check status and counter information
> > that can be retrieved by br_cfm_cc_status_get() and
> > br_cfm_cc_counters_get().
> >
> > A MEP Continuity Check counters can be cleared
> > by br_cfm_cc_counters_clear().
> >
> > A MEP contain Peer MEP Continuity Check status information that
> > can be retrieved by br_cfm_cc_peer_status_get().
> >
> > Signed-off-by: Henrik Bjoernlund <[email protected]>
> > ---
> > include/uapi/linux/cfm_bridge.h | 75 +++
> > net/bridge/Makefile | 2 +
> > net/bridge/br_cfm.c | 880 ++++++++++++++++++++++++++++++++
> > net/bridge/br_private.h | 16 +
> > net/bridge/br_private_cfm.h | 242 +++++++++
> > 5 files changed, 1215 insertions(+)
> > create mode 100644 include/uapi/linux/cfm_bridge.h
> > create mode 100644 net/bridge/br_cfm.c
> > create mode 100644 net/bridge/br_private_cfm.h
> >
>
> This is a large single patch, do you think it can be broken down into pieces?
> I'll review it like this now, but it would be much easier if it's in smaller
> logical pieces.
> In general are you sure there are no holes in the structs being assigned
> directly? Since you use memcmp() and such, you could end up surprised. :)
>

I will try and break this patch up into logical pieces.
I will assure that when called from br_cfm_netlink.c the struct is
memset to zero before members are set.

> > diff --git a/include/uapi/linux/cfm_bridge.h b/include/uapi/linux/cfm_bridge.h
> > new file mode 100644
> > index 000000000000..389ea1e1f68e
> > --- /dev/null
> > +++ b/include/uapi/linux/cfm_bridge.h
> > @@ -0,0 +1,75 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
> > +
> > +#ifndef _UAPI_LINUX_CFM_BRIDGE_H_
> > +#define _UAPI_LINUX_CFM_BRIDGE_H_
> > +
> > +#include <linux/types.h>
> > +#include <linux/if_ether.h>
> > +
> > +#define ETHER_HEADER_LENGTH (6+6+4+2)
> > +#define CFM_MAID_LENGTH 48
> > +#define CFM_CCM_PDU_LENGTH 75
> > +#define CFM_PORT_STATUS_TLV_LENGTH 4
> > +#define CFM_IF_STATUS_TLV_LENGTH 4
> > +#define CFM_IF_STATUS_TLV_TYPE 4
> > +#define CFM_PORT_STATUS_TLV_TYPE 2
> > +#define CFM_ENDE_TLV_TYPE 0
> > +#define CFM_CCM_MAX_FRAME_LENGTH (ETHER_HEADER_LENGTH+\
> > + CFM_CCM_PDU_LENGTH+\
> > + CFM_PORT_STATUS_TLV_LENGTH+\
> > + CFM_IF_STATUS_TLV_LENGTH)
> > +#define CFM_FRAME_PRIO 7
> > +#define CFM_CCM_OPCODE 1
> > +#define CFM_CCM_TLV_OFFSET 70
> > +#define CFM_CCM_PDU_MAID_OFFSET 10
> > +#define CFM_CCM_PDU_MEPID_OFFSET 8
> > +#define CFM_CCM_PDU_SEQNR_OFFSET 4
> > +#define CFM_CCM_PDU_TLV_OFFSET 74
> > +#define CFM_CCM_ITU_RESERVED_SIZE 16
> > +
> > +struct br_cfm_common_hdr {
> > + __u8 mdlevel_version;
> > + __u8 opcode;
> > + __u8 flags;
> > + __u8 tlv_offset;
> > +};
> > +
> > +struct br_cfm_status_tlv {
> > + __u8 type;
> > + __be16 length;
> > + __u8 value;
> > +};
> > +
> > +enum br_cfm_opcodes {
> > + BR_CFM_OPCODE_CCM = 0x1,
> > + BR_CFM_OPCODE_LBR = 0x2,
> > + BR_CFM_OPCODE_LBM = 0x3,
> > + BR_CFM_OPCODE_LTR = 0x4,
> > + BR_CFM_OPCODE_LTM = 0x5,
> > +};
> > +
> > +/* MEP domain */
> > +enum br_cfm_domain {
> > + BR_CFM_PORT,
> > + BR_CFM_VLAN,
> > +};
> > +
> > +/* MEP direction */
> > +enum br_cfm_mep_direction {
> > + BR_CFM_MEP_DIRECTION_DOWN,
> > + BR_CFM_MEP_DIRECTION_UP,
> > +};
> > +
> > +/* CCM interval supported. */
> > +enum br_cfm_ccm_interval {
> > + BR_CFM_CCM_INTERVAL_NONE,
> > + BR_CFM_CCM_INTERVAL_3_3_MS,
> > + BR_CFM_CCM_INTERVAL_10_MS,
> > + BR_CFM_CCM_INTERVAL_100_MS,
> > + BR_CFM_CCM_INTERVAL_1_SEC,
> > + BR_CFM_CCM_INTERVAL_10_SEC,
> > + BR_CFM_CCM_INTERVAL_1_MIN,
> > + BR_CFM_CCM_INTERVAL_10_MIN,
> > +};
> > +
> > +#endif
> > diff --git a/net/bridge/Makefile b/net/bridge/Makefile
> > index ccb394236fbd..ddc0a9192348 100644
> > --- a/net/bridge/Makefile
> > +++ b/net/bridge/Makefile
> > @@ -27,3 +27,5 @@ bridge-$(CONFIG_NET_SWITCHDEV) += br_switchdev.o
> > obj-$(CONFIG_NETFILTER) += netfilter/
> >
> > bridge-$(CONFIG_BRIDGE_MRP) += br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o
> > +
> > +bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o
> > diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
> > new file mode 100644
> > index 000000000000..e38cc3e8f262
> > --- /dev/null
> > +++ b/net/bridge/br_cfm.c
> > @@ -0,0 +1,880 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +#include <linux/cfm_bridge.h>
> > +#include <uapi/linux/cfm_bridge.h>
> > +#include "br_private_cfm.h"
> > +
> > +static struct br_cfm_mep *br_mep_find(struct net_bridge *br, u32 instance)
> > +{
> > + struct br_cfm_mep *res = NULL;
> > + struct br_cfm_mep *mep;
> > +
> > + list_for_each_entry_rcu(mep, &br->mep_list, head,
> > + lockdep_rtnl_is_held()) {
> > + if (mep->instance == instance) {
> > + res = mep;
> > + break;
> > + }
> > + }
> > +
> > + return res;
> > +}
>
> It seems br_mep_find() is called only from functions which rely on rtnl being
> held so you can just use normal list traversing functions instead of the rcu
> flavor.
>
I will change that as suggested.

> > +
> > +static struct br_cfm_mep *br_mep_find_ifindex(struct net_bridge *br,
> > + u32 ifindex)
> > +{
> > + struct br_cfm_mep *res = NULL;
> > + struct br_cfm_mep *mep;
> > +
> > + list_for_each_entry_rcu(mep, &br->mep_list, head,
> > + lockdep_rtnl_is_held()) {
> > + if (mep->create.ifindex == ifindex) {
> > + res = mep;
>
> No need for "res", just return "mep" when you find it and NULL below.
>
I will change that as suggested.

> > + break;
> > + }
> > + }
>
> Then you can delete these brackets and the break.
>
I will change that as suggested.

> > +
> > + return res;
> > +}
> > +
> > +static struct br_cfm_peer_mep *br_peer_mep_find(struct br_cfm_mep *mep,
> > + u32 mepid)
> > +{
> > + struct br_cfm_peer_mep *res = NULL;
> > + struct br_cfm_peer_mep *peer_mep;
> > +
> > + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
> > + lockdep_rtnl_is_held()) {
> > + if (peer_mep->mepid == mepid) {
> > + res = peer_mep;
>
> No need for "res", just return "peer_mep" when you find it and NULL below.
>
I will change that as suggested.

> > + break;
> > + }
> > + }
>
> Then you can delete these brackets and the break.
>
I will change that as suggested.

> > +
> > + return res;
> > +}
> > +
> > +static struct net_bridge_port *br_mep_get_port(struct net_bridge *br,
> > + u32 ifindex)
> > +{
> > + struct net_bridge_port *res = NULL;
> > + struct net_bridge_port *port;
> > +
> > + list_for_each_entry(port, &br->port_list, list) {
> > + if (port->dev->ifindex == ifindex) {
> > + res = port;
>
> No need for "res", just return "port" when you find it and NULL below.
>
I will change that as suggested.

> > + break;
> > + }
> > + }
>
> Then you can delete these brackets and the break. :)
>
I will change that as suggested.

> > +
> > + return res;
> > +}
> > +
> > +/* Calculate the CCM interval in us. */
> > +static u32 interval_to_us(enum br_cfm_ccm_interval interval)
> > +{
> > + switch (interval) {
> > + case BR_CFM_CCM_INTERVAL_NONE:
> > + return 0;
> > + case BR_CFM_CCM_INTERVAL_3_3_MS:
> > + return 3300;
> > + case BR_CFM_CCM_INTERVAL_10_MS:
> > + return 10 * 1000;
> > + case BR_CFM_CCM_INTERVAL_100_MS:
> > + return 100 * 1000;
> > + case BR_CFM_CCM_INTERVAL_1_SEC:
> > + return 1000 * 1000;
> > + case BR_CFM_CCM_INTERVAL_10_SEC:
> > + return 10 * 1000 * 1000;
> > + case BR_CFM_CCM_INTERVAL_1_MIN:
> > + return 60 * 1000 * 1000;
> > + case BR_CFM_CCM_INTERVAL_10_MIN:
> > + return 10 * 60 * 1000 * 1000;
> > + }
> > + return 0;
> > +}
> > +
> > +/* Convert the interface interval to CCM PDU value. */
> > +static u32 interval_to_pdu(enum br_cfm_ccm_interval interval)
> > +{
> > + switch (interval) {
> > + case BR_CFM_CCM_INTERVAL_NONE:
> > + return 0;
> > + case BR_CFM_CCM_INTERVAL_3_3_MS:
> > + return 1;
> > + case BR_CFM_CCM_INTERVAL_10_MS:
> > + return 2;
> > + case BR_CFM_CCM_INTERVAL_100_MS:
> > + return 3;
> > + case BR_CFM_CCM_INTERVAL_1_SEC:
> > + return 4;
> > + case BR_CFM_CCM_INTERVAL_10_SEC:
> > + return 5;
> > + case BR_CFM_CCM_INTERVAL_1_MIN:
> > + return 6;
> > + case BR_CFM_CCM_INTERVAL_10_MIN:
> > + return 7;
> > + }
> > + return 0;
> > +}
> > +
> > +/* Convert the CCM PDU value to interval on interface. */
> > +static u32 pdu_to_interval(u32 value)
> > +{
> > + switch (value) {
> > + case 0:
> > + return BR_CFM_CCM_INTERVAL_NONE;
> > + case 1:
> > + return BR_CFM_CCM_INTERVAL_3_3_MS;
> > + case 2:
> > + return BR_CFM_CCM_INTERVAL_10_MS;
> > + case 3:
> > + return BR_CFM_CCM_INTERVAL_100_MS;
> > + case 4:
> > + return BR_CFM_CCM_INTERVAL_1_SEC;
> > + case 5:
> > + return BR_CFM_CCM_INTERVAL_10_SEC;
> > + case 6:
> > + return BR_CFM_CCM_INTERVAL_1_MIN;
> > + case 7:
> > + return BR_CFM_CCM_INTERVAL_10_MIN;
> > + }
> > + return BR_CFM_CCM_INTERVAL_NONE;
> > +}
> > +
> > +static void ccm_rx_timer_start(struct br_cfm_peer_mep *peer_mep)
> > +{
> > + u32 interval_us;
> > +
> > + interval_us = interval_to_us(peer_mep->mep->cc_config.exp_interval);
> > + /* Function ccm_rx_dwork must be called with 1/4
> > + * of the configured CC 'expected_interval'
> > + * in order to detect CCM defect after 3.25 interval.
> > + */
> > + queue_delayed_work(system_wq, &peer_mep->ccm_rx_dwork,
> > + usecs_to_jiffies(interval_us / 4));
> > +}
> > +
> > +static void cc_peer_enable(struct br_cfm_peer_mep *peer_mep)
> > +{
> > + memset(&peer_mep->cc_status, 0, sizeof(peer_mep->cc_status));
> > + peer_mep->ccm_rx_count_miss = 0;
> > +
> > + ccm_rx_timer_start(peer_mep);
> > +}
> > +
> > +static void cc_peer_disable(struct br_cfm_peer_mep *peer_mep)
> > +{
> > + cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork);
> > +}
> > +
> > +static struct sk_buff *ccm_frame_build(struct br_cfm_mep *mep,
> > + const struct br_cfm_cc_ccm_tx_info *const tx_info)
> > +
> > +{
> > + struct br_cfm_common_hdr *common_hdr;
> > + struct net_bridge_port *b_port;
> > + struct br_cfm_maid *maid;
> > + u8 *itu_reserved, *e_tlv;
> > + struct ethhdr *eth_hdr;
> > + struct sk_buff *skb;
> > + __be32 *status_tlv;
> > + __be32 *snumber;
> > + __be16 *mepid;
> > +
> > + rcu_read_lock();
> > + b_port = rcu_dereference(mep->b_port);
> > + if (!b_port)
>
> You need to rcu_read_unlock() here, how was this tested?
>
I will assure that rcu_read_unlock is called before return.

> > + return NULL;
> > + skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH);
> > + if (!skb)
> > + return NULL;
> > +
> > + skb->dev = b_port->dev;
> > + rcu_read_unlock();
>
> You'll have to explain why the device will still exist past this point.
> I get it, but it'd be nice to have it in the commit msg.
>
I have added a comment in the code explaining.
You want that in the commit message too?

> > + skb->protocol = htons(ETH_P_CFM);
> > + skb->priority = CFM_FRAME_PRIO;
> > +
> > + /* Ethernet header */unnecessary comment
> > + eth_hdr = skb_put(skb, sizeof(*eth_hdr));
> > + ether_addr_copy(eth_hdr->h_dest, tx_info->dmac.addr);
> > + ether_addr_copy(eth_hdr->h_source, mep->config.unicast_mac.addr);
> > + eth_hdr->h_proto = htons(ETH_P_CFM);
> > +
> > + /* Common CFM Header */
> > + common_hdr = skb_put(skb, sizeof(*common_hdr));
> > + common_hdr->mdlevel_version = mep->config.mdlevel << 5;
> > + common_hdr->opcode = CFM_CCM_OPCODE;
> > + common_hdr->flags = (mep->rdi << 7) |
> > + interval_to_pdu(mep->cc_config.exp_interval);
> > + common_hdr->tlv_offset = CFM_CCM_TLV_OFFSET;
> > +
> > + /* Sequence number */
> > + snumber = skb_put(skb, sizeof(*snumber));
> > + if (tx_info->seq_no_update) {
> > + *snumber = cpu_to_be32(mep->ccm_tx_snumber);
> > + mep->ccm_tx_snumber += 1;
> > + } else {
> > + *snumber = 0;
> > + }
> > +
> > + /* MEP ID */
> unnecessary comment
I will change that as suggested.

> > + mepid = skb_put(skb, sizeof(*mepid));
> > + *mepid = cpu_to_be16((u16)mep->config.mepid);
> > +
> > + /* MA ID */
> unnecessary comment
I will change that as suggested.

> > + maid = skb_put(skb, sizeof(*maid));
> > + memcpy(maid->data, mep->cc_config.exp_maid.data, sizeof(maid->data));
> > +
> > + /* ITU reserved (CFM_CCM_ITU_RESERVED_SIZE octets) */
> > + itu_reserved = skb_put(skb, CFM_CCM_ITU_RESERVED_SIZE);
> > + memset(itu_reserved, 0, CFM_CCM_ITU_RESERVED_SIZE);
> > +
> > + /* Generel CFM TLV format:
> > + * TLV type: one byte
> > + * TLV value length: two bytes
> > + * TLV value: 'TLV value length' bytes
> > + */
> > +
> > + /* Port status TLV. The value length is 1. Total of 4 bytes. */
> > + if (tx_info->port_tlv) {
> > + status_tlv = skb_put(skb, sizeof(*status_tlv));
> > + *status_tlv = cpu_to_be32((CFM_PORT_STATUS_TLV_TYPE << 24) |
> > + (1 << 8) | /* Value length */
> > + (tx_info->port_tlv_value & 0xFF));
> > + }
> > +
> > + /* Interface status TLV. The value length is 1. Total of 4 bytes. */
> > + if (tx_info->if_tlv) {
> > + status_tlv = skb_put(skb, sizeof(*status_tlv));
> > + *status_tlv = cpu_to_be32((CFM_IF_STATUS_TLV_TYPE << 24) |
> > + (1 << 8) | /* Value length */
> > + (tx_info->if_tlv_value & 0xFF));
> > + }
> > +
> > + /* End TLV */
> > + e_tlv = skb_put(skb, sizeof(*e_tlv));
> > + *e_tlv = CFM_ENDE_TLV_TYPE;
> > +
> > + return skb;
> > +}
> > +
> > +static void ccm_frame_tx(struct sk_buff *skb)
> > +{
> > + skb_reset_network_header(skb);
> > + dev_queue_xmit(skb);
> > +}
> > +
> > +/* This function is called with the configured CC 'expected_interval'
> > + * in order to drive CCM transmission when enabled.
> > + */
> > +static void ccm_tx_work_expired(struct work_struct *work)
> > +{
> > + struct delayed_work *del_work;
> > + struct br_cfm_mep *mep;
> > + struct sk_buff *skb;
> > + u32 interval_us;
> > +
> > + del_work = to_delayed_work(work);
> > + mep = container_of(del_work, struct br_cfm_mep, ccm_tx_dwork);
> > +
> > + if (time_before_eq(mep->ccm_tx_end, jiffies)) {
> > + /* Transmission period has ended */
> > + mep->cc_ccm_tx_info.period = 0;
> > + return;
> > + }
> > +
> > + skb = ccm_frame_build(mep, &mep->cc_ccm_tx_info);
> > + if (skb)
> > + ccm_frame_tx(skb);
> > +
> > + interval_us = interval_to_us(mep->cc_config.exp_interval);
> > + queue_delayed_work(system_wq, &mep->ccm_tx_dwork,
> > + usecs_to_jiffies(interval_us));
> > +}
> > +
> > +/* This function is called with 1/4 of the configured CC 'expected_interval'
> > + * in order to detect CCM defect after 3.25 interval.
> > + */
> > +static void ccm_rx_work_expired(struct work_struct *work)
> > +{
> > + struct br_cfm_peer_mep *peer_mep;
> > + struct delayed_work *del_work;
> > +
> > + del_work = to_delayed_work(work);
> > + peer_mep = container_of(del_work, struct br_cfm_peer_mep, ccm_rx_dwork);
> > +
> > + /* After 13 counts (4 * 3,25) then 3.25 intervals are expired */
> > + if (peer_mep->ccm_rx_count_miss < 13) {
> > + /* 3.25 intervals are NOT expired without CCM reception */
> > + peer_mep->ccm_rx_count_miss++;
> > +
> > + /* Start timer again */
> > + ccm_rx_timer_start(peer_mep);
> > + } else {
> > + /* 3.25 intervals are expired without CCM reception.
> > + * CCM defect detected
> > + */
> > + peer_mep->cc_status.ccm_defect = true;
> > +
> > + /* Change in CCM defect status - notify */
> > + }
> > +}
> > +
> > +static u32 ccm_tlv_extract(struct sk_buff *skb, u32 index,
> > + struct br_cfm_peer_mep *peer_mep)
> > +{
> > + __be32 *s_tlv;
> > + __be32 _s_tlv;
> > + u32 h_s_tlv;
> > + u8 *e_tlv;
> > + u8 _e_tlv;
> > +
> > + e_tlv = skb_header_pointer(skb, index, sizeof(_e_tlv), &_e_tlv);
> > + if (!e_tlv)
> > + return 0;
> > +
> > + /* TLV is present - get the status TLV */
> > + s_tlv = skb_header_pointer(skb,
> > + index,
> > + sizeof(_s_tlv), &_s_tlv);
> > + if (!s_tlv)
> > + return 0;
> > +
> > + h_s_tlv = ntohl(*s_tlv);
> > + if ((h_s_tlv >> 24) == CFM_IF_STATUS_TLV_TYPE) {
> > + /* Interface status TLV */
> > + peer_mep->cc_status.tlv_seen = true;
> > + peer_mep->cc_status.if_tlv_value = (h_s_tlv & 0xFF);
> > + }
> > +
> > + if ((h_s_tlv >> 24) == CFM_PORT_STATUS_TLV_TYPE) {
> > + /* Port status TLV */
> > + peer_mep->cc_status.tlv_seen = true;
> > + peer_mep->cc_status.port_tlv_value = (h_s_tlv & 0xFF);
> > + }
> > +
> > + /* The Sender ID TLV is not handled */
> > + /* The Organization-Specific TLV is not handled */
> > +
> > + /* Return the length of this tlv.
> > + * This is the length of the value field plus 3 bytes for size of type
> > + * field and length field
> > + */
> > + return ((h_s_tlv >> 8) & 0xFFFF) + 3;
> > +}
> > +
> > +/* note: already called with rcu_read_lock */
> > +static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb)
> > +{
> > + u32 mdlevel, interval, size, index, max;
> > + const struct br_cfm_common_hdr *hdr;
> > + struct br_cfm_peer_mep *peer_mep;
> > + const struct br_cfm_maid *maid;
> > + struct br_cfm_common_hdr _hdr;
> > + struct br_cfm_maid _maid;
> > + struct br_cfm_mep *mep;
> > + struct net_bridge *br;
> > + __be32 *snumber;
> > + __be32 _snumber;
> > + __be16 *mepid;
> > + __be16 _mepid;
> > +
> > + /* If port is disabled don't accept any frames */
>
> Unnecessary comment, obvious from the code.
>
I will change that as suggested.

> > + if (port->state == BR_STATE_DISABLED)
> > + return 0;
> > +
> > + hdr = skb_header_pointer(skb, 0, sizeof(_hdr), &_hdr);
> > + if (!hdr)
> > + return 1;
> > +
> > + br = port->br;
> > + mep = br_mep_find_ifindex(br, port->dev->ifindex);
> > + if (unlikely(!mep))
> > + /* No MEP on this port - must be forwarded */
> > + return 0;
> > +
> > + mdlevel = hdr->mdlevel_version >> 5;
> > + if (mdlevel > mep->config.mdlevel)
> > + /* The level is above this MEP level - must be forwarded */
> > + return 0;
> > +
> > + if ((hdr->mdlevel_version & 0x1F) != 0) {
> > + /* Invalid version */
> > + mep->status.version_unexp_seen = true;
> > + return 1;
> > + }
> > +
> > + if (mdlevel < mep->config.mdlevel) {
> > + /* The level is below this MEP level */
> > + mep->status.rx_level_low_seen = true;
> > + return 1;
> > + }
> > +
> > + if (hdr->opcode == BR_CFM_OPCODE_CCM) {
> > + /* CCM PDU received. */
> > + /* MA ID is after common header + sequence number + MEP ID */
> > + maid = skb_header_pointer(skb,
> > + CFM_CCM_PDU_MAID_OFFSET,
> > + sizeof(_maid), &_maid);
> > + if (!maid)
> > + return 1;
> > + if (memcmp(maid->data, mep->cc_config.exp_maid.data,
> > + sizeof(maid->data)))
> > + /* MA ID not as expected */
> > + return 1;
> > +
> > + /* MEP ID is after common header + sequence number */
> > + mepid = skb_header_pointer(skb,
> > + CFM_CCM_PDU_MEPID_OFFSET,
> > + sizeof(_mepid), &_mepid);
> > + if (!mepid)
> > + return 1;
> > + peer_mep = br_peer_mep_find(mep, (u32)ntohs(*mepid));
> > + if (!peer_mep)
> > + return 1;
> > +
> > + /* Interval is in common header flags */
> > + interval = hdr->flags & 0x07;
> > + if (mep->cc_config.exp_interval != pdu_to_interval(interval))
> > + /* Interval not as expected */
> > + return 1;
> > +
> > + /* A valid CCM frame is received */
> > + if (peer_mep->cc_status.ccm_defect) {
> > + peer_mep->cc_status.ccm_defect = false;
> > +
> > + /* Change in CCM defect status - notify */
> > +
> > + /* Start CCM RX timer */
> > + ccm_rx_timer_start(peer_mep);
> > + }
> > +
> > + peer_mep->cc_status.seen = true;
> > + peer_mep->ccm_rx_count_miss = 0;
> > +
> > + /* RDI is in common header flags */
> > + peer_mep->cc_status.rdi = (hdr->flags & 0x80) ? true : false;
> > +
> > + /* Sequence number is after common header */
> > + snumber = skb_header_pointer(skb,
> > + CFM_CCM_PDU_SEQNR_OFFSET,
> > + sizeof(_snumber), &_snumber);
> > + if (!snumber)
> > + return 1;
> > + if (ntohl(*snumber) != (mep->ccm_rx_snumber + 1))
> > + /* Unexpected sequence number */
> > + peer_mep->cc_status.seq_unexp_seen = true;
> > +
> > + mep->ccm_rx_snumber = ntohl(*snumber);
> > +
> > + /* TLV end is after common header + sequence number + MEP ID +
> > + * MA ID + ITU reserved
> > + */
> > + index = CFM_CCM_PDU_TLV_OFFSET;
> > + max = 0;
> > + do { /* Handle all TLVs */
> > + size = ccm_tlv_extract(skb, index, peer_mep);
> > + index += size;
> > + max += 1;
> > + } while (size != 0 && max < 4); /* Max four TLVs possible */
> > +
> > + return 1;
> > + }
> > +
> > + mep->status.opcode_unexp_seen = true;
> > +
> > + return 1;
> > +}
> > +
> > +static struct br_frame_type cfm_frame_type __read_mostly = {
> > + .type = cpu_to_be16(ETH_P_CFM),
> > + .func = br_cfm_frame_rx,
> > +};
> > +
> > +/* note: already called with rtnl_lock */
>
> Instead of these note comments you can just add ASSERT_RTNL()
> in the beginning of
> these functions if you want to show they expect
> rtnl to be held.
>
I will change that as suggested.

> > +int br_cfm_mep_create(struct net_bridge *br,
> > + const u32 instance,
> > + struct br_cfm_mep_create *const create,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct net_bridge_port *p;
> > + struct br_cfm_mep *mep;
> > +
> > + if (create->domain == BR_CFM_VLAN) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "VLAN domain not supported");
> > + return -EINVAL;
> > + }
> > + if (create->domain != BR_CFM_PORT) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Invalid domain value");
> > + return -EINVAL;
> > + }
> > + if (create->direction == BR_CFM_MEP_DIRECTION_UP) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Up-MEP not supported");
> > + return -EINVAL;
> > + }
> > + if (create->direction != BR_CFM_MEP_DIRECTION_DOWN) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Invalid direction value");
> > + return -EINVAL;
> > + }
> > + if (!br_mep_get_port(br, create->ifindex))
>
> You can set extack here, too.
>
I will change that as suggested.

> > + return -EINVAL;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance already created");
> > + return -EINVAL;
> > + }
> > +
> > + /* In PORT domain only one instance can be created per port */
> > + if (create->domain == BR_CFM_PORT) {
> > + mep = br_mep_find_ifindex(br, create->ifindex);
> > + if (mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Only one Port MEP on a port allowed");
> > + return -EINVAL;
> > + }
> > + }
> > +
> > + mep = kzalloc(sizeof(*mep), GFP_KERNEL);
> > + if (!mep)
> > + return -ENOMEM;
> > +
> > + /* Save create parameters */
> > + mep->create = *create;
> > + mep->instance = instance;
> > + p = br_mep_get_port(br, mep->create.ifindex);
>
> Please do this only once in the beginning and use the local variable here.
>
I will change that as suggested.

> > + rcu_assign_pointer(mep->b_port, p);
>
> Can b_port be already assigned here?
>
Maybe I do not understand your comment. As the mep instance has just
been allocated I cannot see how the pointer can be already assigned.

> > +
> > + INIT_LIST_HEAD(&mep->peer_mep_list);
> > + INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired);
> > +
> > + if (list_empty(&br->mep_list))
> > + br_add_frame(br, &cfm_frame_type);
> > +
> > + /* Add instance to list */
> unnecessary comment
I will change that as suggested.

> > + list_add_tail_rcu(&mep->head, &br->mep_list);
> > +
> > + return 0;
> > +}
> > +
> > +static void mep_delete_implementation(struct net_bridge *br,
> > + struct br_cfm_mep *mep)
> > +{
> > + struct br_cfm_peer_mep *peer_mep;
> > +
> > + /* Empty and free peer MEP list */
> > + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
> > + lockdep_rtnl_is_held()) {
>
> You can use normal list traversal
I will change that as suggested.

> > + cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork);
> > + list_del_rcu(&peer_mep->head);
> > + kfree_rcu(peer_mep, rcu);
> > + }
> > +
> > + /* Stop transmitting */
>
> unnecessary comment
>
I will change that as suggested.

> > + cancel_delayed_work_sync(&mep->ccm_tx_dwork);
> > +
> > + /* Free the MEP instance */
>
> unnecessary comment, code is pretty clear
>
I will change that as suggested.

> > + rcu_assign_pointer(mep->b_port, NULL);
>
> RCU_INIT_POINTER
>
I will change that as suggested.

> > + list_del_rcu(&mep->head);
> > + kfree_rcu(mep, rcu);
> > +
> > + if (list_empty(&br->mep_list))
> > + br_del_frame(br, &cfm_frame_type);
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_mep_delete(struct net_bridge *br,
> > + const u32 instance,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_mep *mep;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
> > + return -EINVAL;
> > + }
> > +
> > + mep_delete_implementation(br, mep);
> > +
> > + return 0;
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_mep_config_set(struct net_bridge *br,
> > + const u32 instance,
> > + const struct br_cfm_mep_config *const config,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_mep *mep;
> > +
> > + if (config->mdlevel > 7) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "mdlevel is invalid");
> > + return -EINVAL;
> > + }
> > + if (config->mepid > 0x1FFF) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "mepid is invalid");
>
> MEP-ID ?
>
I have added a comment explaining what the MEP-ID is

> > + return -EINVAL;
> > + }
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
> > + return -EINVAL;
> > + }
> > +
> > + /* Save config parameters */
>
> unnecessary comment
>
I will change that as suggested.

> > + mep->config = *config;
> > +
> > + return 0;
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_mep_counters_clear(struct net_bridge *br,
> > + const u32 instance,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_mep *mep;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
> > + return -EINVAL;
> > + }
> > +
> > + memset(&mep->counters, 0, sizeof(mep->counters));
> > +
> > + return 0;
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_cc_config_set(struct net_bridge *br,
> > + const u32 instance,
> > + const struct br_cfm_cc_config *const config,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_peer_mep *peer_mep;
> > + struct br_cfm_mep *mep;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
>
> "does not exist" somehow seems more fitting, but up to you. :)
>
I will change that as suggested.

> > + return -EINVAL;
> > + }
> > +
> > + /* Check for no change in configuration */
> > + if (memcmp(config, &mep->cc_config, sizeof(*config)) == 0)
> > + return 0;
> > +
> > + if (config->enable && !mep->cc_config.enable)
> > + /* CC is enabled */
> > + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
> > + lockdep_rtnl_is_held())
> > + cc_peer_enable(peer_mep);
>
> Use normal list traversal when rtnl is being held.
>
I will change that as suggested.

> > +
> > + if (!config->enable && mep->cc_config.enable)
> > + /* CC is disabled */
> > + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
> > + lockdep_rtnl_is_held())
>
> Same here.
>
I will change that as suggested.

> > + cc_peer_disable(peer_mep);
> > +
> > + /* Save new cc_config parameters */
>
> unnecessary comment
>
I will change that as suggested.

> > + mep->cc_config = *config;
> > +
> > + return 0;
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
> > + u32 mepid,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_peer_mep *peer_mep;
> > + struct br_cfm_mep *mep;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
> > + return -EINVAL;
> > + }
> > + if (mepid > 0x1FFF) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "mepid is invalid");
>
> Shouldn't this be MEP-ID as it's written out in other places?
>
I have aligned the error messeges

> > + return -EINVAL;
> > + }
> > +
> > + peer_mep = br_peer_mep_find(mep, mepid);
> > + if (peer_mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Peer MEP-ID already added");
>
> perhaps "already exists" ?
>
I will change that as suggested.

> > + return -EINVAL;
>
> EEXIST ?
>
I will change that as suggested.

> > + }
> > +
> > + peer_mep = kzalloc(sizeof(*peer_mep), GFP_KERNEL);
> > + if (!peer_mep)
> > + return -ENOMEM;
> > +
> > + peer_mep->mepid = mepid;
> > + peer_mep->mep = mep;
> > + INIT_DELAYED_WORK(&peer_mep->ccm_rx_dwork, ccm_rx_work_expired);
> > +
> > + if (mep->cc_config.enable)
> > + /* CC is enabled */
>
> Unnecessary comment, the function name is self-descriptive.
>
I will change that as suggested.

> > + cc_peer_enable(peer_mep);
> > +
> > + /* Add Peer MEP to list */
>
> This comment is unnecessary, the code is seems pretty clear.
>
I will change that as suggested.

> > + list_add_tail_rcu(&peer_mep->head, &mep->peer_mep_list);
> > +
> > + return 0;
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
> > + u32 mepid,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_peer_mep *peer_mep;
> > + struct br_cfm_mep *mep;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
> > + return -EINVAL;
> > + }
> > +
> > + peer_mep = br_peer_mep_find(mep, mepid);
> > + if (!peer_mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Peer MEP-ID already deleted");
>
> Why already deleted ? Maybe it didn't exist at all ? :) "does not exist" sounds
> more appropriate.
>
I will change that as suggested.

> > + return -EINVAL;
>
> in that spirit ENOENT here ?
>
I will change that as suggested.

> > + }
> > +
> > + cc_peer_disable(peer_mep);
> > +
> > + list_del_rcu(&peer_mep->head);
> > + kfree_rcu(peer_mep, rcu);
> > +
> > + return 0;
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
> > + const bool rdi, struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_mep *mep;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
> > + return -EINVAL;
> > + }
> > +
> > + /* Save rdi parameters */
>
> unnecessary comment
>
I will change that as suggested.

> > + mep->rdi = rdi;
> > +
> > + return 0;
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
> > + const struct br_cfm_cc_ccm_tx_info *const tx_info,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_mep *mep;
> > + struct sk_buff *skb;
> > + u32 interval_us;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
> > + return -EINVAL;
> > + }
>
> All of these seem to start with finding the instance and checking if it exists.
> Perhaps this can be factored out in the netlink handling code?
>
I agree that it could be done in br_cfm_netlink.c but I prfer to keep it
like this to make code in this file more self contained regarding mep
instances.

If you think it must be changed I will ofc do it.

> > +
> > + if (memcmp(tx_info, &mep->cc_ccm_tx_info, sizeof(*tx_info)) == 0) {
> > + /* No change in tx_info. */
> > + if (mep->cc_ccm_tx_info.period == 0)
> > + /* Transmission is not enabled - just return */
> > + return 0;
> > +
> > + /* Transmission is ongoing, the end time is recalculated */
> > + mep->ccm_tx_end = jiffies +
> > + usecs_to_jiffies(tx_info->period * 1000000);
> > + return 0;
> > + }
> > +
> > + if (tx_info->period == 0 && mep->cc_ccm_tx_info.period == 0)
> > + /* Some change in info and transmission is not ongoing */
> > + goto save;
> > +
> > + if (tx_info->period != 0 && mep->cc_ccm_tx_info.period != 0) {
> > + /* Some change in info and transmission is ongoing */
> > + /* The end time is recalculated */
>
> Multi-line comments format is:
> /* comment1
> * comment2
> */
>
I will change that as suggested.

> > + mep->ccm_tx_end = jiffies +
> > + usecs_to_jiffies(tx_info->period * 1000000);
> > +
> > + goto save;
> > + }
> > +
> > + if (tx_info->period == 0 && mep->cc_ccm_tx_info.period != 0) {
> > + /* CCM transmission stop */
>
> This comment seems unnecessary, the code is pretty clear.
>
I will change that as suggested.

> > + cancel_delayed_work_sync(&mep->ccm_tx_dwork);
> > + goto save;
> > + }
> > +
> > + /* Start delayed work to transmit CCM frames */
>
> This comment seems unnecessary, the code is pretty clear.
>
I will change that as suggested.

> > + mep->ccm_tx_end = jiffies + usecs_to_jiffies(tx_info->period * 1000000);
> > + interval_us = interval_to_us(mep->cc_config.exp_interval);
> > + queue_delayed_work(system_wq, &mep->ccm_tx_dwork,
> > + usecs_to_jiffies(interval_us));
> > +
> > + /* Send first CCM frame now */
> > + mep->ccm_tx_snumber = 0;
> > + skb = ccm_frame_build(mep, tx_info);
> > + if (skb)
> > + ccm_frame_tx(skb);
> > +
> > +save:
> > + /* Save tx_info parameters */
> > + mep->cc_ccm_tx_info = *tx_info;
> > +
> > + return 0;
> > +}
> > +
> > +/* note: already called with rtnl_lock */
> > +int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct br_cfm_mep *mep;
> > +
> > + mep = br_mep_find(br, instance);
> > + if (!mep) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "MEP instance not created");
> > + return -EINVAL;
> > + }
> > +
> > + memset(&mep->cc_counters, 0, sizeof(mep->cc_counters));
> > +
> > + return 0;
> > +}
> > +
> > +bool br_cfm_created(struct net_bridge *br)
> > +{
> > + return !list_empty(&br->mep_list);
> > +}
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index 6294a3e51a33..385a6a6aa17e 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -1363,6 +1363,22 @@ static inline int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br)
> >
> > #endif
> >
> > +/* br_cfm.c */
> > +#if IS_ENABLED(CONFIG_BRIDGE_CFM)
> > +int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
> > +bool br_cfm_created(struct net_bridge *br);
> > +#else
> > +static inline int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb)
> > +{
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +static inline bool br_cfm_created(struct net_bridge *br)
> > +{
> > + return false;
> > +}
> > +#endif
> > +
> > /* br_netlink.c */
> > extern struct rtnl_link_ops br_link_ops;
> > int br_netlink_init(void);
> > diff --git a/net/bridge/br_private_cfm.h b/net/bridge/br_private_cfm.h
> > new file mode 100644
> > index 000000000000..379334a42667
> > --- /dev/null
> > +++ b/net/bridge/br_private_cfm.h
> > @@ -0,0 +1,242 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +
> > +#ifndef _BR_PRIVATE_CFM_H_
> > +#define _BR_PRIVATE_CFM_H_
> > +
> > +#include "br_private.h"
> > +#include <uapi/linux/cfm_bridge.h>
> > +
> > +/* MEP create parameters */
> > +struct br_cfm_mep_create {
> > + enum br_cfm_domain domain; /* Domain for this MEP */
> > + enum br_cfm_mep_direction direction; /* Up or Down MEP direction */
> > + u32 ifindex; /* Residence port */
> > + u16 vid; /* Classified VID in VLAN domain */
> > +};
> > +
> > +/* Create/Delete a MEP. */
>
> This is clear.
>
I will change that as suggested.

> > +int br_cfm_mep_create(struct net_bridge *br,
> > + const u32 instance,
> > + struct br_cfm_mep_create *const create,
> > + struct netlink_ext_ack *extack);
> > +int br_cfm_mep_delete(struct net_bridge *br,
> > + const u32 instance,
> > + struct netlink_ext_ack *extack);
> > +
> > +/* MEP configuration parameters */
> > +struct br_cfm_mep_config {
> > + u32 mdlevel;
> > + u32 mepid; /* MEPID for this MEP */
> > + struct mac_addr unicast_mac; /* The MEP unicast MAC */
> > + /* Added tag VID in case of tagged PORT domain. Untagged if zero. */
> > + u16 vid;
> > +};
> > +
> > +/* Set general configuration of MEP */
>
> The function name is self-descriptive.
>
I will change that as suggested.

> > +int br_cfm_mep_config_set(struct net_bridge *br,
> > + const u32 instance,
> > + const struct br_cfm_mep_config *const config,
> > + struct netlink_ext_ack *extack);
> > +
> > +/* MEP status. */
> > +struct br_cfm_mep_status {
> > + /* Indications that an OAM PDU has been seen. */
> > + /* Are cleared during call to br_cfm_status_get(). */
> > + bool opcode_unexp_seen; /* RX of OAM PDU with unexpected opcode */
> > + bool dmac_unexp_seen; /* RX of OAM Frame with unexpected DMAC */
> > + bool tx_level_low_seen; /* TX of OAM PDU with level low blocked */
> > + bool version_unexp_seen; /* RX of OAM PDU with unexpected version */
> > + bool rx_level_low_seen; /* Rx of OAM PDU with level low */
> > +};
> > +
> > +/* MEP status get. */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_mep_status_get(const u32 instance, struct br_cfm_mep_status *const status);
> > +
> > +/* MEP counters */
>
> unnecessary comment
>
I will change that as suggested.

> > +struct br_cfm_mep_counters {
> > + /* All the following counters are cleared during call to */
> > + /* br_cfm_counters_clear(). */
> > +
> > + /* OAM PDU Rx and Tx counters. */
> > + u64 rx_counter;
> > + u64 tx_counter;
> > +
> > + /* Rx/Tx PDUs that are discarded due to filtering */
> > + u64 rx_discard_counter; /* Check of MEL or DMAC or Version or CCM */
> > + u64 tx_discard_counter; /* Check of MEL */
> > +};
> > +
> > +/* Get MEP counters. */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_mep_counters_get(const u32 instance, struct br_cfm_mep_counters *counters);
> > +
> > +/* MEP counter clear. */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_mep_counters_clear(struct net_bridge *br,
> > + const u32 instance,
> > + struct netlink_ext_ack *extack);
> > +
> > +struct br_cfm_maid {
> > + u8 data[CFM_MAID_LENGTH];
> > +};
> > +
> > +struct br_cfm_cc_config {
> > + /* Expected received CCM PDU MAID. */
> > + struct br_cfm_maid exp_maid;
> > +
> > + /* Expected received CCM PDU interval. */
> > + /* Transmitting CCM PDU interval when CCM tx is enabled. */
> > + enum br_cfm_ccm_interval exp_interval;
> > +
> > + /* Expected received CCM PDU Priority */
> > + u8 exp_priority;
> > +
> > + bool enable; /* Enable/disable CCM PDU handling */
> > +};
> > +
> > +/* Set CC configuration of a MEP */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_cc_config_set(struct net_bridge *br,
> > + const u32 instance,
> > + const struct br_cfm_cc_config *const config,
> > + struct netlink_ext_ack *extack);
> > +
> > +/* Set CC Peer MEP ID add/remove */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
> > + u32 peer_mep_id,
> > + struct netlink_ext_ack *extack);
> > +int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
> > + u32 peer_mep_id,
> > + struct netlink_ext_ack *extack);
> > +
> > +/* Transmitted CCM Remote Defect Indication status set.
> > + * This RDI is inserted in transmitted CCM PDUs if CCM transmission is enabled.
> > + * See br_cfm_cc_ccm_tx() with interval != BR_CFM_CCM_INTERVAL_NONE
> > + */
> > +int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
> > + const bool rdi, struct netlink_ext_ack *extack);
> > +
> > +/* OAM PDU Tx information */
> > +struct br_cfm_cc_ccm_tx_info {
> > + struct mac_addr dmac;
> > + u8 priority;
> > + bool dei;
> > + /* The CCM will be transmitted for this period in seconds.
> > + * Call br_cfm_cc_ccm_tx before timeout to keep transmission alive.
> > + * When period is zero any ongoing transmission will be stopped.
> > + */
> > + u32 period;
> > +
> > + bool seq_no_update; /* Update Tx CCM sequence number */
> > + bool if_tlv; /* Insert Interface Status TLV */
> > + u8 if_tlv_value; /* Interface Status TLV value */
> > + bool port_tlv; /* Insert Port Status TLV */
> > + u8 port_tlv_value; /* Port Status TLV value */
> > + /* Sender ID TLV ??
> > + * Organization-Specific TLV ??
> > + */
> > +};
> > +
> > +/* Transmit CCM PDU */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
> > + const struct br_cfm_cc_ccm_tx_info *const tx_info,
> > + struct netlink_ext_ack *extack);
> > +
> > +struct br_cfm_cc_status {
> > + /* (interval == 0) for last received CCM PDU */
> > + bool zero_interval;
> > +
> > + /* Unexpected MD Level received. Cleared after 3.5 interval */
> > + bool mdlevel_unexp;
> > +
> > + /* Unexpected MAID received. Cleared after 3.5 interval */
> > + bool maid_unexp;
> > +
> > + /* Unexpected MEPID received. Cleared after 3.5 interval */
> > + bool mepid_unexp;
> > +};
> > +
> > +/* Get CC status. */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_cc_status_get(const u32 instance, struct br_cfm_cc_status *const status);
> > +
> > +struct br_cfm_cc_counters {
> > + /* CCM PDU RX and TX counters
> > + * Are cleared during call to br_cfm_cc_counters_clear()
> > + */
> > + u64 rx_valid_counter;
> > + u64 rx_invalid_counter;
> > + u64 rx_oo_counter; /* Out of Order sequence numbers counter */
> > + u64 tx_counter;
> > +};
> > +
> > +/* Get CC counters. */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_cc_counters_get(const u32 instance, struct br_cfm_cc_counters *counters);
> > +
> > +/* CC counter clear. */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
> > + struct netlink_ext_ack *extack);
> > +
> > +struct br_cfm_cc_peer_status {
> > + struct mac_addr unicast_mac; /* The Peer MEP unicast MAC */
> > +
> > + /* Unexpected Interval. Cleared after 3.5 interval */
> > + bool interval_unexp;
> > +
> > + /* Unexpected Priority received. Cleared after 3.5 interval */
> > + bool priority_unexp;
> > +
> > + /* This CCM related status is based on the latest received CCM PDU. */
> > + u32 rx_ifindex; /* The ingress port */
> > + u8 port_tlv_value; /* Port Status TLV value */
> > + u8 if_tlv_value; /* Interface Status TLV value */
> > +
> > + /* CCM has not been received for 3.25 intervals */
> > + bool ccm_defect;
> > +
> > + /* (RDI == 1) for last received CCM PDU */
> > + bool rdi;
> > +
> > + /* Indications that a CCM PDU has been seen.
> > + * Are cleared during call to br_cfm_cc_status_get()
> > + */
> > + bool seen; /* CCM PDU received */
> > + bool tlv_seen; /* CCM PDU with TLV received */
> > + /* CCM PDU with unexpected sequence number received */
> > + bool seq_unexp_seen;
> > +};
> > +
> > +/* Get CC peer MEP status. */
> unnecessary comment
I will change that as suggested.

> > +int br_cfm_cc_peer_status_get(const u32 instance, const u32 mepid,
> > + struct br_cfm_cc_peer_status *const status);
> > +
> > +struct br_cfm_mep {
> > + /* list header of MEP instances */
> > + struct list_head head;
> > + u32 instance;
> > + struct br_cfm_mep_create create;
> > + struct br_cfm_mep_config config;
> > + struct br_cfm_cc_config cc_config;
> > + struct br_cfm_cc_ccm_tx_info cc_ccm_tx_info;
> > + struct br_cfm_mep_counters counters;
> > + struct br_cfm_cc_counters cc_counters;
> > + /* List of multiple peer MEPs */
> > + struct list_head peer_mep_list;
> > + struct rcu_head rcu;
>
> Please move the rcu at the end, it's used only on deletion.
>
I have moved it but there are consequences regarding memory holes in the
structs. According to pahole this create a hole of 6 bytes in br_cfm_mep
and 4 bytes in br_cfm_peer_mep

> > + struct net_bridge_port __rcu *b_port;
> > + unsigned long ccm_tx_end;
> > + struct delayed_work ccm_tx_dwork;
> > + struct br_cfm_cc_status cc_status;
> > + u32 ccm_tx_snumber;
> > + u32 ccm_rx_snumber;
> > + struct br_cfm_mep_status status;
> > + bool rdi;
> > +};
> > +
> > +struct br_cfm_peer_mep {
> > + struct list_head head;
> > + struct br_cfm_mep *mep;
> > + struct rcu_head rcu;
> > + struct delayed_work ccm_rx_dwork;
> > + u32 mepid;
> > + struct br_cfm_cc_peer_status cc_status;
> > + u32 ccm_rx_count_miss;
> > +};
> > +
> > +#endif /* _BR_PRIVATE_CFM_H_ */
>

--
/Henrik

2020-09-15 09:56:08

by Henrik Bjoernlund

[permalink] [raw]
Subject: Re: [PATCH RFC 5/7] bridge: cfm: Netlink Interface.

Thanks for the review. Comments below.

The 09/08/2020 13:47, Nikolay Aleksandrov wrote:
>
> On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> > This is the implementation of CFM netlink configuration
> > and status information interface.
> >
> > Add new nested netlink attributes. These attributes are used by the
> > user space to create/delete/configure CFM instances and get status.
> > Also they are used by the kernel to notify the user space when changes
> > in any status happens.
> [snip]
> > __u64 transition_fwd;
> > diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
> > index 9b814c92de12..fdd408f6a5d2 100644
> > --- a/include/uapi/linux/rtnetlink.h
> > +++ b/include/uapi/linux/rtnetlink.h
> > @@ -779,6 +779,8 @@ enum {
> > #define RTEXT_FILTER_BRVLAN_COMPRESSED (1 << 2)
> > #define RTEXT_FILTER_SKIP_STATS (1 << 3)
> > #define RTEXT_FILTER_MRP (1 << 4)
> > +#define RTEXT_FILTER_CFM_CONFIG (1 << 5)
> > +#define RTEXT_FILTER_CFM_STATUS (1 << 6)
> >
> > /* End of information exported to user level */
> >
> > diff --git a/net/bridge/Makefile b/net/bridge/Makefile
> > index ddc0a9192348..4702702a74d3 100644
> > --- a/net/bridge/Makefile
> > +++ b/net/bridge/Makefile
> > @@ -28,4 +28,4 @@ obj-$(CONFIG_NETFILTER) += netfilter/
> >
> > bridge-$(CONFIG_BRIDGE_MRP) += br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o
> >
> > -bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o
> > +bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o br_cfm_netlink.o
> > diff --git a/net/bridge/br_cfm_netlink.c b/net/bridge/br_cfm_netlink.c
> > new file mode 100644
> > index 000000000000..4e39aab1cd0b
> > --- /dev/null
> > +++ b/net/bridge/br_cfm_netlink.c
> > @@ -0,0 +1,684 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +#include <net/genetlink.h>
> > +
> > +#include "br_private.h"
> > +#include "br_private_cfm.h"
> > +
> > +static inline struct mac_addr nla_get_mac(const struct nlattr *nla)
> > +{
> > + struct mac_addr mac;
> > +
> > + nla_memcpy(&mac.addr, nla, sizeof(mac.addr));
> > +
> > + return mac;
> > +}
> > +
> > +static inline struct br_cfm_maid nla_get_maid(const struct nlattr *nla)
> > +{
> > + struct br_cfm_maid maid;
> > +
> > + nla_memcpy(&maid.data, nla, sizeof(maid.data));
> > +
> > + return maid;
> > +}
>
> IMO, these 1-line helpers don't really help readability.
>
I think they make a difference - you can write nicely in one line:
config.unicast_mac = nla_get_mac(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC]);
Also you can argue the same with functions nla_get_u32() and nla_get_u8 () they are one liner functions.
I you think it must be changed I will ofc do it.

> > +
> > +static inline int nla_put_u64(struct sk_buff *skb, int attrtype, u64 value)
> > +{
> > + u64 tmp = value;
> > +
> > + return nla_put(skb, attrtype, sizeof(u64), &tmp);
> > +}
>
> What?! Read include/net/netlink.h
>
I have removed this. Not used anyway.

> > +
> > +static const struct nla_policy
> > +br_cfm_policy[IFLA_BRIDGE_CFM_MAX + 1] = {
> > + [IFLA_BRIDGE_CFM_UNSPEC] = { .type = NLA_REJECT },
> > + [IFLA_BRIDGE_CFM_MEP_CREATE] = { .type = NLA_NESTED },
> > + [IFLA_BRIDGE_CFM_MEP_DELETE] = { .type = NLA_NESTED },
> > + [IFLA_BRIDGE_CFM_MEP_CONFIG] = { .type = NLA_NESTED },
> > + [IFLA_BRIDGE_CFM_CC_CONFIG] = { .type = NLA_NESTED },
> > + [IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD] = { .type = NLA_NESTED },
> > + [IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE] = { .type = NLA_NESTED },
> > + [IFLA_BRIDGE_CFM_CC_RDI] = { .type = NLA_NESTED },
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX] = { .type = NLA_NESTED },
> > +};
> > +
> > +static const struct nla_policy
> > +br_cfm_mep_create_policy[IFLA_BRIDGE_CFM_MEP_CREATE_MAX + 1] = {
> > + [IFLA_BRIDGE_CFM_MEP_CREATE_UNSPEC] = { .type = NLA_REJECT },
> > + [IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX] = { .type = NLA_U32 },
> > +};
> > +
> > +static const struct nla_policy
> > +br_cfm_mep_delete_policy[IFLA_BRIDGE_CFM_MEP_DELETE_MAX + 1] = {
> > + [IFLA_BRIDGE_CFM_MEP_DELETE_UNSPEC] = { .type = NLA_REJECT },
> > + [IFLA_BRIDGE_CFM_MEP_DELETE_INSTANCE] = { .type = NLA_U32 },
> > +};
> > +
> > +static const struct nla_policy
> > +br_cfm_mep_config_policy[IFLA_BRIDGE_CFM_MEP_CONFIG_MAX + 1] = {
> > + [IFLA_BRIDGE_CFM_MEP_CONFIG_UNSPEC] = { .type = NLA_REJECT },
> > + [IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC] = NLA_POLICY_ETH_ADDR,
> > + [IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID] = { .type = NLA_U32 },
> > +};
> > +
> > +static const struct nla_policy
> > +br_cfm_cc_config_policy[IFLA_BRIDGE_CFM_CC_CONFIG_MAX + 1] = {
> > + [IFLA_BRIDGE_CFM_CC_CONFIG_UNSPEC] = { .type = NLA_REJECT },
> > + [IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID] = {
> > + .type = NLA_BINARY, .len = CFM_MAID_LENGTH },
> > +};
> > +
> > +static const struct nla_policy
> > +br_cfm_cc_peer_mep_policy[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1] = {
> > + [IFLA_BRIDGE_CFM_CC_PEER_MEP_UNSPEC] = { .type = NLA_REJECT },
> > + [IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_PEER_MEPID] = { .type = NLA_U32 },
> > +};
> > +
> > +static const struct nla_policy
> > +br_cfm_cc_rdi_policy[IFLA_BRIDGE_CFM_CC_RDI_MAX + 1] = {
> > + [IFLA_BRIDGE_CFM_CC_RDI_UNSPEC] = { .type = NLA_REJECT },
> > + [IFLA_BRIDGE_CFM_CC_RDI_INSTANCE] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_RDI_RDI] = { .type = NLA_U32 },
> > +};
> > +
> > +static const struct nla_policy
> > +br_cfm_cc_ccm_tx_policy[IFLA_BRIDGE_CFM_CC_CCM_TX_MAX + 1] = {
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_UNSPEC] = { .type = NLA_REJECT },
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC] = NLA_POLICY_ETH_ADDR,
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE] = { .type = NLA_U8 },
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV] = { .type = NLA_U32 },
> > + [IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE] = { .type = NLA_U8 },
> > +};
> > +
> > +static int br_mep_create_parse(struct net_bridge *br, struct nlattr *attr,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_CREATE_MAX + 1];
> > + struct br_cfm_mep_create create;
> > + u32 instance;
> > + int err;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_CREATE_MAX, attr,
> > + br_cfm_mep_create_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (!tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE] ||
> > + !tb[IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN] ||
> > + !tb[IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION] ||
> > + !tb[IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX]) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Missing attribute: INSTANCE or DOMAIN or DIRECTION or VID or IFINDEX");
>
> Break these into separate errors.
>
> > + return -EINVAL;
> > + }
> > +
> > + memset(&create, 0, sizeof(create));
> > +
> > + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]);
> > + create.domain = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN]);
> > + create.direction = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION]);
> > + create.ifindex = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX]);
> > +
> > + return br_cfm_mep_create(br, instance, &create, extack);
> > +}
> > +
> > +static int br_mep_delete_parse(struct net_bridge *br, struct nlattr *attr,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_DELETE_MAX + 1];
> > + u32 instance;
> > + int err;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_DELETE_MAX, attr,
> > + br_cfm_mep_delete_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (!tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Missing attribute: INSTANCE");
>
> "Missing instance attribute". Same for all similar messages.
>
I will change that as suggested.

> > + return -EINVAL;
> > + }
> > +
> > + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE]);
> > +
> > + return br_cfm_mep_delete(br, instance, extack);
> > +}
> > +
> > +static int br_mep_config_parse(struct net_bridge *br, struct nlattr *attr,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MAX + 1];
> > + struct br_cfm_mep_config config;
> > + u32 instance;
> > + int err;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MEP_CONFIG_MAX, attr,
> > + br_cfm_mep_config_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (!tb[IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE] ||
> > + !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC] ||
> > + !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL] ||
> > + !tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID]) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Missing attribute: INSTANCE or UNICAST_MAC or MDLEVEL or MEPID or VID");
>
> Break these into separate errors.
>
I will change that as suggested.

> > + return -EINVAL;
> > + }
> > +
> > + memset(&config, 0, sizeof(config));
> > +
> > + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE]);
> > + config.unicast_mac = nla_get_mac(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC]);
> > + config.mdlevel = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL]);
> > + config.mepid = nla_get_u32(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID]);
> > + config.vid = 0;
> > +
> > + return br_cfm_mep_config_set(br, instance, &config, extack);
> > +}
> > +
> > +static int br_cc_config_parse(struct net_bridge *br, struct nlattr *attr,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_CONFIG_MAX + 1];
> > + struct br_cfm_cc_config config;
> > + u32 instance;
> > + int err;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_CONFIG_MAX, attr,
> > + br_cfm_cc_config_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (!tb[IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID]) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Missing attribute: INSTANCE or ENABLE or INTERVAL or PRIORITY or MAID");
>
> Break these into separate errors.
>
I will change that as suggested.

> > + return -EINVAL;
> > + }
> > +
> > + memset(&config, 0, sizeof(config));
> > +
> > + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE]);
> > + config.enable = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE]);
> > + config.exp_interval = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL]);
> > + config.exp_priority = 0;
> > + config.exp_maid = nla_get_maid(tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID]);
> > +
> > + return br_cfm_cc_config_set(br, instance, &config, extack);
> > +}
> > +
> > +static int br_cc_peer_mep_add_parse(struct net_bridge *br, struct nlattr *attr,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1];
> > + u32 instance, peer_mep_id;
> > + int err;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX, attr,
> > + br_cfm_cc_peer_mep_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (!tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Missing attribute: INSTANCE or PEER_MEP_ID");
>
> Break these into separate errors.
>
I will change that as suggested.

> > + return -EINVAL;
> > + }
> > +
> > + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE]);
> > + peer_mep_id = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]);
> > +
> > + return br_cfm_cc_peer_mep_add(br, instance, peer_mep_id, extack);
> > +}
> > +
> > +static int br_cc_peer_mep_remove_parse(struct net_bridge *br, struct nlattr *attr,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1];
> > + u32 instance, peer_mep_id;
> > + int err;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX, attr,
> > + br_cfm_cc_peer_mep_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (!tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Missing attribute: INSTANCE or PEER_MEP_ID");
>
> Break these into separate errors.
>
I will change that as suggested.

> > + return -EINVAL;
> > + }
> > +
> > + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE]);
> > + peer_mep_id = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_PEER_MEPID]);
> > +
> > + return br_cfm_cc_peer_mep_remove(br, instance, peer_mep_id, extack);
> > +}
> > +
> > +static int br_cc_rdi_parse(struct net_bridge *br, struct nlattr *attr,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_RDI_MAX + 1];
> > + u32 instance, rdi;
> > + int err;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_RDI_MAX, attr,
> > + br_cfm_cc_rdi_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (!tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_RDI_RDI]) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Missing attribute: INSTANCE or RDI");
>
> Break these into separate errors.
>
I will change that as suggested.

> > + return -EINVAL;
> > + }
> > +
> > + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE]);
> > + rdi = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_RDI]);
> > +
> > + return br_cfm_cc_rdi_set(br, instance, rdi, extack);
> > +}
> > +
> > +static int br_cc_ccm_tx_parse(struct net_bridge *br, struct nlattr *attr,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_CC_CCM_TX_MAX + 1];
> > + u32 instance;
> > + struct br_cfm_cc_ccm_tx_info tx_info;
> > + int err;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_MAX, attr,
> > + br_cfm_cc_ccm_tx_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (!tb[IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV] ||
> > + !tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE]) {
> > + NL_SET_ERR_MSG_MOD(extack,
> > + "Missing attribute: INSTANCE or PRIORITY or DEI or DMAC or SEQ_NO_UPDATE or PERIOD or IF_TLV or IF_TLV_VALUE or PORT_TLV or PORT_TLV_VALUE");
>
> Break these into separate errors.
>
I will change that as suggested.

> > + return -EINVAL;
> > + }
> > +
> > + instance = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_RDI_INSTANCE]);
> > + tx_info.priority = 0;
> > + tx_info.dei = 0;
> > + tx_info.dmac = nla_get_mac(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC]);
> > + tx_info.seq_no_update = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE]);
> > + tx_info.period = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD]);
> > + tx_info.if_tlv = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV]);
> > + tx_info.if_tlv_value = nla_get_u8(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE]);
> > + tx_info.port_tlv = nla_get_u32(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV]);
> > + tx_info.port_tlv_value = nla_get_u8(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE]);
> > +
> > + return br_cfm_cc_ccm_tx(br, instance, &tx_info, extack);
> > +}
> > +
> > +int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> > + struct nlattr *attr, int cmd, struct netlink_ext_ack *extack)
> > +{
> > + struct nlattr *tb[IFLA_BRIDGE_CFM_MAX + 1];
> > + int err;
> > +
> > + /* When this function is called for a port then the br pointer is
> > + * invalid, therefor set the br to point correctly
> > + */
> > + if (p)
> > + br = p->br;
> > +
> > + err = nla_parse_nested(tb, IFLA_BRIDGE_CFM_MAX, attr,
> > + br_cfm_policy, extack);
> > + if (err)
> > + return err;
> > +
> > + if (tb[IFLA_BRIDGE_CFM_MEP_CREATE]) {
> > + err = br_mep_create_parse(br, tb[IFLA_BRIDGE_CFM_MEP_CREATE],
> > + extack);
> > + if (err)
> > + return err;
> > + }
> > +
> > + if (tb[IFLA_BRIDGE_CFM_MEP_DELETE]) {
> > + err = br_mep_delete_parse(br, tb[IFLA_BRIDGE_CFM_MEP_DELETE],
> > + extack);
> > + if (err)
> > + return err;
> > + }
> > +
> > + if (tb[IFLA_BRIDGE_CFM_MEP_CONFIG]) {
> > + err = br_mep_config_parse(br, tb[IFLA_BRIDGE_CFM_MEP_CONFIG],
> > + extack);
> > + if (err)
> > + return err;
> > + }
> > +
> > + if (tb[IFLA_BRIDGE_CFM_CC_CONFIG]) {
> > + err = br_cc_config_parse(br, tb[IFLA_BRIDGE_CFM_CC_CONFIG],
> > + extack);
> > + if (err)
> > + return err;
> > + }
> > +
> > + if (tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD]) {
> > + err = br_cc_peer_mep_add_parse(br, tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD],
> > + extack);
> > + if (err)
> > + return err;
> > + }
> > +
> > + if (tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE]) {
> > + err = br_cc_peer_mep_remove_parse(br, tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE],
> > + extack);
> > + if (err)
> > + return err;
> > + }
> > +
> > + if (tb[IFLA_BRIDGE_CFM_CC_RDI]) {
> > + err = br_cc_rdi_parse(br, tb[IFLA_BRIDGE_CFM_CC_RDI],
> > + extack);
> > + if (err)
> > + return err;
> > + }
> > +
> > + if (tb[IFLA_BRIDGE_CFM_CC_CCM_TX]) {
> > + err = br_cc_ccm_tx_parse(br, tb[IFLA_BRIDGE_CFM_CC_CCM_TX],
> > + extack);
> > + if (err)
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > +{
> > + struct nlattr *tb, *cfm_tb;
> > + struct br_cfm_mep *mep;
> > + struct br_cfm_peer_mep *peer_mep;
> > +
> > + cfm_tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM);
>
> Why _noflag everywhere?
>
I will change that to nla_nest_start().

> > + if (!cfm_tb)
> > + return -EMSGSIZE;
> > +
> > + list_for_each_entry_rcu(mep, &br->mep_list, head) {
> > + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_CREATE_INFO);
> > + if (!tb)
> > + goto nla_info_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE,
> > + mep->instance))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN,
> > + mep->create.domain))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION,
> > + mep->create.direction))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX,
> > + mep->create.ifindex))
> > + goto nla_put_failure;
> > +
> > + nla_nest_end(skb, tb);
> > +
> > + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_INFO);
> > +
> > + if (!tb)
> > + goto nla_info_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE,
> > + mep->instance))
> > + goto nla_put_failure;
> > +
> > + if (nla_put(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC,
> > + sizeof(mep->config.unicast_mac.addr),
> > + mep->config.unicast_mac.addr))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL,
> > + mep->config.mdlevel))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID,
> > + mep->config.mepid))
> > + goto nla_put_failure;
> > +
> > + nla_nest_end(skb, tb);
> > +
> > + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_CONFIG_INFO);
> > +
> > + if (!tb)
> > + goto nla_info_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE,
> > + mep->instance))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE,
> > + mep->cc_config.enable))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL,
> > + mep->cc_config.exp_interval))
> > + goto nla_put_failure;
> > +
> > + if (nla_put(skb, IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID,
> > + sizeof(mep->cc_config.exp_maid.data),
> > + mep->cc_config.exp_maid.data))
> > + goto nla_put_failure;
> > +
> > + nla_nest_end(skb, tb);
> > +
> > + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_RDI_INFO);
> > +
> > + if (!tb)
> > + goto nla_info_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_RDI_INSTANCE,
> > + mep->instance))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_RDI_RDI,
> > + mep->rdi))
> > + goto nla_put_failure;
> > +
> > + nla_nest_end(skb, tb);
> > +
> > + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_INFO);
> > +
> > + if (!tb)
> > + goto nla_info_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE,
> > + mep->instance))
> > + goto nla_put_failure;
> > +
> > + if (nla_put(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC,
> > + sizeof(mep->cc_ccm_tx_info.dmac),
> > + mep->cc_ccm_tx_info.dmac.addr))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE,
> > + mep->cc_ccm_tx_info.seq_no_update))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD,
> > + mep->cc_ccm_tx_info.period))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV,
> > + mep->cc_ccm_tx_info.if_tlv))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE,
> > + mep->cc_ccm_tx_info.if_tlv_value))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV,
> > + mep->cc_ccm_tx_info.port_tlv))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE,
> > + mep->cc_ccm_tx_info.port_tlv_value))
> > + goto nla_put_failure;
> > +
> > + nla_nest_end(skb, tb);
> > +
> > + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head) {
> > + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO);
> > +
> > + if (!tb)
> > + goto nla_info_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE,
> > + mep->instance))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_MEPID,
> > + peer_mep->mepid))
> > + goto nla_put_failure;
> > +
> > + nla_nest_end(skb, tb);
> > + }
> > + }
> > + nla_nest_end(skb, cfm_tb);
> > +
> > + return 0;
> > +
> > +nla_put_failure:
> > + nla_nest_cancel(skb, tb);
> > +
> > +nla_info_failure:
> > + nla_nest_cancel(skb, cfm_tb);
> > +
> > + return -EMSGSIZE;
> > +}
> > +
> > +int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > +{
> > + struct nlattr *tb, *cfm_tb;
> > + struct br_cfm_mep *mep;
> > + struct br_cfm_peer_mep *peer_mep;
> > +
> > + cfm_tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM);
> > + if (!cfm_tb)
> > + return -EMSGSIZE;
> > +
> > + list_for_each_entry_rcu(mep, &br->mep_list, head) {
> > + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_MEP_STATUS_INFO);
> > + if (!tb)
> > + goto nla_info_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE,
> > + mep->instance))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN,
> > + mep->status.opcode_unexp_seen))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN,
> > + mep->status.version_unexp_seen))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN,
> > + mep->status.rx_level_low_seen))
> > + goto nla_put_failure;
> > +
> > + /* Clear all 'seen' indications */
> > + mep->status.opcode_unexp_seen = false;
> > + mep->status.version_unexp_seen = false;
> > + mep->status.rx_level_low_seen = false;
> > +
> > + nla_nest_end(skb, tb);
> > +
> > + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head) {
> > + tb = nla_nest_start_noflag(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO);
> > +
> > + if (!tb)
> > + goto nla_info_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE,
> > + mep->instance))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID,
> > + peer_mep->mepid))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT,
> > + peer_mep->cc_status.ccm_defect))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI,
> > + peer_mep->cc_status.rdi))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE,
> > + peer_mep->cc_status.port_tlv_value))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u8(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE,
> > + peer_mep->cc_status.if_tlv_value))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN,
> > + peer_mep->cc_status.seen))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN,
> > + peer_mep->cc_status.tlv_seen))
> > + goto nla_put_failure;
> > +
> > + if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN,
> > + peer_mep->cc_status.seq_unexp_seen))
> > + goto nla_put_failure;
> > +
> > + /* Clear all 'seen' indications */
> > + peer_mep->cc_status.seen = false;
> > + peer_mep->cc_status.tlv_seen = false;
> > + peer_mep->cc_status.seq_unexp_seen = false;
> > +
> > + nla_nest_end(skb, tb);
> > + }
> > + }
> > + nla_nest_end(skb, cfm_tb);
> > +
> > + return 0;
> > +
> > +nla_put_failure:
> > + nla_nest_cancel(skb, tb);
> > +
> > +nla_info_failure:
> > + nla_nest_cancel(skb, cfm_tb);
> > +
> > + return -EMSGSIZE;
> > +}
> > diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> > index 8a71c60fa357..6de5cb1295f6 100644
> > --- a/net/bridge/br_netlink.c
> > +++ b/net/bridge/br_netlink.c
> > @@ -16,6 +16,7 @@
> >
> > #include "br_private.h"
> > #include "br_private_stp.h"
> > +#include "br_private_cfm.h"
> > #include "br_private_tunnel.h"
> >
> > static int __get_num_vlan_infos(struct net_bridge_vlan_group *vg,
> > @@ -481,6 +482,48 @@ static int br_fill_ifinfo(struct sk_buff *skb,
> > nla_nest_end(skb, af);
> > }
> >
> > + if (filter_mask & RTEXT_FILTER_CFM_CONFIG) {
> > + struct nlattr *af;
> > + int err;
> > +
> > + if (!br_cfm_created(br) || port)
> > + goto done;
> > +
> > + af = nla_nest_start_noflag(skb, IFLA_AF_SPEC);
> > + if (!af)
> > + goto nla_put_failure;
> > +
> > + rcu_read_lock();
> > + err = br_cfm_config_fill_info(skb, br);
> > + rcu_read_unlock();
> > +
> > + if (err)
> > + goto nla_put_failure;
> > +
> > + nla_nest_end(skb, af);
> > + }
> > +
> > + if (filter_mask & RTEXT_FILTER_CFM_STATUS) {
> > + struct nlattr *af;
> > + int err;
> > +
> > + if (!br_cfm_created(br) || port)
> > + goto done;
> > +
> > + af = nla_nest_start_noflag(skb, IFLA_AF_SPEC);
> > + if (!af)
> > + goto nla_put_failure;
> > +
> > + rcu_read_lock();
> > + err = br_cfm_status_fill_info(skb, br);
> > + rcu_read_unlock();
> > +
> > + if (err)
> > + goto nla_put_failure;
> > +
> > + nla_nest_end(skb, af);
> > + }
>
> I actually noticed this just now, you can't have multiple IFLA_AF_SPEC
> attributes. It seems we already have that problem with MRP.
> Since filter_mask is a mask one could request multiple rtext attributes.
>
I will change to assure that only one IFLA_AF_SPEC is inserted for all
possible flags set in filter_mask.
I will also assure that only one IFLA_BRIDGE_CFM is inserted if both
RTEXT_FILTER_CFM_CONFIG and RTEXT_FILTER_CFM_STATUS is set in
filter_mask.

> > +
> > done:
> > nlmsg_end(skb, nlh);
> > return 0;
> > @@ -542,7 +585,9 @@ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq,
> >
> > if (!port && !(filter_mask & RTEXT_FILTER_BRVLAN) &&
> > !(filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED) &&
> > - !(filter_mask & RTEXT_FILTER_MRP))
> > + !(filter_mask & RTEXT_FILTER_MRP) &&
> > + !(filter_mask & RTEXT_FILTER_CFM_CONFIG) &&
> > + !(filter_mask & RTEXT_FILTER_CFM_STATUS))
> > return 0;
> >
> > return br_fill_ifinfo(skb, port, pid, seq, RTM_NEWLINK, nlflags,
> > @@ -704,6 +749,11 @@ static int br_afspec(struct net_bridge *br,
> > if (err)
> > return err;
> > break;
> > + case IFLA_BRIDGE_CFM:
> > + err = br_cfm_parse(br, p, attr, cmd, extack);
> > + if (err)
> > + return err;
> > + break;
> > }
> > }
> >
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index 385a6a6aa17e..fe36592f7525 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -1365,9 +1365,20 @@ static inline int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br)
> >
> > /* br_cfm.c */
> > #if IS_ENABLED(CONFIG_BRIDGE_CFM)
> > +int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> > + struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
> > int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
> > bool br_cfm_created(struct net_bridge *br);
> > +int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
> > +int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br);
> > #else
> > +static inline int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> > + struct nlattr *attr, int cmd,
> > + struct netlink_ext_ack *extack)
> > +{
> > + return -EOPNOTSUPP;
> > +}
> > +
> > static inline int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb)
> > {
> > return -EOPNOTSUPP;
> > @@ -1377,6 +1388,16 @@ static inline bool br_cfm_created(struct net_bridge *br)
> > {
> > return false;
> > }
> > +
> > +static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > +{
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +static inline int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > +{
> > + return -EOPNOTSUPP;
> > +}
> > #endif
> >
> > /* br_netlink.c */
>

--
/Henrik

2020-09-15 10:03:18

by Henrik Bjoernlund

[permalink] [raw]
Subject: Re: [PATCH RFC 6/7] bridge: cfm: Netlink Notifications.

Thanks for the review. Comments below.

The 09/08/2020 13:54, Nikolay Aleksandrov wrote:
>
> On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> > This is the implementation of Netlink notifications out of CFM.
> >
> > Notifications are initiated whenever a state change happens in CFM.
> >
> [snip]
> > @@ -445,6 +458,7 @@ static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb)
> > peer_mep->cc_status.ccm_defect = false;
> >
> > /* Change in CCM defect status - notify */
> > + br_cfm_notify(RTM_NEWLINK, port);
> >
> > /* Start CCM RX timer */
> > ccm_rx_timer_start(peer_mep);
> > @@ -874,6 +888,35 @@ int br_cfm_cc_counters_clear(struct net_bridge *br, const u32 instance,
> > return 0;
> > }
> >
> > +int br_cfm_mep_count(struct net_bridge *br, u32 *count)
> > +{
> > + struct br_cfm_mep *mep;
>
> Leave a blank line between local variable definitions and code.
>
I will change that as suggested.

> > + *count = 0;
> > +
> > + rcu_read_lock();
> > + list_for_each_entry_rcu(mep, &br->mep_list, head)
> > + * count += 1;
>
> please remove the extra space
>
I will change that as suggested.

> > + rcu_read_unlock();
> > +
> > + return 0;
> > +}
> > +
> > +int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count)
> > +{
> > + struct br_cfm_peer_mep *peer_mep;
> > + struct br_cfm_mep *mep;
>
> Leave a blank line between local variable definitions and code.
>
I will change that as suggested.

> > + *count = 0;
> > +
> > + rcu_read_lock();
> > + list_for_each_entry_rcu(mep, &br->mep_list, head) {
> > + list_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head)
> > + * count += 1;
>
> please remove the extra space
>
I will change that as suggested.

> > + }
> > + rcu_read_unlock();
> > +
> > + return 0;
> > +}
> > +
> > bool br_cfm_created(struct net_bridge *br)
> > {
> > return !list_empty(&br->mep_list);
> > diff --git a/net/bridge/br_cfm_netlink.c b/net/bridge/br_cfm_netlink.c
> > index 4e39aab1cd0b..13664ac8608a 100644
> > --- a/net/bridge/br_cfm_netlink.c
> > +++ b/net/bridge/br_cfm_netlink.c
> > @@ -582,7 +582,9 @@ int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > return -EMSGSIZE;
> > }
> >
> > -int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > +int br_cfm_status_fill_info(struct sk_buff *skb,
> > + struct net_bridge *br,
> > + bool getlink)
> > {
> > struct nlattr *tb, *cfm_tb;
> > struct br_cfm_mep *mep;
> > @@ -613,10 +615,12 @@ int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > mep->status.rx_level_low_seen))
> > goto nla_put_failure;
> >
> > - /* Clear all 'seen' indications */
> > - mep->status.opcode_unexp_seen = false;
> > - mep->status.version_unexp_seen = false;
> > - mep->status.rx_level_low_seen = false;
> > + if (getlink) { /* Only clear if this is a GETLINK */
> > + /* Clear all 'seen' indications */
> > + mep->status.opcode_unexp_seen = false;
> > + mep->status.version_unexp_seen = false;
> > + mep->status.rx_level_low_seen = false;
> > + }
> >
> > nla_nest_end(skb, tb);
> >
> > @@ -662,10 +666,12 @@ int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > peer_mep->cc_status.seq_unexp_seen))
> > goto nla_put_failure;
> >
> > - /* Clear all 'seen' indications */
> > - peer_mep->cc_status.seen = false;
> > - peer_mep->cc_status.tlv_seen = false;
> > - peer_mep->cc_status.seq_unexp_seen = false;
> > + if (getlink) { /* Only clear if this is a GETLINK */
> > + /* Clear all 'seen' indications */
> > + peer_mep->cc_status.seen = false;
> > + peer_mep->cc_status.tlv_seen = false;
> > + peer_mep->cc_status.seq_unexp_seen = false;
>
> Why clear these on GETLINK? This sounds like it should be a set op.
>
The idea is that when getting the CFM status any '_seen' indications are
cleared so that they are giving information about what was seen since
last get.
I assume this is a get/clear atomic operations as rcu is held.
If you think this must be changed to a seperate clear_set operation I
will ofc do it.

> > + }
> >
> > nla_nest_end(skb, tb);
> > }
> > diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> > index 6de5cb1295f6..f2e885521f4f 100644
> > --- a/net/bridge/br_netlink.c
> > +++ b/net/bridge/br_netlink.c
> > @@ -94,9 +94,11 @@ static size_t br_get_link_af_size_filtered(const struct net_device *dev,
> > {
> > struct net_bridge_vlan_group *vg = NULL;
> > struct net_bridge_port *p = NULL;
> > - struct net_bridge *br;
> > - int num_vlan_infos;
> > + struct net_bridge *br = NULL;
> > + u32 num_cfm_peer_mep_infos;
> > + u32 num_cfm_mep_infos;
> > size_t vinfo_sz = 0;
> > + int num_vlan_infos;
> >
> > rcu_read_lock();
> > if (netif_is_bridge_port(dev)) {
> > @@ -115,6 +117,52 @@ static size_t br_get_link_af_size_filtered(const struct net_device *dev,
> > /* Each VLAN is returned in bridge_vlan_info along with flags */
> > vinfo_sz += num_vlan_infos * nla_total_size(sizeof(struct bridge_vlan_info));
> >
> > + if (!(filter_mask & RTEXT_FILTER_CFM_STATUS))
> > + return vinfo_sz;
> > +
> > + if (!br)
> > + return vinfo_sz;
> > +
> > + /* CFM status info must be added */
> > + if (br_cfm_mep_count(br, &num_cfm_mep_infos))
> > + return vinfo_sz;
> > +
> > + if (br_cfm_peer_mep_count(br, &num_cfm_peer_mep_infos))
> > + return vinfo_sz;
> > +
>
> Can these return non-0 at all?
>
I have removed check of return value.

> > + vinfo_sz += nla_total_size(0); /* IFLA_BRIDGE_CFM */
> > + /* For each status struct the MEP instance (u32) is added */
> > + /* MEP instance (u32) + br_cfm_mep_status */
> > + vinfo_sz += num_cfm_mep_infos *
> > + /*IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE */
> > + (nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN */
> > + + nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN */
> > + + nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN */
> > + + nla_total_size(sizeof(u32)));
> > + /* MEP instance (u32) + br_cfm_cc_peer_status */
> > + vinfo_sz += num_cfm_peer_mep_infos *
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE */
> > + (nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID */
> > + + nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT */
> > + + nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI */
> > + + nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE */
> > + + nla_total_size(sizeof(u8))
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE */
> > + + nla_total_size(sizeof(u8))
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN */
> > + + nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN */
> > + + nla_total_size(sizeof(u32))
> > + /* IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN */
> > + + nla_total_size(sizeof(u32)));
> > +
> > return vinfo_sz;
> > }
> >
> > @@ -378,7 +426,8 @@ static int br_fill_ifvlaninfo(struct sk_buff *skb,
> > static int br_fill_ifinfo(struct sk_buff *skb,
> > const struct net_bridge_port *port,
> > u32 pid, u32 seq, int event, unsigned int flags,
> > - u32 filter_mask, const struct net_device *dev)
> > + u32 filter_mask, const struct net_device *dev,
> > + bool getlink)
> > {
> > u8 operstate = netif_running(dev) ? dev->operstate : IF_OPER_DOWN;
> > struct net_bridge *br;
> > @@ -515,7 +564,7 @@ static int br_fill_ifinfo(struct sk_buff *skb,
> > goto nla_put_failure;
> >
> > rcu_read_lock();
> > - err = br_cfm_status_fill_info(skb, br);
> > + err = br_cfm_status_fill_info(skb, br, getlink);
> > rcu_read_unlock();
> >
> > if (err)
> > @@ -533,11 +582,9 @@ static int br_fill_ifinfo(struct sk_buff *skb,
> > return -EMSGSIZE;
> > }
> >
> > -/* Notify listeners of a change in bridge or port information */
> > -void br_ifinfo_notify(int event, const struct net_bridge *br,
> > - const struct net_bridge_port *port)
> > +void br_info_notify(int event, const struct net_bridge *br,
> > + const struct net_bridge_port *port, u32 filter)
> > {
> > - u32 filter = RTEXT_FILTER_BRVLAN_COMPRESSED;
> > struct net_device *dev;
> > struct sk_buff *skb;
> > int err = -ENOBUFS;
> > @@ -562,7 +609,7 @@ void br_ifinfo_notify(int event, const struct net_bridge *br,
> > if (skb == NULL)
> > goto errout;
> >
> > - err = br_fill_ifinfo(skb, port, 0, 0, event, 0, filter, dev);
> > + err = br_fill_ifinfo(skb, port, 0, 0, event, 0, filter, dev, false);
> > if (err < 0) {
> > /* -EMSGSIZE implies BUG in br_nlmsg_size() */
> > WARN_ON(err == -EMSGSIZE);
> > @@ -575,6 +622,15 @@ void br_ifinfo_notify(int event, const struct net_bridge *br,
> > rtnl_set_sk_err(net, RTNLGRP_LINK, err);
> > }
> >
> > +/* Notify listeners of a change in bridge or port information */
> > +void br_ifinfo_notify(int event, const struct net_bridge *br,
> > + const struct net_bridge_port *port)
> > +{
> > + u32 filter = RTEXT_FILTER_BRVLAN_COMPRESSED;
> > +
> > + return br_info_notify(event, br, port, filter);
> > +}
> > +
> > /*
> > * Dump information about all ports, in response to GETLINK
> > */
> > @@ -591,7 +647,7 @@ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq,
> > return 0;
> >
> > return br_fill_ifinfo(skb, port, pid, seq, RTM_NEWLINK, nlflags,
> > - filter_mask, dev);
> > + filter_mask, dev, true);
> > }
> >
> > static int br_vlan_info(struct net_bridge *br, struct net_bridge_port *p,
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index fe36592f7525..53bcbdd21f34 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -1370,7 +1370,11 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> > int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
> > bool br_cfm_created(struct net_bridge *br);
> > int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
> > -int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br);
> > +int br_cfm_status_fill_info(struct sk_buff *skb,
> > + struct net_bridge *br,
> > + bool getlink);
> > +int br_cfm_mep_count(struct net_bridge *br, u32 *count);
> > +int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count);
> > #else
> > static inline int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> > struct nlattr *attr, int cmd,
> > @@ -1394,7 +1398,19 @@ static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge
> > return -EOPNOTSUPP;
> > }
> >
> > -static inline int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > +static inline int br_cfm_status_fill_info(struct sk_buff *skb,
> > + struct net_bridge *br,
> > + bool getlink)
> > +{
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +static inline int br_cfm_mep_count(struct net_bridge *br, u32 *count)
> > +{
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +static inline int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count)
> > {
> > return -EOPNOTSUPP;
> > }
> > @@ -1406,6 +1422,8 @@ int br_netlink_init(void);
> > void br_netlink_fini(void);
> > void br_ifinfo_notify(int event, const struct net_bridge *br,
> > const struct net_bridge_port *port);
> > +void br_info_notify(int event, const struct net_bridge *br,
> > + const struct net_bridge_port *port, u32 filter);
> > int br_setlink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags,
> > struct netlink_ext_ack *extack);
> > int br_dellink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags);
>

--
/Henrik

2020-09-15 10:04:33

by Henrik Bjoernlund

[permalink] [raw]
Subject: Re: [PATCH RFC 7/7] bridge: cfm: Bridge port remove.

Thanks for the review. I will update the next version as suggested.

The 09/08/2020 13:58, Nikolay Aleksandrov wrote:
>
> On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> > This is addition of CFM functionality to delete MEP instances
> > on a port that is removed from the bridge.
> > A MEP can only exist on a port that is related to a bridge.
> >
> > Signed-off-by: Henrik Bjoernlund <[email protected]>
> > ---
> > net/bridge/br_cfm.c | 13 +++++++++++++
> > net/bridge/br_if.c | 1 +
> > net/bridge/br_private.h | 6 ++++++
> > 3 files changed, 20 insertions(+)
> >
> > diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
> > index b7fed2c1d8ec..c724ce020ce3 100644
> > --- a/net/bridge/br_cfm.c
> > +++ b/net/bridge/br_cfm.c
> > @@ -921,3 +921,16 @@ bool br_cfm_created(struct net_bridge *br)
> > {
> > return !list_empty(&br->mep_list);
> > }
> > +
> > +/* Deletes the CFM instances on a specific bridge port
> > + * note: called under rtnl_lock
> > + */
> > +void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *port)
> > +{
> > + struct br_cfm_mep *mep;
> > +
> > + list_for_each_entry_rcu(mep, &br->mep_list, head,
> > + lockdep_rtnl_is_held())
>
> Use standard/non-rcu list traversing, rtnl is already held.
>
> > + if (mep->create.ifindex == port->dev->ifindex)
> > + mep_delete_implementation(br, mep);
> > +}
> > diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c
> > index a0e9a7937412..f7d2f472ae24 100644
> > --- a/net/bridge/br_if.c
> > +++ b/net/bridge/br_if.c
> > @@ -334,6 +334,7 @@ static void del_nbp(struct net_bridge_port *p)
> > spin_unlock_bh(&br->lock);
> >
> > br_mrp_port_del(br, p);
> > + br_cfm_port_del(br, p);
> >
> > br_ifinfo_notify(RTM_DELLINK, NULL, p);
> >
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index 53bcbdd21f34..5617255f0c0c 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -1369,6 +1369,7 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
> > struct nlattr *attr, int cmd, struct netlink_ext_ack *extack);
> > int br_cfm_rx_frame_process(struct net_bridge_port *p, struct sk_buff *skb);
> > bool br_cfm_created(struct net_bridge *br);
> > +void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *p);
> > int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br);
> > int br_cfm_status_fill_info(struct sk_buff *skb,
> > struct net_bridge *br,
> > @@ -1393,6 +1394,11 @@ static inline bool br_cfm_created(struct net_bridge *br)
> > return false;
> > }
> >
> > +static inline void br_cfm_port_del(struct net_bridge *br,
> > + struct net_bridge_port *p)
> > +{
> > +}
> > +
> > static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br)
> > {
> > return -EOPNOTSUPP;
>

--
/Henrik

2020-09-15 10:28:41

by Henrik Bjoernlund

[permalink] [raw]
Subject: Re: [PATCH RFC 6/7] bridge: cfm: Netlink Notifications.

Thanks for the review. Comments below.

The 09/08/2020 13:54, Nikolay Aleksandrov wrote:
>
> On Fri, 2020-09-04 at 09:15 +0000, Henrik Bjoernlund wrote:
> > This is the implementation of Netlink notifications out of CFM.
> >
> > Notifications are initiated whenever a state change happens in CFM.
> >
> [snip]
> > + *count = 0;
> > +
> > + rcu_read_lock();
> > + list_for_each_entry_rcu(mep, &br->mep_list, head)
> > + * count += 1;
>
> please remove the extra space
>
I have removed the extra space.
This space was added to satify checkpatch as without this space it gives
this error:
CHECK: spaces preferred around that '*' (ctx:ExV)
#136: FILE: net/bridge/br_cfm.c:883:
+ *count += 1;
^

> > + rcu_read_unlock();
> > +
> > + return 0;
> > +}
> > +
>
>

--
/Henrik