Commit b6d0425b authored by Henrik Bjoernlund's avatar Henrik Bjoernlund Committed by Jakub Kicinski
Browse files

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: default avatarHenrik Bjoernlund <henrik.bjoernlund@microchip.com>
Reviewed-by: default avatarHoratiu Vultur <horatiu.vultur@microchip.com>
Acked-by: default avatarNikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent e77824d8
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -138,6 +138,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));
@@ -288,6 +295,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);
@@ -305,6 +313,13 @@ static void ccm_rx_work_expired(struct work_struct *work)
		 * CCM defect detected
		 */
		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();
	}
}

@@ -430,6 +445,9 @@ static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb)
		if (peer_mep->cc_status.ccm_defect) {
			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);
		}
@@ -799,6 +817,36 @@ save:
	return 0;
}

int br_cfm_mep_count(struct net_bridge *br, u32 *count)
{
	struct br_cfm_mep *mep;

	*count = 0;

	rcu_read_lock();
	hlist_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();
	hlist_for_each_entry_rcu(mep, &br->mep_list, head)
		hlist_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 !hlist_empty(&br->mep_list);
+16 −9
Original line number Diff line number Diff line
@@ -613,7 +613,9 @@ nla_info_failure:
	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 br_cfm_peer_mep *peer_mep;
	struct br_cfm_mep *mep;
@@ -643,10 +645,13 @@ int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br)
				mep->status.rx_level_low_seen))
			goto nla_put_failure;

		/* Only clear if this is a GETLINK */
		if (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);

@@ -700,10 +705,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;

			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);
		}
+63 −10
Original line number Diff line number Diff line
@@ -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,49 @@ 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 */
	br_cfm_mep_count(br, &num_cfm_mep_infos);
	br_cfm_peer_mep_count(br, &num_cfm_peer_mep_infos);

	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 +423,8 @@ nla_put_failure:
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 nlattr *af = NULL;
@@ -499,7 +545,7 @@ static int br_fill_ifinfo(struct sk_buff *skb,

		if (filter_mask & RTEXT_FILTER_CFM_STATUS) {
			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)
				goto nla_put_failure;
@@ -519,11 +565,9 @@ nla_put_failure:
	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;
@@ -548,7 +592,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);
@@ -561,6 +605,15 @@ errout:
	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
 */
@@ -577,7 +630,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,
+20 −2
Original line number Diff line number Diff line
@@ -1466,7 +1466,11 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p,
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);
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,
@@ -1490,7 +1494,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;
}
@@ -1502,6 +1518,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);