STP与LLC协议接收路径

网桥子系统 专栏收录该内容
17 篇文章 0 订阅

网桥初始化函数br_init使用stp_proto_register注册stp协议,此处注册的stp_proto结构体仅包含一个rcv回调函数:br_stp_rcv。

static int __init br_init(void)
{
    err = stp_proto_register(&br_stp_proto);
    if (err < 0) {
        pr_err("bridge: can't register sap for STP\n");
        return err;
    }
}
static const struct stp_proto br_stp_proto = {
    .rcv    = br_stp_rcv,
};

另外,内核中VLAN相关的通用属性注册协议GARP(Generic Attribute Registration Protocol),也运行在802.2 LLC之上。其在注册时指定了二层组播地址:GARP_GVRP_ADDRESS。

int garp_register_application(struct garp_application *appl)
{   
    appl->proto.rcv = garp_pdu_rcv;
    appl->proto.data = appl;
    return stp_proto_register(&appl->proto);
}

#define GARP_GVRP_ADDRESS   { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x21 }

static struct garp_application vlan_gvrp_app __read_mostly = {
    .proto.group_address    = GARP_GVRP_ADDRESS,
};
int __init vlan_gvrp_init(void)
{
    return garp_register_application(&vlan_gvrp_app);
}

由于stp协议使用llc(Local-Link Control)头封装,其指定了DSAP(Destination Service Access Point)的值LLC_SAP_BSPAN(42),用来标识STP BPDU报文。无论是STP或者GARP都使用统一的接收函数stp_pdu_rcv处理报文。对于stp,内核将注册的stp_proto结构体赋予了全局指针stp_proto。对于GARP,内核使用其二层组播地址的最后一个字节与GARP_ADDR_MIN之差值为索引,将其注册的stp_proto结构赋予到了索引指定的garp_protos数组中。

#define LLC_SAP_BSPAN   0x42        /* Bridge Spanning Tree Proto   */

int stp_proto_register(const struct stp_proto *proto)
{
    if (sap_registered++ == 0) {
        sap = llc_sap_open(LLC_SAP_BSPAN, stp_pdu_rcv);
        if (!sap) {
            err = -ENOMEM;
            goto out;
        }
    }
    if (is_zero_ether_addr(proto->group_address))
        rcu_assign_pointer(stp_proto, proto);
    else
        rcu_assign_pointer(garp_protos[proto->group_address[5] - GARP_ADDR_MIN], proto);
}

由宏GARP_ADDR_MIN和GARP_ADDR_MAX的定义可知,内核目前支持处理16个不同的二层组播地址。

/* 01:80:c2:00:00:20 - 01:80:c2:00:00:2F */
#define GARP_ADDR_MIN   0x20
#define GARP_ADDR_MAX   0x2F
#define GARP_ADDR_RANGE (GARP_ADDR_MAX - GARP_ADDR_MIN)

static const struct stp_proto __rcu *garp_protos[GARP_ADDR_RANGE + 1] __read_mostly;
static const struct stp_proto __rcu *stp_proto __read_mostly;

如果接收到的数据帧的以太网头部目的地址的最后一个字节位于:[GARP_ADDR_MIN, GARP_ADDR_MAX]范围之后,说明是正确的GARP报文。之后,根据最后一个字节与GARP_ADDR_MIN差值找到garp_protos数组中注册的具体stp_proto结构,调用其中的rcv函数指针。

对于STP,其SSAP和DSAP都等于LLC_SAP_BSPAN,注册的stp_proto结构保存在stp_proto全局指针中,调用其中的rcv指针,即br_stp_proto结构中的初始化的br_stp_rcv处理函数。

static int stp_pdu_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
    const struct ethhdr *eh = eth_hdr(skb);
    const struct llc_pdu_un *pdu = llc_pdu_un_hdr(skb);
    const struct stp_proto *proto;

    if (pdu->ssap != LLC_SAP_BSPAN || pdu->dsap != LLC_SAP_BSPAN || pdu->ctrl_1 != LLC_PDU_TYPE_U)
        goto err;

    if (eh->h_dest[5] >= GARP_ADDR_MIN && eh->h_dest[5] <= GARP_ADDR_MAX) {
        proto = rcu_dereference(garp_protos[eh->h_dest[5] - GARP_ADDR_MIN]);
        if (proto && !ether_addr_equal(eh->h_dest, proto->group_address))
            goto err;
    } else
        proto = rcu_dereference(stp_proto);

    if (!proto) goto err;
    proto->rcv(proto, skb, dev);
}

以上我们注意到stp_pdu_rcv接收函数,是在stp_proto_register第一次被调用的时候,由llc_sap_open函数注册到LLC系统中的。如下代码所示,新分配一个llc_sap结构,连接到全局的llc_sap_list链表中。另外SNAP/LLC协议也会使用llc_sap_open注册协议处理函数,此协议由AppleTalk使用。

struct llc_sap *llc_sap_open(unsigned char lsap, int (*func)(struct sk_buff *skb,
                     struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev))
{
    struct llc_sap *sap = NULL;

    spin_lock_bh(&llc_sap_list_lock);
    if (__llc_sap_find(lsap)) /* SAP already exists */
        goto out;
    sap = llc_sap_alloc();
    if (!sap)
        goto out;
    sap->laddr.lsap = lsap;
    sap->rcv_func   = func;
    list_add_tail_rcu(&sap->node, &llc_sap_list);
}

内核的llc子系统在初始化时,注册了对类型为ETH_P_802_2的数据帧的处理函数llc_rcv。llc_init函数通过dev_add_pack注册到网络系统的接收流程中。

static struct packet_type llc_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_802_2),
    .func = llc_rcv,
};
static int __init llc_init(void)
{
    dev_add_pack(&llc_packet_type);
    dev_add_pack(&llc_tr_packet_type);
    return 0;
}

如下函数eth_type_trans用来判定数据帧的协议类型,对于协议为802.3,直接返回其头部中的h_proto字段值,如0x0806(ETH_P_ARP)或者0x0800(ETH_P_IP)。另外如果dsap和ssap字段都为0xff的话,由于是不合法的802.2 SSAP/DSAP值,内核认为是 ETH_P_802_3协议。最后,排除以上情况的数据帧就是ETH_P_802_2,其由以上注册的llc_rcv函数接收处理。

__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
    const unsigned short *sap;
    const struct ethhdr *eth;

    if (likely(eth_proto_is_802_3(eth->h_proto)))
        return eth->h_proto;

    sap = skb_header_pointer(skb, 0, sizeof(*sap), &_service_access_point);
    if (sap && *sap == 0xFFFF)
        return htons(ETH_P_802_3);

    /* Real 802.2 LLC */
    return htons(ETH_P_802_2);
}

802.2协议的接收函数llc_rcv。其首先使用数据帧中的dsap值在全局的llc_sap_list注册链表中查找匹配的llc_sap接口,此通过llc_sap_find函数完成。对于dsap等于LLC_SAP_BSPAN的数据包,将会调用STP的处理函数stp_pdu_rcv。SNAP/LLC协议注册时使用的DSAP为0xAA,其处理函数为snap_rcv。

int llc_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
    pdu = llc_pdu_sn_hdr(skb);
    if (unlikely(!pdu->dsap)) /* NULL DSAP, refer to station */
           goto handle_station;
    sap = llc_sap_find(pdu->dsap);
    if (unlikely(!sap)) {    /* unknown SAP */
        goto drop;
    }

    rcv = rcu_dereference(sap->rcv_func);
    dest = llc_pdu_type(skb);
    sap_handler = dest ? ACCESS_ONCE(llc_type_handlers[dest - 1]) : NULL;
    if (unlikely(!sap_handler)) {
        if (rcv)
            rcv(skb, dev, pt, orig_dev);
        else
            kfree_skb(skb);
    } else {
        if (rcv) {
            struct sk_buff *cskb = skb_clone(skb, GFP_ATOMIC);
            if (cskb)
                rcv(cskb, dev, pt, orig_dev);
        }
        sap_handler(sap, skb);
    }
}

除了以上的处理之外,llc_rcv函数还要处理LLC Type 1(无连接)和LLC Type 2(面向连接)类型的数据帧。

#define LLC_DEST_SAP             1      /* Type 1 goes here */
#define LLC_DEST_CONN            2      /* Type 2 goes here */

void llc_add_pack(int type, void (*handler)(struct llc_sap *sap, struct sk_buff *skb))
{
    if (type == LLC_DEST_SAP || type == LLC_DEST_CONN)
        llc_type_handlers[type - 1] = handler;
}

内核版本 5.0

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值