Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bridge: cfm: Kernel space implementation of CFM. CCM frame RX added.

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

Functionality is extended with CCM frame reception.
The MEP instance now contains CCM based status information.
Most important is the CCM defect status indicating if correct
CCM frames are received with the expected interval.

Signed-off-by: Henrik Bjoernlund <henrik.bjoernlund@microchip.com>
Reviewed-by: Horatiu Vultur <horatiu.vultur@microchip.com>
Acked-by: Nikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Henrik Bjoernlund and committed by
Jakub Kicinski
dc32cbb3 a806ad8e

+305
+4
include/uapi/linux/cfm_bridge.h
··· 20 20 CFM_IF_STATUS_TLV_LENGTH) 21 21 #define CFM_FRAME_PRIO 7 22 22 #define CFM_CCM_TLV_OFFSET 70 23 + #define CFM_CCM_PDU_MAID_OFFSET 10 24 + #define CFM_CCM_PDU_MEPID_OFFSET 8 25 + #define CFM_CCM_PDU_SEQNR_OFFSET 4 26 + #define CFM_CCM_PDU_TLV_OFFSET 74 23 27 #define CFM_CCM_ITU_RESERVED_SIZE 16 24 28 25 29 struct br_cfm_common_hdr {
+269
net/bridge/br_cfm.c
··· 101 101 return 0; 102 102 } 103 103 104 + /* Convert the CCM PDU value to interval on interface. */ 105 + static u32 pdu_to_interval(u32 value) 106 + { 107 + switch (value) { 108 + case 0: 109 + return BR_CFM_CCM_INTERVAL_NONE; 110 + case 1: 111 + return BR_CFM_CCM_INTERVAL_3_3_MS; 112 + case 2: 113 + return BR_CFM_CCM_INTERVAL_10_MS; 114 + case 3: 115 + return BR_CFM_CCM_INTERVAL_100_MS; 116 + case 4: 117 + return BR_CFM_CCM_INTERVAL_1_SEC; 118 + case 5: 119 + return BR_CFM_CCM_INTERVAL_10_SEC; 120 + case 6: 121 + return BR_CFM_CCM_INTERVAL_1_MIN; 122 + case 7: 123 + return BR_CFM_CCM_INTERVAL_10_MIN; 124 + } 125 + return BR_CFM_CCM_INTERVAL_NONE; 126 + } 127 + 128 + static void ccm_rx_timer_start(struct br_cfm_peer_mep *peer_mep) 129 + { 130 + u32 interval_us; 131 + 132 + interval_us = interval_to_us(peer_mep->mep->cc_config.exp_interval); 133 + /* Function ccm_rx_dwork must be called with 1/4 134 + * of the configured CC 'expected_interval' 135 + * in order to detect CCM defect after 3.25 interval. 136 + */ 137 + queue_delayed_work(system_wq, &peer_mep->ccm_rx_dwork, 138 + usecs_to_jiffies(interval_us / 4)); 139 + } 140 + 141 + static void cc_peer_enable(struct br_cfm_peer_mep *peer_mep) 142 + { 143 + memset(&peer_mep->cc_status, 0, sizeof(peer_mep->cc_status)); 144 + peer_mep->ccm_rx_count_miss = 0; 145 + 146 + ccm_rx_timer_start(peer_mep); 147 + } 148 + 149 + static void cc_peer_disable(struct br_cfm_peer_mep *peer_mep) 150 + { 151 + cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork); 152 + } 153 + 104 154 static struct sk_buff *ccm_frame_build(struct br_cfm_mep *mep, 105 155 const struct br_cfm_cc_ccm_tx_info *const tx_info) 106 156 ··· 282 232 usecs_to_jiffies(interval_us)); 283 233 } 284 234 235 + /* This function is called with 1/4 of the configured CC 'expected_interval' 236 + * in order to detect CCM defect after 3.25 interval. 237 + */ 238 + static void ccm_rx_work_expired(struct work_struct *work) 239 + { 240 + struct br_cfm_peer_mep *peer_mep; 241 + struct delayed_work *del_work; 242 + 243 + del_work = to_delayed_work(work); 244 + peer_mep = container_of(del_work, struct br_cfm_peer_mep, ccm_rx_dwork); 245 + 246 + /* After 13 counts (4 * 3,25) then 3.25 intervals are expired */ 247 + if (peer_mep->ccm_rx_count_miss < 13) { 248 + /* 3.25 intervals are NOT expired without CCM reception */ 249 + peer_mep->ccm_rx_count_miss++; 250 + 251 + /* Start timer again */ 252 + ccm_rx_timer_start(peer_mep); 253 + } else { 254 + /* 3.25 intervals are expired without CCM reception. 255 + * CCM defect detected 256 + */ 257 + peer_mep->cc_status.ccm_defect = true; 258 + } 259 + } 260 + 261 + static u32 ccm_tlv_extract(struct sk_buff *skb, u32 index, 262 + struct br_cfm_peer_mep *peer_mep) 263 + { 264 + __be32 *s_tlv; 265 + __be32 _s_tlv; 266 + u32 h_s_tlv; 267 + u8 *e_tlv; 268 + u8 _e_tlv; 269 + 270 + e_tlv = skb_header_pointer(skb, index, sizeof(_e_tlv), &_e_tlv); 271 + if (!e_tlv) 272 + return 0; 273 + 274 + /* TLV is present - get the status TLV */ 275 + s_tlv = skb_header_pointer(skb, 276 + index, 277 + sizeof(_s_tlv), &_s_tlv); 278 + if (!s_tlv) 279 + return 0; 280 + 281 + h_s_tlv = ntohl(*s_tlv); 282 + if ((h_s_tlv >> 24) == CFM_IF_STATUS_TLV_TYPE) { 283 + /* Interface status TLV */ 284 + peer_mep->cc_status.tlv_seen = true; 285 + peer_mep->cc_status.if_tlv_value = (h_s_tlv & 0xFF); 286 + } 287 + 288 + if ((h_s_tlv >> 24) == CFM_PORT_STATUS_TLV_TYPE) { 289 + /* Port status TLV */ 290 + peer_mep->cc_status.tlv_seen = true; 291 + peer_mep->cc_status.port_tlv_value = (h_s_tlv & 0xFF); 292 + } 293 + 294 + /* The Sender ID TLV is not handled */ 295 + /* The Organization-Specific TLV is not handled */ 296 + 297 + /* Return the length of this tlv. 298 + * This is the length of the value field plus 3 bytes for size of type 299 + * field and length field 300 + */ 301 + return ((h_s_tlv >> 8) & 0xFFFF) + 3; 302 + } 303 + 304 + /* note: already called with rcu_read_lock */ 305 + static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb) 306 + { 307 + u32 mdlevel, interval, size, index, max; 308 + const struct br_cfm_common_hdr *hdr; 309 + struct br_cfm_peer_mep *peer_mep; 310 + const struct br_cfm_maid *maid; 311 + struct br_cfm_common_hdr _hdr; 312 + struct br_cfm_maid _maid; 313 + struct br_cfm_mep *mep; 314 + struct net_bridge *br; 315 + __be32 *snumber; 316 + __be32 _snumber; 317 + __be16 *mepid; 318 + __be16 _mepid; 319 + 320 + if (port->state == BR_STATE_DISABLED) 321 + return 0; 322 + 323 + hdr = skb_header_pointer(skb, 0, sizeof(_hdr), &_hdr); 324 + if (!hdr) 325 + return 1; 326 + 327 + br = port->br; 328 + mep = br_mep_find_ifindex(br, port->dev->ifindex); 329 + if (unlikely(!mep)) 330 + /* No MEP on this port - must be forwarded */ 331 + return 0; 332 + 333 + mdlevel = hdr->mdlevel_version >> 5; 334 + if (mdlevel > mep->config.mdlevel) 335 + /* The level is above this MEP level - must be forwarded */ 336 + return 0; 337 + 338 + if ((hdr->mdlevel_version & 0x1F) != 0) { 339 + /* Invalid version */ 340 + mep->status.version_unexp_seen = true; 341 + return 1; 342 + } 343 + 344 + if (mdlevel < mep->config.mdlevel) { 345 + /* The level is below this MEP level */ 346 + mep->status.rx_level_low_seen = true; 347 + return 1; 348 + } 349 + 350 + if (hdr->opcode == BR_CFM_OPCODE_CCM) { 351 + /* CCM PDU received. */ 352 + /* MA ID is after common header + sequence number + MEP ID */ 353 + maid = skb_header_pointer(skb, 354 + CFM_CCM_PDU_MAID_OFFSET, 355 + sizeof(_maid), &_maid); 356 + if (!maid) 357 + return 1; 358 + if (memcmp(maid->data, mep->cc_config.exp_maid.data, 359 + sizeof(maid->data))) 360 + /* MA ID not as expected */ 361 + return 1; 362 + 363 + /* MEP ID is after common header + sequence number */ 364 + mepid = skb_header_pointer(skb, 365 + CFM_CCM_PDU_MEPID_OFFSET, 366 + sizeof(_mepid), &_mepid); 367 + if (!mepid) 368 + return 1; 369 + peer_mep = br_peer_mep_find(mep, (u32)ntohs(*mepid)); 370 + if (!peer_mep) 371 + return 1; 372 + 373 + /* Interval is in common header flags */ 374 + interval = hdr->flags & 0x07; 375 + if (mep->cc_config.exp_interval != pdu_to_interval(interval)) 376 + /* Interval not as expected */ 377 + return 1; 378 + 379 + /* A valid CCM frame is received */ 380 + if (peer_mep->cc_status.ccm_defect) { 381 + peer_mep->cc_status.ccm_defect = false; 382 + 383 + /* Start CCM RX timer */ 384 + ccm_rx_timer_start(peer_mep); 385 + } 386 + 387 + peer_mep->cc_status.seen = true; 388 + peer_mep->ccm_rx_count_miss = 0; 389 + 390 + /* RDI is in common header flags */ 391 + peer_mep->cc_status.rdi = (hdr->flags & 0x80) ? true : false; 392 + 393 + /* Sequence number is after common header */ 394 + snumber = skb_header_pointer(skb, 395 + CFM_CCM_PDU_SEQNR_OFFSET, 396 + sizeof(_snumber), &_snumber); 397 + if (!snumber) 398 + return 1; 399 + if (ntohl(*snumber) != (mep->ccm_rx_snumber + 1)) 400 + /* Unexpected sequence number */ 401 + peer_mep->cc_status.seq_unexp_seen = true; 402 + 403 + mep->ccm_rx_snumber = ntohl(*snumber); 404 + 405 + /* TLV end is after common header + sequence number + MEP ID + 406 + * MA ID + ITU reserved 407 + */ 408 + index = CFM_CCM_PDU_TLV_OFFSET; 409 + max = 0; 410 + do { /* Handle all TLVs */ 411 + size = ccm_tlv_extract(skb, index, peer_mep); 412 + index += size; 413 + max += 1; 414 + } while (size != 0 && max < 4); /* Max four TLVs possible */ 415 + 416 + return 1; 417 + } 418 + 419 + mep->status.opcode_unexp_seen = true; 420 + 421 + return 1; 422 + } 423 + 424 + static struct br_frame_type cfm_frame_type __read_mostly = { 425 + .type = cpu_to_be16(ETH_P_CFM), 426 + .frame_handler = br_cfm_frame_rx, 427 + }; 428 + 285 429 int br_cfm_mep_create(struct net_bridge *br, 286 430 const u32 instance, 287 431 struct br_cfm_mep_create *const create, ··· 540 296 INIT_HLIST_HEAD(&mep->peer_mep_list); 541 297 INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired); 542 298 299 + if (hlist_empty(&br->mep_list)) 300 + br_add_frame(br, &cfm_frame_type); 301 + 543 302 hlist_add_tail_rcu(&mep->head, &br->mep_list); 544 303 545 304 return 0; ··· 558 311 559 312 /* Empty and free peer MEP list */ 560 313 hlist_for_each_entry_safe(peer_mep, n_store, &mep->peer_mep_list, head) { 314 + cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork); 561 315 hlist_del_rcu(&peer_mep->head); 562 316 kfree_rcu(peer_mep, rcu); 563 317 } ··· 568 320 RCU_INIT_POINTER(mep->b_port, NULL); 569 321 hlist_del_rcu(&mep->head); 570 322 kfree_rcu(mep, rcu); 323 + 324 + if (hlist_empty(&br->mep_list)) 325 + br_del_frame(br, &cfm_frame_type); 571 326 } 572 327 573 328 int br_cfm_mep_delete(struct net_bridge *br, ··· 619 368 const struct br_cfm_cc_config *const config, 620 369 struct netlink_ext_ack *extack) 621 370 { 371 + struct br_cfm_peer_mep *peer_mep; 622 372 struct br_cfm_mep *mep; 623 373 624 374 ASSERT_RTNL(); ··· 635 383 if (memcmp(config, &mep->cc_config, sizeof(*config)) == 0) 636 384 return 0; 637 385 386 + if (config->enable && !mep->cc_config.enable) 387 + /* CC is enabled */ 388 + hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head) 389 + cc_peer_enable(peer_mep); 390 + 391 + if (!config->enable && mep->cc_config.enable) 392 + /* CC is disabled */ 393 + hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head) 394 + cc_peer_disable(peer_mep); 395 + 638 396 mep->cc_config = *config; 397 + mep->ccm_rx_snumber = 0; 639 398 mep->ccm_tx_snumber = 1; 640 399 641 400 return 0; ··· 681 418 682 419 peer_mep->mepid = mepid; 683 420 peer_mep->mep = mep; 421 + INIT_DELAYED_WORK(&peer_mep->ccm_rx_dwork, ccm_rx_work_expired); 422 + 423 + if (mep->cc_config.enable) 424 + cc_peer_enable(peer_mep); 684 425 685 426 hlist_add_tail_rcu(&peer_mep->head, &mep->peer_mep_list); 686 427 ··· 713 446 "Peer MEP-ID does not exists"); 714 447 return -ENOENT; 715 448 } 449 + 450 + cc_peer_disable(peer_mep); 716 451 717 452 hlist_del_rcu(&peer_mep->head); 718 453 kfree_rcu(peer_mep, rcu);
+32
net/bridge/br_private_cfm.h
··· 43 43 /* Expected received CCM PDU interval. */ 44 44 /* Transmitting CCM PDU interval when CCM tx is enabled. */ 45 45 enum br_cfm_ccm_interval exp_interval; 46 + 47 + bool enable; /* Enable/disable CCM PDU handling */ 46 48 }; 47 49 48 50 int br_cfm_cc_config_set(struct net_bridge *br, ··· 89 87 const struct br_cfm_cc_ccm_tx_info *const tx_info, 90 88 struct netlink_ext_ack *extack); 91 89 90 + struct br_cfm_mep_status { 91 + /* Indications that an OAM PDU has been seen. */ 92 + bool opcode_unexp_seen; /* RX of OAM PDU with unexpected opcode */ 93 + bool version_unexp_seen; /* RX of OAM PDU with unexpected version */ 94 + bool rx_level_low_seen; /* Rx of OAM PDU with level low */ 95 + }; 96 + 97 + struct br_cfm_cc_peer_status { 98 + /* This CCM related status is based on the latest received CCM PDU. */ 99 + u8 port_tlv_value; /* Port Status TLV value */ 100 + u8 if_tlv_value; /* Interface Status TLV value */ 101 + 102 + /* CCM has not been received for 3.25 intervals */ 103 + u8 ccm_defect:1; 104 + 105 + /* (RDI == 1) for last received CCM PDU */ 106 + u8 rdi:1; 107 + 108 + /* Indications that a CCM PDU has been seen. */ 109 + u8 seen:1; /* CCM PDU received */ 110 + u8 tlv_seen:1; /* CCM PDU with TLV received */ 111 + /* CCM PDU with unexpected sequence number received */ 112 + u8 seq_unexp_seen:1; 113 + }; 114 + 92 115 struct br_cfm_mep { 93 116 /* list header of MEP instances */ 94 117 struct hlist_node head; ··· 128 101 unsigned long ccm_tx_end; 129 102 struct delayed_work ccm_tx_dwork; 130 103 u32 ccm_tx_snumber; 104 + u32 ccm_rx_snumber; 105 + struct br_cfm_mep_status status; 131 106 bool rdi; 132 107 struct rcu_head rcu; 133 108 }; ··· 137 108 struct br_cfm_peer_mep { 138 109 struct hlist_node head; 139 110 struct br_cfm_mep *mep; 111 + struct delayed_work ccm_rx_dwork; 140 112 u32 mepid; 113 + struct br_cfm_cc_peer_status cc_status; 114 + u32 ccm_rx_count_miss; 141 115 struct rcu_head rcu; 142 116 }; 143 117