* [PATCH iproute2-next 0/2] bridge: add cfm command
@ 2026-10-04 4:13 Abdul Wasey
2026-10-04 4:13 ` [PATCH iproute2-next 1/2] uapi: import cfm_bridge.h Abdul Wasey
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Abdul Wasey @ 2026-10-04 4:13 UTC (permalink / raw)
To: netdev; +Cc: dsahern, stephen, idosch, razor, horatiu.vultur, bridge
The bridge has had 802.1ag CFM support since 5.11, but iproute2 never
got a command for it. The only userspace for it today is Microchip's out
of tree cfm tool. This series adds "bridge cfm".
Patch 1 imports cfm_bridge.h, which has the domain, direction and
interval enums the CFM attributes use. Patch 2 adds the command and a
man page section.
I tested it on a 7.0 kernel with two bridges joined by a veth pair and
a MEP on each end. CCMs go out at every interval, each side sees the
other as a peer, the TLV values and the RDI bit get through, and
ccm_defect gets set when one side stops sending. I also checked show
config and show status, text and JSON, with more than one MEP and peer
on a bridge.
While testing I found that the kernel sends CCMs from a workqueue, so
with HZ=1000 the 3.3ms interval actually comes out at about 5ms and 1s
at about 1024ms. The man page says the real interval can be longer than
its name.
One thing I wasn't sure about: the MAID is given as a hex string, the
same bytes the kernel stores. The Microchip tool builds it from an MD
name and an MA name. I can add that, but it seemed better as a separate
patch.
Separately, I'm sending a net-next patch so that CFM config changes
send a notification. Right now only status changes do. The two don't
depend on each other.
Abdul Wasey (2):
uapi: import cfm_bridge.h
bridge: add cfm command
bridge/Makefile | 2 +-
bridge/br_common.h | 1 +
bridge/bridge.c | 3 +-
bridge/cfm.c | 1058 +++++++++++++++++++++++++++++++
include/uapi/linux/cfm_bridge.h | 64 ++
man/man8/bridge.8 | 196 +++++-
6 files changed, 1321 insertions(+), 3 deletions(-)
create mode 100644 bridge/cfm.c
create mode 100644 include/uapi/linux/cfm_bridge.h
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH iproute2-next 1/2] uapi: import cfm_bridge.h
2026-10-04 4:13 [PATCH iproute2-next 0/2] bridge: add cfm command Abdul Wasey
@ 2026-10-04 4:13 ` Abdul Wasey
2026-10-04 4:13 ` [PATCH iproute2-next 2/2] bridge: add cfm command Abdul Wasey
2026-10-06 10:42 ` [PATCH iproute2-next 0/2] " Ido Schimmel
2 siblings, 0 replies; 5+ messages in thread
From: Abdul Wasey @ 2026-10-04 4:13 UTC (permalink / raw)
To: netdev; +Cc: dsahern, stephen, idosch, razor, horatiu.vultur, bridge
The bridge CFM netlink attributes use the MEP domain, MEP direction and
CCM interval enums from cfm_bridge.h, but the header was never imported.
The bridge cfm command in the next patch needs it. Copied as is from
net-next.
Signed-off-by: Abdul Wasey <w453y.me@gmail.com>
---
include/uapi/linux/cfm_bridge.h | 64 +++++++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 include/uapi/linux/cfm_bridge.h
diff --git a/include/uapi/linux/cfm_bridge.h b/include/uapi/linux/cfm_bridge.h
new file mode 100644
index 00000000..3c1cbd1d
--- /dev/null
+++ b/include/uapi/linux/cfm_bridge.h
@@ -0,0 +1,64 @@
+/* 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_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;
+};
+
+enum br_cfm_opcodes {
+ BR_CFM_OPCODE_CCM = 0x1,
+};
+
+/* 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
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH iproute2-next 2/2] bridge: add cfm command
2026-10-04 4:13 [PATCH iproute2-next 0/2] bridge: add cfm command Abdul Wasey
2026-10-04 4:13 ` [PATCH iproute2-next 1/2] uapi: import cfm_bridge.h Abdul Wasey
@ 2026-10-04 4:13 ` Abdul Wasey
2026-10-06 10:42 ` [PATCH iproute2-next 0/2] " Ido Schimmel
2 siblings, 0 replies; 5+ messages in thread
From: Abdul Wasey @ 2026-10-04 4:13 UTC (permalink / raw)
To: netdev; +Cc: dsahern, stephen, idosch, razor, horatiu.vultur, bridge
The bridge has had 802.1ag CFM support since 5.11 (net/bridge/br_cfm.c),
but iproute2 never got a command for it, so the only way to use it is
Microchip's out of tree cfm tool.
Add "bridge cfm", which covers what the kernel supports:
bridge cfm mep { add | del | set }
bridge cfm cc config
bridge cfm cc peer { add | del }
bridge cfm cc rdi
bridge cfm cc ccm-tx
bridge cfm show { config | status }
Each command maps to one IFLA_BRIDGE_CFM_* group and is sent to the
bridge device with BRIDGE_FLAGS_SELF. The MAID is taken as a hex string,
the same bytes the kernel stores. There is no way yet to build it from
an MD and MA name.
Example with a MEP on each end of a veth pair, one bridge each side:
$ bridge cfm mep add bridge brA instance 1 port vA
$ bridge cfm mep set bridge brA instance 1 \
address 02:00:00:00:00:0a level 3 mepid 10
$ bridge cfm cc config bridge brA instance 1 \
enable on interval 100ms maid 0104746573740002
$ bridge cfm cc peer add bridge brA instance 1 mepid 20
$ bridge cfm cc ccm-tx bridge brA instance 1 \
dmac 01:80:c2:00:00:33 period 30
(same on brB with mepid 20 and peer 10)
$ bridge cfm show status bridge brA
brA
mep 1 opcode_unexp_seen false version_unexp_seen false ...
peer mepid 20 ccm_defect false rdi false ... seen true ...
Signed-off-by: Abdul Wasey <w453y.me@gmail.com>
---
bridge/Makefile | 2 +-
bridge/br_common.h | 1 +
bridge/bridge.c | 3 +-
bridge/cfm.c | 1058 ++++++++++++++++++++++++++++++++++++++++++++
man/man8/bridge.8 | 196 +++++++-
5 files changed, 1257 insertions(+), 3 deletions(-)
create mode 100644 bridge/cfm.c
diff --git a/bridge/Makefile b/bridge/Makefile
index 4c57df43..4bf5b529 100644
--- a/bridge/Makefile
+++ b/bridge/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-BROBJ = bridge.o fdb.o monitor.o link.o mdb.o mst.o vlan.o vni.o
+BROBJ = bridge.o fdb.o monitor.o link.o mdb.o mst.o vlan.o vni.o cfm.o
include ../config.mk
diff --git a/bridge/br_common.h b/bridge/br_common.h
index 3a0cf882..fb9b0d5a 100644
--- a/bridge/br_common.h
+++ b/bridge/br_common.h
@@ -24,6 +24,7 @@ int do_mst(int argc, char **argv);
int do_vlan(int argc, char **argv);
int do_link(int argc, char **argv);
int do_vni(int argc, char **argv);
+int do_cfm(int argc, char **argv);
extern int preferred_family;
extern int show_stats;
diff --git a/bridge/bridge.c b/bridge/bridge.c
index d993ba19..1c3bde32 100644
--- a/bridge/bridge.c
+++ b/bridge/bridge.c
@@ -36,7 +36,7 @@ static void usage(void)
fprintf(stderr,
"Usage: bridge [ OPTIONS ] OBJECT { COMMAND | help }\n"
" bridge [ -force ] -batch filename\n"
-"where OBJECT := { link | fdb | mdb | mst | vlan | vni | monitor }\n"
+"where OBJECT := { link | fdb | mdb | mst | vlan | vni | cfm | monitor }\n"
" OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] |\n"
" -o[neline] | -t[imestamp] | -n[etns] name |\n"
" -com[pressvlans] -c[olor] -p[retty] -j[son] }\n");
@@ -59,6 +59,7 @@ static const struct cmd {
{ "mst", do_mst },
{ "vlan", do_vlan },
{ "vni", do_vni },
+ { "cfm", do_cfm },
{ "monitor", do_monitor },
{ "help", do_help },
{ 0 }
diff --git a/bridge/cfm.c b/bridge/cfm.c
new file mode 100644
index 00000000..1b7fb159
--- /dev/null
+++ b/bridge/cfm.c
@@ -0,0 +1,1058 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * bridge cfm: configure and show IEEE 802.1ag Connectivity Fault Management
+ * (CFM) Maintenance Endpoints (MEPs) on a Linux bridge.
+ *
+ * Authors: Abdul Wasey <w453y.me@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <net/if.h>
+#include <linux/if_bridge.h>
+#include <linux/cfm_bridge.h>
+#include <linux/rtnetlink.h>
+
+#include "libnetlink.h"
+#include "utils.h"
+#include "br_common.h"
+#include "json_print.h"
+#include "rt_names.h"
+
+static unsigned int filter_index;
+
+static void cfm_usage(void)
+{
+ fprintf(stderr,
+"Usage: bridge cfm mep add bridge DEV instance INSTANCE port PORT\n"
+" [ domain port ] [ direction down ]\n"
+" bridge cfm mep del bridge DEV instance INSTANCE\n"
+" bridge cfm mep set bridge DEV instance INSTANCE address LLADDR\n"
+" level LEVEL mepid MEPID\n"
+" bridge cfm cc config bridge DEV instance INSTANCE enable { on | off }\n"
+" interval INTERVAL maid MAID\n"
+" bridge cfm cc peer { add | del } bridge DEV instance INSTANCE mepid MEPID\n"
+" bridge cfm cc rdi bridge DEV instance INSTANCE rdi { on | off }\n"
+" bridge cfm cc ccm-tx bridge DEV instance INSTANCE dmac LLADDR period PERIOD\n"
+" [ seq { on | off } ]\n"
+" [ iftlv { on | off } ] [ iftlv-value VALUE ]\n"
+" [ porttlv { on | off } ] [ porttlv-value VALUE ]\n"
+" bridge cfm show { config | status } [ bridge DEV ]\n"
+"\n"
+"where INTERVAL := { 3ms3 | 10ms | 100ms | 1s | 10s | 1m | 10m }\n"
+" MAID is the 48 byte MAID as a hex string, zero padded if shorter\n"
+" PERIOD is how many seconds to send CCMs for, 0 stops sending\n"
+" seq on puts an incrementing sequence number in each CCM\n");
+ exit(-1);
+}
+
+static int cfm_parse_interval(const char *str)
+{
+ if (!strcmp(str, "none"))
+ return BR_CFM_CCM_INTERVAL_NONE;
+ if (!strcmp(str, "3ms3") || !strcmp(str, "3.3ms"))
+ return BR_CFM_CCM_INTERVAL_3_3_MS;
+ if (!strcmp(str, "10ms"))
+ return BR_CFM_CCM_INTERVAL_10_MS;
+ if (!strcmp(str, "100ms"))
+ return BR_CFM_CCM_INTERVAL_100_MS;
+ if (!strcmp(str, "1s"))
+ return BR_CFM_CCM_INTERVAL_1_SEC;
+ if (!strcmp(str, "10s"))
+ return BR_CFM_CCM_INTERVAL_10_SEC;
+ if (!strcmp(str, "1m"))
+ return BR_CFM_CCM_INTERVAL_1_MIN;
+ if (!strcmp(str, "10m"))
+ return BR_CFM_CCM_INTERVAL_10_MIN;
+ return -1;
+}
+
+static const char *cfm_interval_str(__u32 interval)
+{
+ switch (interval) {
+ case BR_CFM_CCM_INTERVAL_NONE:
+ return "none";
+ case BR_CFM_CCM_INTERVAL_3_3_MS:
+ return "3ms3";
+ case BR_CFM_CCM_INTERVAL_10_MS:
+ return "10ms";
+ case BR_CFM_CCM_INTERVAL_100_MS:
+ return "100ms";
+ case BR_CFM_CCM_INTERVAL_1_SEC:
+ return "1s";
+ case BR_CFM_CCM_INTERVAL_10_SEC:
+ return "10s";
+ case BR_CFM_CCM_INTERVAL_1_MIN:
+ return "1m";
+ case BR_CFM_CCM_INTERVAL_10_MIN:
+ return "10m";
+ default:
+ return "unknown";
+ }
+}
+
+static int cfm_talk(struct nlmsghdr *n)
+{
+ return rtnl_talk(&rth, n, NULL);
+}
+
+/* "bridge cfm mep add" / "bridge cfm mep del" / "bridge cfm mep set" */
+
+static int cfm_mep_add(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ struct rtattr *afspec, *cfm, *create;
+ __u32 instance = 0, domain = BR_CFM_PORT, direction = BR_CFM_MEP_DIRECTION_DOWN;
+ __u32 port_idx = 0;
+ char *bridge_dev = NULL, *port_dev = NULL;
+ bool instance_set = false;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bridge") == 0) {
+ NEXT_ARG();
+ bridge_dev = *argv;
+ } else if (strcmp(*argv, "instance") == 0) {
+ NEXT_ARG();
+ if (get_u32(&instance, *argv, 0))
+ invarg("invalid instance", *argv);
+ instance_set = true;
+ } else if (strcmp(*argv, "domain") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "port") == 0)
+ domain = BR_CFM_PORT;
+ else if (strcmp(*argv, "vlan") == 0)
+ domain = BR_CFM_VLAN;
+ else
+ invarg("invalid domain", *argv);
+ } else if (strcmp(*argv, "direction") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "down") == 0)
+ direction = BR_CFM_MEP_DIRECTION_DOWN;
+ else if (strcmp(*argv, "up") == 0)
+ direction = BR_CFM_MEP_DIRECTION_UP;
+ else
+ invarg("invalid direction", *argv);
+ } else if (strcmp(*argv, "port") == 0) {
+ NEXT_ARG();
+ port_dev = *argv;
+ } else {
+ cfm_usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!bridge_dev || !instance_set || !port_dev) {
+ fprintf(stderr,
+ "Required: bridge DEV instance INSTANCE port PORT_DEV\n");
+ return -1;
+ }
+
+ req.ifm.ifi_index = ll_name_to_index(bridge_dev);
+ if (!req.ifm.ifi_index)
+ return nodev(bridge_dev);
+
+ port_idx = ll_name_to_index(port_dev);
+ if (!port_idx)
+ return nodev(port_dev);
+
+ afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
+ cfm = addattr_nest(&req.n, sizeof(req), IFLA_BRIDGE_CFM | NLA_F_NESTED);
+ create = addattr_nest(&req.n, sizeof(req),
+ IFLA_BRIDGE_CFM_MEP_CREATE | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE,
+ instance);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN,
+ domain);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION,
+ direction);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX,
+ port_idx);
+ addattr_nest_end(&req.n, create);
+ addattr_nest_end(&req.n, cfm);
+ addattr_nest_end(&req.n, afspec);
+
+ return cfm_talk(&req.n);
+}
+
+static int cfm_mep_del(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ struct rtattr *afspec, *cfm, *del;
+ __u32 instance = 0;
+ char *bridge_dev = NULL;
+ bool instance_set = false;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bridge") == 0) {
+ NEXT_ARG();
+ bridge_dev = *argv;
+ } else if (strcmp(*argv, "instance") == 0) {
+ NEXT_ARG();
+ if (get_u32(&instance, *argv, 0))
+ invarg("invalid instance", *argv);
+ instance_set = true;
+ } else {
+ cfm_usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!bridge_dev || !instance_set) {
+ fprintf(stderr, "Required: bridge DEV instance INSTANCE\n");
+ return -1;
+ }
+
+ req.ifm.ifi_index = ll_name_to_index(bridge_dev);
+ if (!req.ifm.ifi_index)
+ return nodev(bridge_dev);
+
+ afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
+ cfm = addattr_nest(&req.n, sizeof(req), IFLA_BRIDGE_CFM | NLA_F_NESTED);
+ del = addattr_nest(&req.n, sizeof(req),
+ IFLA_BRIDGE_CFM_MEP_DELETE | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_DELETE_INSTANCE,
+ instance);
+ addattr_nest_end(&req.n, del);
+ addattr_nest_end(&req.n, cfm);
+ addattr_nest_end(&req.n, afspec);
+
+ return cfm_talk(&req.n);
+}
+
+static int cfm_mep_set(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ struct rtattr *afspec, *cfm, *config;
+ __u32 instance = 0, level = 0, mepid = 0;
+ char *bridge_dev = NULL, *address = NULL;
+ bool instance_set = false, level_set = false, mepid_set = false;
+ char abuf[32];
+ int alen = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bridge") == 0) {
+ NEXT_ARG();
+ bridge_dev = *argv;
+ } else if (strcmp(*argv, "instance") == 0) {
+ NEXT_ARG();
+ if (get_u32(&instance, *argv, 0))
+ invarg("invalid instance", *argv);
+ instance_set = true;
+ } else if (strcmp(*argv, "address") == 0) {
+ NEXT_ARG();
+ address = *argv;
+ } else if (strcmp(*argv, "level") == 0) {
+ NEXT_ARG();
+ if (get_u32(&level, *argv, 0) || level > 7)
+ invarg("invalid level (0-7)", *argv);
+ level_set = true;
+ } else if (strcmp(*argv, "mepid") == 0) {
+ NEXT_ARG();
+ if (get_u32(&mepid, *argv, 0) || mepid > 0x1FFF)
+ invarg("invalid mepid (0-8191)", *argv);
+ mepid_set = true;
+ } else {
+ cfm_usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!bridge_dev || !instance_set || !address ||
+ !level_set || !mepid_set) {
+ fprintf(stderr,
+ "Required: bridge DEV instance INSTANCE address LLADDR level LEVEL mepid MEPID\n");
+ return -1;
+ }
+
+ req.ifm.ifi_index = ll_name_to_index(bridge_dev);
+ if (!req.ifm.ifi_index)
+ return nodev(bridge_dev);
+
+ alen = ll_addr_a2n(abuf, sizeof(abuf), address);
+ if (alen != ETH_ALEN) {
+ fprintf(stderr, "Invalid address %s\n", address);
+ return -1;
+ }
+
+ afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
+ cfm = addattr_nest(&req.n, sizeof(req), IFLA_BRIDGE_CFM | NLA_F_NESTED);
+ config = addattr_nest(&req.n, sizeof(req),
+ IFLA_BRIDGE_CFM_MEP_CONFIG | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE,
+ instance);
+ addattr_l(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC,
+ abuf, ETH_ALEN);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL,
+ level);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID,
+ mepid);
+ addattr_nest_end(&req.n, config);
+ addattr_nest_end(&req.n, cfm);
+ addattr_nest_end(&req.n, afspec);
+
+ return cfm_talk(&req.n);
+}
+
+static int cfm_mep(int argc, char **argv)
+{
+ if (argc <= 0)
+ cfm_usage();
+
+ if (strcmp(*argv, "add") == 0)
+ return cfm_mep_add(argc - 1, argv + 1);
+ if (strcmp(*argv, "delete") == 0 || strcmp(*argv, "del") == 0)
+ return cfm_mep_del(argc - 1, argv + 1);
+ if (strcmp(*argv, "set") == 0)
+ return cfm_mep_set(argc - 1, argv + 1);
+
+ cfm_usage();
+ return -1;
+}
+
+/* "bridge cfm cc ..." */
+
+static int cfm_cc_config(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ struct rtattr *afspec, *cfm, *config;
+ __u32 instance = 0;
+ int enable = -1, interval = -1;
+ char *bridge_dev = NULL, *maid_str = NULL;
+ bool instance_set = false;
+ unsigned char maid[CFM_MAID_LENGTH] = {};
+ unsigned int maid_len = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bridge") == 0) {
+ NEXT_ARG();
+ bridge_dev = *argv;
+ } else if (strcmp(*argv, "instance") == 0) {
+ NEXT_ARG();
+ if (get_u32(&instance, *argv, 0))
+ invarg("invalid instance", *argv);
+ instance_set = true;
+ } else if (strcmp(*argv, "enable") == 0) {
+ int ret;
+
+ NEXT_ARG();
+ enable = parse_on_off("enable", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "interval") == 0) {
+ NEXT_ARG();
+ interval = cfm_parse_interval(*argv);
+ if (interval < 0)
+ invarg("invalid interval", *argv);
+ } else if (strcmp(*argv, "maid") == 0) {
+ NEXT_ARG();
+ maid_str = *argv;
+ } else {
+ cfm_usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!bridge_dev || !instance_set || enable < 0 || interval < 0 ||
+ !maid_str) {
+ fprintf(stderr,
+ "Required: bridge DEV instance INSTANCE enable { on | off } interval INTERVAL maid MAID\n");
+ return -1;
+ }
+
+ req.ifm.ifi_index = ll_name_to_index(bridge_dev);
+ if (!req.ifm.ifi_index)
+ return nodev(bridge_dev);
+
+ if (!hexstring_a2n(maid_str, maid, sizeof(maid), &maid_len)) {
+ fprintf(stderr, "Invalid maid %s\n", maid_str);
+ return -1;
+ }
+
+ afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
+ cfm = addattr_nest(&req.n, sizeof(req), IFLA_BRIDGE_CFM | NLA_F_NESTED);
+ config = addattr_nest(&req.n, sizeof(req),
+ IFLA_BRIDGE_CFM_CC_CONFIG | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE,
+ instance);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE,
+ enable);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL,
+ interval);
+ addattr_l(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID, maid,
+ sizeof(maid));
+ addattr_nest_end(&req.n, config);
+ addattr_nest_end(&req.n, cfm);
+ addattr_nest_end(&req.n, afspec);
+
+ return cfm_talk(&req.n);
+}
+
+static int cfm_cc_peer(int argc, char **argv, bool add)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ struct rtattr *afspec, *cfm, *peer;
+ __u32 instance = 0, mepid = 0;
+ char *bridge_dev = NULL;
+ bool instance_set = false, mepid_set = false;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bridge") == 0) {
+ NEXT_ARG();
+ bridge_dev = *argv;
+ } else if (strcmp(*argv, "instance") == 0) {
+ NEXT_ARG();
+ if (get_u32(&instance, *argv, 0))
+ invarg("invalid instance", *argv);
+ instance_set = true;
+ } else if (strcmp(*argv, "mepid") == 0) {
+ NEXT_ARG();
+ if (get_u32(&mepid, *argv, 0) || mepid > 0x1FFF)
+ invarg("invalid mepid (0-8191)", *argv);
+ mepid_set = true;
+ } else {
+ cfm_usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!bridge_dev || !instance_set || !mepid_set) {
+ fprintf(stderr,
+ "Required: bridge DEV instance INSTANCE mepid MEPID\n");
+ return -1;
+ }
+
+ req.ifm.ifi_index = ll_name_to_index(bridge_dev);
+ if (!req.ifm.ifi_index)
+ return nodev(bridge_dev);
+
+ afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
+ cfm = addattr_nest(&req.n, sizeof(req), IFLA_BRIDGE_CFM | NLA_F_NESTED);
+ peer = addattr_nest(&req.n, sizeof(req),
+ (add ? IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD :
+ IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE) |
+ NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE,
+ instance);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_PEER_MEPID, mepid);
+ addattr_nest_end(&req.n, peer);
+ addattr_nest_end(&req.n, cfm);
+ addattr_nest_end(&req.n, afspec);
+
+ return cfm_talk(&req.n);
+}
+
+static int cfm_cc_rdi(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ struct rtattr *afspec, *cfm, *rdi;
+ __u32 instance = 0;
+ int rdi_val = -1;
+ char *bridge_dev = NULL;
+ bool instance_set = false;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bridge") == 0) {
+ NEXT_ARG();
+ bridge_dev = *argv;
+ } else if (strcmp(*argv, "instance") == 0) {
+ NEXT_ARG();
+ if (get_u32(&instance, *argv, 0))
+ invarg("invalid instance", *argv);
+ instance_set = true;
+ } else if (strcmp(*argv, "rdi") == 0) {
+ int ret;
+
+ NEXT_ARG();
+ rdi_val = parse_on_off("rdi", *argv, &ret);
+ if (ret)
+ return ret;
+ } else {
+ cfm_usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!bridge_dev || !instance_set || rdi_val < 0) {
+ fprintf(stderr,
+ "Required: bridge DEV instance INSTANCE rdi { on | off }\n");
+ return -1;
+ }
+
+ req.ifm.ifi_index = ll_name_to_index(bridge_dev);
+ if (!req.ifm.ifi_index)
+ return nodev(bridge_dev);
+
+ afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
+ cfm = addattr_nest(&req.n, sizeof(req), IFLA_BRIDGE_CFM | NLA_F_NESTED);
+ rdi = addattr_nest(&req.n, sizeof(req),
+ IFLA_BRIDGE_CFM_CC_RDI | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_RDI_INSTANCE,
+ instance);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_RDI_RDI, rdi_val);
+ addattr_nest_end(&req.n, rdi);
+ addattr_nest_end(&req.n, cfm);
+ addattr_nest_end(&req.n, afspec);
+
+ return cfm_talk(&req.n);
+}
+
+static int cfm_cc_ccm_tx(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ struct rtattr *afspec, *cfm, *tx;
+ __u32 instance = 0, period = 0;
+ __u32 iftlv = 0, porttlv = 0;
+ __u8 iftlv_value = 0, porttlv_value = 0;
+ int seq = 0;
+ char *bridge_dev = NULL, *dmac = NULL;
+ bool instance_set = false, period_set = false;
+ char abuf[32];
+ int alen;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bridge") == 0) {
+ NEXT_ARG();
+ bridge_dev = *argv;
+ } else if (strcmp(*argv, "instance") == 0) {
+ NEXT_ARG();
+ if (get_u32(&instance, *argv, 0))
+ invarg("invalid instance", *argv);
+ instance_set = true;
+ } else if (strcmp(*argv, "dmac") == 0) {
+ NEXT_ARG();
+ dmac = *argv;
+ } else if (strcmp(*argv, "seq") == 0) {
+ int ret;
+
+ NEXT_ARG();
+ seq = parse_on_off("seq", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "period") == 0) {
+ NEXT_ARG();
+ if (get_u32(&period, *argv, 0))
+ invarg("invalid period", *argv);
+ period_set = true;
+ } else if (strcmp(*argv, "iftlv") == 0) {
+ int ret;
+
+ NEXT_ARG();
+ iftlv = parse_on_off("iftlv", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "iftlv-value") == 0) {
+ __u32 v;
+
+ NEXT_ARG();
+ if (get_u32(&v, *argv, 0) || v > 0xff)
+ invarg("invalid iftlv-value", *argv);
+ iftlv_value = v;
+ } else if (strcmp(*argv, "porttlv") == 0) {
+ int ret;
+
+ NEXT_ARG();
+ porttlv = parse_on_off("porttlv", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "porttlv-value") == 0) {
+ __u32 v;
+
+ NEXT_ARG();
+ if (get_u32(&v, *argv, 0) || v > 0xff)
+ invarg("invalid porttlv-value", *argv);
+ porttlv_value = v;
+ } else {
+ cfm_usage();
+ }
+ argc--; argv++;
+ }
+
+ if (!bridge_dev || !instance_set || !dmac || !period_set) {
+ fprintf(stderr,
+ "Required: bridge DEV instance INSTANCE dmac LLADDR period PERIOD\n");
+ return -1;
+ }
+
+ req.ifm.ifi_index = ll_name_to_index(bridge_dev);
+ if (!req.ifm.ifi_index)
+ return nodev(bridge_dev);
+
+ alen = ll_addr_a2n(abuf, sizeof(abuf), dmac);
+ if (alen != ETH_ALEN) {
+ fprintf(stderr, "Invalid address %s\n", dmac);
+ return -1;
+ }
+
+ afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
+ cfm = addattr_nest(&req.n, sizeof(req), IFLA_BRIDGE_CFM | NLA_F_NESTED);
+ tx = addattr_nest(&req.n, sizeof(req),
+ IFLA_BRIDGE_CFM_CC_CCM_TX | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE,
+ instance);
+ addattr_l(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC, abuf,
+ ETH_ALEN);
+ addattr32(&req.n, sizeof(req),
+ IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE, seq);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD,
+ period);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV,
+ iftlv);
+ addattr8(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE,
+ iftlv_value);
+ addattr32(&req.n, sizeof(req), IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV,
+ porttlv);
+ addattr8(&req.n, sizeof(req),
+ IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE, porttlv_value);
+ addattr_nest_end(&req.n, tx);
+ addattr_nest_end(&req.n, cfm);
+ addattr_nest_end(&req.n, afspec);
+
+ return cfm_talk(&req.n);
+}
+
+static int cfm_cc(int argc, char **argv)
+{
+ if (argc <= 0)
+ cfm_usage();
+
+ if (strcmp(*argv, "config") == 0)
+ return cfm_cc_config(argc - 1, argv + 1);
+ if (strcmp(*argv, "peer") == 0) {
+ if (argc < 2)
+ cfm_usage();
+ if (strcmp(argv[1], "add") == 0)
+ return cfm_cc_peer(argc - 2, argv + 2, true);
+ if (strcmp(argv[1], "delete") == 0 ||
+ strcmp(argv[1], "del") == 0)
+ return cfm_cc_peer(argc - 2, argv + 2, false);
+ cfm_usage();
+ }
+ if (strcmp(*argv, "rdi") == 0)
+ return cfm_cc_rdi(argc - 1, argv + 1);
+ if (strcmp(*argv, "ccm-tx") == 0)
+ return cfm_cc_ccm_tx(argc - 1, argv + 1);
+
+ cfm_usage();
+ return -1;
+}
+
+/* "bridge cfm show { config | status }" */
+
+static __u32 cfm_get_u32(struct rtattr *tb[], int type)
+{
+ return tb[type] ? rta_getattr_u32(tb[type]) : 0;
+}
+
+static __u8 cfm_get_u8(struct rtattr *tb[], int type)
+{
+ return tb[type] ? rta_getattr_u8(tb[type]) : 0;
+}
+
+static void cfm_print_mac(struct rtattr *attr, const char *key,
+ const char *fmt)
+{
+ SPRINT_BUF(b);
+
+ if (!attr || RTA_PAYLOAD(attr) < ETH_ALEN)
+ return;
+ ll_addr_n2a(RTA_DATA(attr), ETH_ALEN, 0, b, sizeof(b));
+ print_string(PRINT_ANY, key, fmt, b);
+}
+
+static void cfm_print_mep_create(struct rtattr *attr)
+{
+ struct rtattr *tb[IFLA_BRIDGE_CFM_MEP_CREATE_MAX + 1];
+ const char *port;
+
+ parse_rtattr_nested(tb, IFLA_BRIDGE_CFM_MEP_CREATE_MAX, attr);
+
+ print_uint(PRINT_ANY, "instance", " mep %u",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE));
+ print_string(PRINT_ANY, "domain", " domain %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN) ==
+ BR_CFM_VLAN ? "vlan" : "port");
+ print_string(PRINT_ANY, "direction", " direction %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION) ==
+ BR_CFM_MEP_DIRECTION_UP ? "up" : "down");
+ port = ll_index_to_name(cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX));
+ print_string(PRINT_ANY, "port", " port %s", port);
+ print_nl();
+}
+
+static void cfm_print_mep_config(struct rtattr *attr)
+{
+ struct rtattr *tb[IFLA_BRIDGE_CFM_MEP_CONFIG_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_BRIDGE_CFM_MEP_CONFIG_MAX, attr);
+
+ cfm_print_mac(tb[IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC], "address",
+ " address %s");
+ print_uint(PRINT_ANY, "level", " level %u",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL));
+ print_uint(PRINT_ANY, "mepid", " mepid %u",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID));
+ print_nl();
+}
+
+static void cfm_print_cc_config(struct rtattr *attr)
+{
+ struct rtattr *tb[IFLA_BRIDGE_CFM_CC_CONFIG_MAX + 1];
+ char maid[CFM_MAID_LENGTH * 2 + 1] = "";
+
+ parse_rtattr_nested(tb, IFLA_BRIDGE_CFM_CC_CONFIG_MAX, attr);
+
+ open_json_object("cc");
+ print_on_off(PRINT_ANY, "enable", " cc enable %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE));
+ print_string(PRINT_ANY, "interval", " interval %s",
+ cfm_interval_str(cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL)));
+ if (tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID])
+ hexstring_n2a(RTA_DATA(tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID]),
+ RTA_PAYLOAD(tb[IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID]),
+ maid, sizeof(maid));
+ print_string(PRINT_ANY, "maid", " maid %s", maid);
+ print_nl();
+ close_json_object();
+}
+
+static void cfm_print_cc_rdi(struct rtattr *attr)
+{
+ struct rtattr *tb[IFLA_BRIDGE_CFM_CC_RDI_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_BRIDGE_CFM_CC_RDI_MAX, attr);
+
+ print_on_off(PRINT_ANY, "rdi", " rdi %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_RDI_RDI));
+ print_nl();
+}
+
+static void cfm_print_cc_ccm_tx(struct rtattr *attr)
+{
+ struct rtattr *tb[IFLA_BRIDGE_CFM_CC_CCM_TX_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_MAX, attr);
+
+ open_json_object("ccm_tx");
+ cfm_print_mac(tb[IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC], "dmac",
+ " ccm-tx dmac %s");
+ print_on_off(PRINT_ANY, "seq", " seq %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE));
+ print_uint(PRINT_ANY, "period", " period %u",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD));
+ print_on_off(PRINT_ANY, "iftlv", " iftlv %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV));
+ print_uint(PRINT_ANY, "iftlv_value", " iftlv-value %u",
+ cfm_get_u8(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE));
+ print_on_off(PRINT_ANY, "porttlv", " porttlv %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV));
+ print_uint(PRINT_ANY, "porttlv_value", " porttlv-value %u",
+ cfm_get_u8(tb, IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE));
+ print_nl();
+ close_json_object();
+}
+
+static void cfm_print_cc_peer(struct rtattr *attr)
+{
+ struct rtattr *tb[IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX, attr);
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "mepid", " peer mepid %u",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_PEER_MEPID));
+ print_nl();
+ close_json_object();
+}
+
+static void cfm_print_mep_status(struct rtattr *attr)
+{
+ struct rtattr *tb[IFLA_BRIDGE_CFM_MEP_STATUS_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_BRIDGE_CFM_MEP_STATUS_MAX, attr);
+
+ print_uint(PRINT_ANY, "instance", " mep %u",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE));
+ print_bool(PRINT_ANY, "opcode_unexp_seen", " opcode_unexp_seen %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN));
+ print_bool(PRINT_ANY, "version_unexp_seen", " version_unexp_seen %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN));
+ print_bool(PRINT_ANY, "rx_level_low_seen", " rx_level_low_seen %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN));
+ print_nl();
+}
+
+static void cfm_print_cc_peer_status(struct rtattr *attr)
+{
+ struct rtattr *tb[IFLA_BRIDGE_CFM_CC_PEER_STATUS_MAX + 1];
+
+ parse_rtattr_nested(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_MAX, attr);
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "mepid", " peer mepid %u",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID));
+ print_bool(PRINT_ANY, "ccm_defect", " ccm_defect %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT));
+ print_bool(PRINT_ANY, "rdi", " rdi %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI));
+ print_uint(PRINT_ANY, "port_tlv_value", " port_tlv_value %u",
+ cfm_get_u8(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE));
+ print_uint(PRINT_ANY, "if_tlv_value", " if_tlv_value %u",
+ cfm_get_u8(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE));
+ print_bool(PRINT_ANY, "seen", " seen %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN));
+ print_bool(PRINT_ANY, "tlv_seen", " tlv_seen %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN));
+ print_bool(PRINT_ANY, "seq_unexp_seen", " seq_unexp_seen %s",
+ cfm_get_u32(tb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN));
+ print_nl();
+ close_json_object();
+}
+
+/* The kernel sends one nest per MEP followed by its sub-nests and one
+ * nest per peer, so walk them in order and group by MEP.
+ */
+static int cfm_show_filter(struct nlmsghdr *n, void *arg)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+ bool status = !strcmp(arg, "status");
+ bool dev_open = false, mep_open = false, peers_open = false;
+ struct rtattr *afspec, *cfm, *a;
+
+ if (len < 0)
+ return -1;
+
+ if (n->nlmsg_type != RTM_NEWLINK || ifi->ifi_family != AF_BRIDGE)
+ return 0;
+
+ if (filter_index && filter_index != (unsigned int)ifi->ifi_index)
+ return 0;
+
+ afspec = parse_rtattr_one(IFLA_AF_SPEC, IFLA_RTA(ifi), len);
+ if (!afspec)
+ return 0;
+
+ cfm = parse_rtattr_one_nested(NLA_F_NESTED | IFLA_BRIDGE_CFM, afspec);
+ if (!cfm)
+ return 0;
+
+ rtattr_for_each_nested(a, cfm) {
+ unsigned short type = a->rta_type & NLA_TYPE_MASK;
+ bool new_mep, peer;
+
+ new_mep = type == (status ? IFLA_BRIDGE_CFM_MEP_STATUS_INFO :
+ IFLA_BRIDGE_CFM_MEP_CREATE_INFO);
+ peer = type == (status ? IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO :
+ IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO);
+
+ if (!new_mep && !mep_open)
+ continue;
+
+ if (peers_open && !peer) {
+ close_json_array(PRINT_JSON, NULL);
+ peers_open = false;
+ }
+ if (new_mep) {
+ if (mep_open)
+ close_json_object();
+ if (!dev_open) {
+ open_json_object(NULL);
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", "%s\n",
+ ll_index_to_name(ifi->ifi_index));
+ open_json_array(PRINT_JSON, "meps");
+ dev_open = true;
+ }
+ open_json_object(NULL);
+ mep_open = true;
+ }
+ if (peer && !peers_open) {
+ open_json_array(PRINT_JSON, "peers");
+ peers_open = true;
+ }
+
+ switch (type) {
+ case IFLA_BRIDGE_CFM_MEP_CREATE_INFO:
+ cfm_print_mep_create(a);
+ break;
+ case IFLA_BRIDGE_CFM_MEP_CONFIG_INFO:
+ cfm_print_mep_config(a);
+ break;
+ case IFLA_BRIDGE_CFM_CC_CONFIG_INFO:
+ cfm_print_cc_config(a);
+ break;
+ case IFLA_BRIDGE_CFM_CC_RDI_INFO:
+ cfm_print_cc_rdi(a);
+ break;
+ case IFLA_BRIDGE_CFM_CC_CCM_TX_INFO:
+ cfm_print_cc_ccm_tx(a);
+ break;
+ case IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO:
+ cfm_print_cc_peer(a);
+ break;
+ case IFLA_BRIDGE_CFM_MEP_STATUS_INFO:
+ cfm_print_mep_status(a);
+ break;
+ case IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO:
+ cfm_print_cc_peer_status(a);
+ break;
+ }
+ }
+
+ if (peers_open)
+ close_json_array(PRINT_JSON, NULL);
+ if (mep_open)
+ close_json_object();
+ if (dev_open) {
+ close_json_array(PRINT_JSON, NULL);
+ close_json_object();
+ }
+
+ return 0;
+}
+
+static int cfm_show(int argc, char **argv, const char *what)
+{
+ char *bridge_dev = NULL;
+ __u32 filt_mask;
+ int ret;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bridge") == 0) {
+ NEXT_ARG();
+ bridge_dev = *argv;
+ } else {
+ cfm_usage();
+ }
+ argc--; argv++;
+ }
+
+ if (bridge_dev) {
+ filter_index = ll_name_to_index(bridge_dev);
+ if (!filter_index)
+ return nodev(bridge_dev);
+ }
+
+ filt_mask = !strcmp(what, "config") ? RTEXT_FILTER_CFM_CONFIG :
+ RTEXT_FILTER_CFM_STATUS;
+
+ if (rtnl_linkdump_req_filter(&rth, PF_BRIDGE, filt_mask) < 0) {
+ fprintf(stderr, "Cannot send dump request\n");
+ return -1;
+ }
+
+ new_json_obj(json);
+ ret = rtnl_dump_filter(&rth, cfm_show_filter, (void *)what);
+ delete_json_obj();
+ if (ret < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int cfm_show_cmd(int argc, char **argv)
+{
+ if (argc <= 0)
+ cfm_usage();
+
+ if (strcmp(*argv, "config") == 0)
+ return cfm_show(argc - 1, argv + 1, "config");
+ if (strcmp(*argv, "status") == 0)
+ return cfm_show(argc - 1, argv + 1, "status");
+
+ cfm_usage();
+ return -1;
+}
+
+int do_cfm(int argc, char **argv)
+{
+ if (argc <= 0)
+ cfm_usage();
+
+ if (strcmp(*argv, "mep") == 0)
+ return cfm_mep(argc - 1, argv + 1);
+ if (strcmp(*argv, "cc") == 0)
+ return cfm_cc(argc - 1, argv + 1);
+ if (strcmp(*argv, "show") == 0)
+ return cfm_show_cmd(argc - 1, argv + 1);
+ if (strcmp(*argv, "help") == 0)
+ cfm_usage();
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"bridge cfm help\".\n",
+ *argv);
+ return -1;
+}
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
index d2323521..13b02bb3 100644
--- a/man/man8/bridge.8
+++ b/man/man8/bridge.8
@@ -13,7 +13,7 @@ bridge \- show / manipulate bridge addresses and devices
.ti -8
.IR OBJECT " := { "
-.BR link " | " fdb " | " mdb " | " vlan " | " vni " | " monitor " }"
+.BR link " | " fdb " | " mdb " | " vlan " | " vni " | " cfm " | " monitor " }"
.sp
.ti -8
@@ -1666,6 +1666,200 @@ option, the command displays per-vni traffic statistics.
.BI dev " NAME"
shows vni filtering table associated with the vxlan device
+.SH bridge cfm - Connectivity Fault Management
+
+.B cfm
+objects are IEEE 802.1ag Maintenance End Points (MEPs) on a bridge. A MEP
+sends Continuity Check Messages (CCMs) out of one bridge port and tracks
+the CCMs it receives from a list of peer MEPs. All commands take the
+bridge device, not the port.
+
+The kernel only supports port domain, down MEPs.
+
+.SS bridge cfm mep add - create a MEP
+
+.B bridge cfm mep add bridge
+.I DEV
+.B instance
+.I INSTANCE
+.B port
+.I PORT
+.RB "[ " domain " { " port " | " vlan " } ]"
+.RB "[ " direction " { " down " | " up " } ]"
+
+.TP
+.BI instance " INSTANCE"
+a number that identifies the MEP on this bridge.
+
+.TP
+.BI port " PORT"
+the bridge port the MEP sits on. Only one MEP per port.
+
+.TP
+.BI domain " { port | vlan }"
+the MEP domain. Default is
+.BR port ,
+the kernel rejects
+.BR vlan .
+
+.TP
+.BI direction " { down | up }"
+the MEP direction. Default is
+.BR down ,
+the kernel rejects
+.BR up .
+
+.SS bridge cfm mep del - delete a MEP
+
+.B bridge cfm mep del bridge
+.I DEV
+.B instance
+.I INSTANCE
+
+.SS bridge cfm mep set - set the MEP address, level and ID
+
+.B bridge cfm mep set bridge
+.I DEV
+.B instance
+.I INSTANCE
+.B address
+.I LLADDR
+.B level
+.I LEVEL
+.B mepid
+.I MEPID
+
+.TP
+.BI address " LLADDR"
+source MAC address of the CCMs this MEP sends.
+
+.TP
+.BI level " LEVEL"
+the MD level, 0 to 7.
+
+.TP
+.BI mepid " MEPID"
+the MEP ID, 0 to 8191.
+
+.SS bridge cfm cc config - configure Continuity Check
+
+.B bridge cfm cc config bridge
+.I DEV
+.B instance
+.I INSTANCE
+.B enable
+.RB "{ " on " | " off " }"
+.B interval
+.I INTERVAL
+.B maid
+.I MAID
+
+.TP
+.BI enable " { on | off }"
+turn CCM reception and peer tracking on or off.
+
+.TP
+.BI interval " INTERVAL"
+the CCM interval, one of
+.BR 3ms3 ", " 10ms ", " 100ms ", " 1s ", " 10s ", " 1m ", " 10m .
+It is used both for sending and for the peer loss timeout. The kernel runs
+CCMs from a workqueue, so the real interval is rounded up to jiffies and
+can be noticeably longer than the name, most of all for
+.BR 3ms3 .
+
+.TP
+.BI maid " MAID"
+the Maintenance Association ID as a hex string, up to 48 bytes. Shorter
+strings are padded with zeros. Received CCMs must carry the same MAID.
+
+.SS bridge cfm cc peer - add or delete a peer MEP
+
+.B bridge cfm cc peer
+.RB "{ " add " | " del " }"
+.B bridge
+.I DEV
+.B instance
+.I INSTANCE
+.B mepid
+.I MEPID
+
+CCMs from MEP IDs not in this list are not tracked.
+
+.SS bridge cfm cc rdi - set the RDI bit
+
+.B bridge cfm cc rdi bridge
+.I DEV
+.B instance
+.I INSTANCE
+.B rdi
+.RB "{ " on " | " off " }"
+
+Sets or clears the Remote Defect Indication bit in the CCMs this MEP sends.
+
+.SS bridge cfm cc ccm-tx - send CCMs
+
+.B bridge cfm cc ccm-tx bridge
+.I DEV
+.B instance
+.I INSTANCE
+.B dmac
+.I LLADDR
+.B period
+.I PERIOD
+.RB "[ " seq " { " on " | " off " } ]"
+.RB "[ " iftlv " { " on " | " off " } ]"
+.RB "[ " iftlv-value
+.IR VALUE " ]"
+.RB "[ " porttlv " { " on " | " off " } ]"
+.RB "[ " porttlv-value
+.IR VALUE " ]"
+
+.TP
+.BI dmac " LLADDR"
+destination MAC address of the CCMs, usually
+.BR 01:80:c2:00:00:3X
+where X is the MD level.
+
+.TP
+.BI period " PERIOD"
+how many seconds to keep sending. The kernel stops by itself after that,
+so run the command again to keep going.
+.B 0
+stops sending.
+
+.TP
+.BI seq " { on | off }"
+put an incrementing sequence number in each CCM. Default
+.BR off ,
+which sends 0.
+
+.TP
+.BI iftlv " { on | off }"
+add an Interface Status TLV with value
+.BR iftlv-value .
+
+.TP
+.BI porttlv " { on | off }"
+add a Port Status TLV with value
+.BR porttlv-value .
+
+.SS bridge cfm show - show configuration or status
+
+.B bridge cfm show
+.RB "{ " config " | " status " }"
+.RB "[ " bridge
+.IR DEV " ]"
+
+.B config
+shows every MEP with its CC, RDI, CCM transmit settings and peers.
+.B status
+shows the MEP and peer state. The
+.BR seen ", " tlv_seen ", " seq_unexp_seen
+and the MEP
+.B _seen
+flags are cleared by the kernel each time status is read, so they mean
+"since the last read".
+
.SH bridge monitor - state monitoring
The
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH iproute2-next 0/2] bridge: add cfm command
2026-10-04 4:13 [PATCH iproute2-next 0/2] bridge: add cfm command Abdul Wasey
2026-10-04 4:13 ` [PATCH iproute2-next 1/2] uapi: import cfm_bridge.h Abdul Wasey
2026-10-04 4:13 ` [PATCH iproute2-next 2/2] bridge: add cfm command Abdul Wasey
@ 2026-10-06 10:42 ` Ido Schimmel
2026-10-06 13:44 ` Abdul Wasey
2 siblings, 1 reply; 5+ messages in thread
From: Ido Schimmel @ 2026-10-06 10:42 UTC (permalink / raw)
To: Abdul Wasey; +Cc: netdev, dsahern, stephen, razor, horatiu.vultur, bridge
On Sun, Oct 04, 2026 at 04:13:05AM +0000, Abdul Wasey wrote:
> The bridge has had 802.1ag CFM support since 5.11, but iproute2 never
> got a command for it. The only userspace for it today is Microchip's out
> of tree cfm tool. This series adds "bridge cfm".
[...]
> 6 files changed, 1321 insertions(+), 3 deletions(-)
Please elaborate on the motivation: What is your interest in CFM? Are
you using it in production? Does anyone? Who needs this in iproute2?
What is missing from Microchip's cfm tool?
Looking at the kernel log, since CFM was merged six years ago, no new
features were added and the only bug fixes are for issues found by
static checkers and LLMs with no reviews from the original authors.
There are also no selftests.
If nobody cares about CFM or uses it, then I would prefer to start
deprecating it instead of accumulating more code that needs to be
maintained and that nobody is going to use.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH iproute2-next 0/2] bridge: add cfm command
2026-10-06 10:42 ` [PATCH iproute2-next 0/2] " Ido Schimmel
@ 2026-10-06 13:44 ` Abdul Wasey
0 siblings, 0 replies; 5+ messages in thread
From: Abdul Wasey @ 2026-10-06 13:44 UTC (permalink / raw)
To: Ido Schimmel; +Cc: netdev, dsahern, stephen, razor, horatiu.vultur, bridge
On Tue, Oct 06, 2026 at 10:42:49AM +0000, Ido Schimmel wrote:
> Please elaborate on the motivation: What is your interest in CFM? Are
> you using it in production? Does anyone? Who needs this in iproute2?
> What is missing from Microchip's cfm tool?
>
> Looking at the kernel log, since CFM was merged six years ago, no new
> features were added and the only bug fixes are for issues found by
> static checkers and LLMs with no reviews from the original authors.
> There are also no selftests.
>
> If nobody cares about CFM or uses it, then I would prefer to start
> deprecating it instead of accumulating more code that needs to be
> maintained and that nobody is going to use.
Hi Ido,
Thanks for asking directly. Short answer: I don't run CFM in
production and I don't know anyone who does.
How I got here: I work on BFD (FRR's bfdd and an XDP BFD data plane)
and was looking at how other liveness protocols are handled on Linux.
CFM in the bridge had no iproute2 support, so I wrote it. I didn't
check first whether anyone needed it, and I should have.
Testing it, I found the bridge CFM does not keep its own timing at the
short intervals: with a 3.3 ms CCM interval it sends every ~5 ms and
declares loss after about 8 intervals instead of 3.5, because the
timers are delayed works in jiffies. The ccm-tx period also overflows
a u32 above 4294 s, and interval "none" with CC enabled rearms the work
with no delay. I think that fits what you describe: anyone running
the short intervals would have hit the first one.
Microchip's cfm tool covers configuration and show; what it does not
have is being packaged anywhere and a monitor for events. That alone
is not a strong reason to carry 1300 lines in iproute2.
So I'm fine with dropping this series. Horatiu, do you know of anyone
using bridge CFM? If nobody does, I'm also fine with deprecating it,
and I can help with that if you want.
The same goes for my net-next series "net: bridge: cfm: notify
userspace on CFM config changes", which only makes sense if CFM
stays; I'll leave it until this is settled.
Thanks,
Abdul Wasey
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-06 13:44 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-04 4:13 [PATCH iproute2-next 0/2] bridge: add cfm command Abdul Wasey
2026-10-04 4:13 ` [PATCH iproute2-next 1/2] uapi: import cfm_bridge.h Abdul Wasey
2026-10-04 4:13 ` [PATCH iproute2-next 2/2] bridge: add cfm command Abdul Wasey
2026-10-06 10:42 ` [PATCH iproute2-next 0/2] " Ido Schimmel
2026-10-06 13:44 ` Abdul Wasey
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox