forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
867 lines
20 KiB
867 lines
20 KiB
// 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 *mep; |
|
|
|
hlist_for_each_entry(mep, &br->mep_list, head) |
|
if (mep->instance == instance) |
|
return mep; |
|
|
|
return NULL; |
|
} |
|
|
|
static struct br_cfm_mep *br_mep_find_ifindex(struct net_bridge *br, |
|
u32 ifindex) |
|
{ |
|
struct br_cfm_mep *mep; |
|
|
|
hlist_for_each_entry_rcu(mep, &br->mep_list, head, |
|
lockdep_rtnl_is_held()) |
|
if (mep->create.ifindex == ifindex) |
|
return mep; |
|
|
|
return NULL; |
|
} |
|
|
|
static struct br_cfm_peer_mep *br_peer_mep_find(struct br_cfm_mep *mep, |
|
u32 mepid) |
|
{ |
|
struct br_cfm_peer_mep *peer_mep; |
|
|
|
hlist_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head, |
|
lockdep_rtnl_is_held()) |
|
if (peer_mep->mepid == mepid) |
|
return peer_mep; |
|
|
|
return NULL; |
|
} |
|
|
|
static struct net_bridge_port *br_mep_get_port(struct net_bridge *br, |
|
u32 ifindex) |
|
{ |
|
struct net_bridge_port *port; |
|
|
|
list_for_each_entry(port, &br->port_list, list) |
|
if (port->dev->ifindex == ifindex) |
|
return port; |
|
|
|
return NULL; |
|
} |
|
|
|
/* 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 br_cfm_notify(int event, const struct net_bridge_port *port) |
|
{ |
|
u32 filter = RTEXT_FILTER_CFM_STATUS; |
|
|
|
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)); |
|
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; |
|
|
|
skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH); |
|
if (!skb) |
|
return NULL; |
|
|
|
rcu_read_lock(); |
|
b_port = rcu_dereference(mep->b_port); |
|
if (!b_port) { |
|
kfree_skb(skb); |
|
rcu_read_unlock(); |
|
return NULL; |
|
} |
|
skb->dev = b_port->dev; |
|
rcu_read_unlock(); |
|
/* The device cannot be deleted until the work_queue functions has |
|
* completed. This function is called from ccm_tx_work_expired() |
|
* that is a work_queue functions. |
|
*/ |
|
|
|
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 = BR_CFM_OPCODE_CCM; |
|
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; |
|
} |
|
|
|
mepid = skb_put(skb, sizeof(*mepid)); |
|
*mepid = cpu_to_be16((u16)mep->config.mepid); |
|
|
|
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 net_bridge_port *b_port; |
|
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 */ |
|
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(); |
|
} |
|
} |
|
|
|
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->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 */ |
|
br_cfm_notify(RTM_NEWLINK, port); |
|
|
|
/* 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), |
|
.frame_handler = br_cfm_frame_rx, |
|
}; |
|
|
|
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; |
|
|
|
ASSERT_RTNL(); |
|
|
|
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; |
|
} |
|
p = br_mep_get_port(br, create->ifindex); |
|
if (!p) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"Port is not related to bridge"); |
|
return -EINVAL; |
|
} |
|
mep = br_mep_find(br, instance); |
|
if (mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"MEP instance already exists"); |
|
return -EEXIST; |
|
} |
|
|
|
/* 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; |
|
|
|
mep->create = *create; |
|
mep->instance = instance; |
|
rcu_assign_pointer(mep->b_port, p); |
|
|
|
INIT_HLIST_HEAD(&mep->peer_mep_list); |
|
INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired); |
|
|
|
if (hlist_empty(&br->mep_list)) |
|
br_add_frame(br, &cfm_frame_type); |
|
|
|
hlist_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; |
|
struct hlist_node *n_store; |
|
|
|
ASSERT_RTNL(); |
|
|
|
/* Empty and free peer MEP list */ |
|
hlist_for_each_entry_safe(peer_mep, n_store, &mep->peer_mep_list, head) { |
|
cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork); |
|
hlist_del_rcu(&peer_mep->head); |
|
kfree_rcu(peer_mep, rcu); |
|
} |
|
|
|
cancel_delayed_work_sync(&mep->ccm_tx_dwork); |
|
|
|
RCU_INIT_POINTER(mep->b_port, NULL); |
|
hlist_del_rcu(&mep->head); |
|
kfree_rcu(mep, rcu); |
|
|
|
if (hlist_empty(&br->mep_list)) |
|
br_del_frame(br, &cfm_frame_type); |
|
} |
|
|
|
int br_cfm_mep_delete(struct net_bridge *br, |
|
const u32 instance, |
|
struct netlink_ext_ack *extack) |
|
{ |
|
struct br_cfm_mep *mep; |
|
|
|
ASSERT_RTNL(); |
|
|
|
mep = br_mep_find(br, instance); |
|
if (!mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"MEP instance does not exists"); |
|
return -ENOENT; |
|
} |
|
|
|
mep_delete_implementation(br, mep); |
|
|
|
return 0; |
|
} |
|
|
|
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; |
|
|
|
ASSERT_RTNL(); |
|
|
|
mep = br_mep_find(br, instance); |
|
if (!mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"MEP instance does not exists"); |
|
return -ENOENT; |
|
} |
|
|
|
mep->config = *config; |
|
|
|
return 0; |
|
} |
|
|
|
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; |
|
|
|
ASSERT_RTNL(); |
|
|
|
mep = br_mep_find(br, instance); |
|
if (!mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"MEP instance does not exists"); |
|
return -ENOENT; |
|
} |
|
|
|
/* 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 */ |
|
hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head) |
|
cc_peer_enable(peer_mep); |
|
|
|
if (!config->enable && mep->cc_config.enable) |
|
/* CC is disabled */ |
|
hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head) |
|
cc_peer_disable(peer_mep); |
|
|
|
mep->cc_config = *config; |
|
mep->ccm_rx_snumber = 0; |
|
mep->ccm_tx_snumber = 1; |
|
|
|
return 0; |
|
} |
|
|
|
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; |
|
|
|
ASSERT_RTNL(); |
|
|
|
mep = br_mep_find(br, instance); |
|
if (!mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"MEP instance does not exists"); |
|
return -ENOENT; |
|
} |
|
|
|
peer_mep = br_peer_mep_find(mep, mepid); |
|
if (peer_mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"Peer MEP-ID already exists"); |
|
return -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_peer_enable(peer_mep); |
|
|
|
hlist_add_tail_rcu(&peer_mep->head, &mep->peer_mep_list); |
|
|
|
return 0; |
|
} |
|
|
|
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; |
|
|
|
ASSERT_RTNL(); |
|
|
|
mep = br_mep_find(br, instance); |
|
if (!mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"MEP instance does not exists"); |
|
return -ENOENT; |
|
} |
|
|
|
peer_mep = br_peer_mep_find(mep, mepid); |
|
if (!peer_mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"Peer MEP-ID does not exists"); |
|
return -ENOENT; |
|
} |
|
|
|
cc_peer_disable(peer_mep); |
|
|
|
hlist_del_rcu(&peer_mep->head); |
|
kfree_rcu(peer_mep, rcu); |
|
|
|
return 0; |
|
} |
|
|
|
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; |
|
|
|
ASSERT_RTNL(); |
|
|
|
mep = br_mep_find(br, instance); |
|
if (!mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"MEP instance does not exists"); |
|
return -ENOENT; |
|
} |
|
|
|
mep->rdi = rdi; |
|
|
|
return 0; |
|
} |
|
|
|
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; |
|
|
|
ASSERT_RTNL(); |
|
|
|
mep = br_mep_find(br, instance); |
|
if (!mep) { |
|
NL_SET_ERR_MSG_MOD(extack, |
|
"MEP instance does not exists"); |
|
return -ENOENT; |
|
} |
|
|
|
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) { |
|
cancel_delayed_work_sync(&mep->ccm_tx_dwork); |
|
goto save; |
|
} |
|
|
|
/* Start delayed work to transmit CCM frames. It is done with zero delay |
|
* to send first frame immediately |
|
*/ |
|
mep->ccm_tx_end = jiffies + usecs_to_jiffies(tx_info->period * 1000000); |
|
queue_delayed_work(system_wq, &mep->ccm_tx_dwork, 0); |
|
|
|
save: |
|
mep->cc_ccm_tx_info = *tx_info; |
|
|
|
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); |
|
} |
|
|
|
/* Deletes the CFM instances on a specific bridge port |
|
*/ |
|
void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *port) |
|
{ |
|
struct hlist_node *n_store; |
|
struct br_cfm_mep *mep; |
|
|
|
ASSERT_RTNL(); |
|
|
|
hlist_for_each_entry_safe(mep, n_store, &br->mep_list, head) |
|
if (mep->create.ifindex == port->dev->ifindex) |
|
mep_delete_implementation(br, mep); |
|
}
|
|
|