NETDEV 协议 四

2024-06-11 17:48
文章标签 协议 netdev

本文主要是介绍NETDEV 协议 四,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

内核版本:2.6.34

NetFilter在2.4.x内核中引入,成为linux平台下进行网络应用的主要扩展,不仅包括防火墙的实现,还包括报文的处理(如报文加密、报文分类统计等)等。

NetFilter数据结构         勾子struct nf_hook_ops[net\filter\core.c]
struct nf_hook_ops {
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook;
struct module *owner;
u_int8_t pf;
unsigned int hooknum;
/* Hooks are ordered in ascending priority. */
int priority;
};
        成员list用于链入全局勾子数组nf_hooks中,它一定在第一位,保证&nf_hook_ops->list的值与&nf_hook_ops相同,稍后在使用时会用到这一技巧;
成员hook即用户定义的勾子函数;owner表示注册这个勾子函数的模块,因为netfilter是内核空间的,所以一般为模块来完成勾子函数注册;pf与hooknum一起索引到特定协议特定编号的勾子函数队列,用于索引nf_hooks;
priority决定在同一队列(pf与hooknum相同)的顺序,priority越小则排列越靠前。
        struct nf_hook_ops只是存储勾子的数据结构,而真正存储这些勾子供协议栈调用的是nf_hooks,从定义可以看出,它其实就是二维数组的链表。
        struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; [net\filter\core.c]
        其中NFPROTO_NUMPROTO表示勾子关联的协议,可取值:
enum {
NFPROTO_UNSPEC =  0,
NFPROTO_IPV4   =  2,
NFPROTO_ARP    =  3,
NFPROTO_BRIDGE =  7,
NFPROTO_IPV6   = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,
};
        NF_MAX_HOOKS表示勾子应用的位置,可选值在每个协议模块内部定义,这些值代表了勾子函数在协议流程中应用的位置(稍后会以bridge为例详细说明),大致上都有以下值:
	NF_XXX_PRE_ROUTING,
NF_XXX_LOCAL_IN,
NF_XXX_FORWARD,
NF_XXX_LOCAL_OUT,
NF_XXX_POST_ROUTING,
NF_XXX_NUMHOOKS

NetFilter注册
        在了解了nf_hook_ops和nf_hooks后,来看下如何操作nf_hooks中的元素。
        nf_register_hook()将nf_hook_ops注册到nf_hooks中:
int nf_register_hook(struct nf_hook_ops *reg)
{
struct nf_hook_ops *elem;
int err;
err = mutex_lock_interruptible(&nf_hook_mutex);
if (err < 0)
return err;
list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
if (reg->priority < elem->priority)
break;
}
list_add_rcu(®->list, elem->list.prev);
mutex_unlock(&nf_hook_mutex);
return 0;
}
        这个函数很简单,从指定pf&hooknum的nf_hooks队列遍历,按priority从小到大顺序,将reg插入相应位置,完成勾子函数的注册。

        nf_unregister_hook()将nf_hook_ops从nf_hooks中注销掉:
void nf_unregister_hook(struct nf_hook_ops *reg)
{
mutex_lock(&nf_hook_mutex);
list_del_rcu(®->list);
mutex_unlock(&nf_hook_mutex);
synchronize_net();
}
        这个函数更简单,从nf_hooks中删除reg。
        内核同时还提供了nf_register_hooks()和nf_unregister_hooks(),将reg重复注册n次或将reg从nf_hooks中注销n次。当勾子函数注册完成后,nf_hooks的结构如图所示:
 

NetFilter调用
        在报文在内核协议栈传递时,会调用NetFilter模块对报文进行特定的进滤,这样的过滤在代码中随处可见。
        以上一篇讲过的网桥为例,对于要进行网桥处理的报文,handle_bridge()->br_handle_frame(),如果端口处理于LEARNING或FORWARDING状态,且报文目的地址正确,则会调用br_handle_frame()进行后续处理,而这个函数调用就是:
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
NF_HOOK()->NF_HOOK_THRESH()->nf_hook_thresh()->nf_hook_slow():
int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
struct net_device *indev,
struct net_device *outdev,
int (*okfn)(struct sk_buff *),
int hook_thresh)
{
struct list_head *elem;
unsigned int verdict;
int ret = 0;
/* We may already have this, but read-locks nest anyway */
rcu_read_lock();
elem = &nf_hooks[pf][hook];
next_hook:
verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
outdev, &elem, okfn, hook_thresh);
if (verdict == NF_ACCEPT || verdict == NF_STOP) {
ret = 1;
} else if (verdict == NF_DROP) {
kfree_skb(skb);
ret = -EPERM;
} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
verdict >> NF_VERDICT_BITS))
goto next_hook;
}
rcu_read_unlock();
return ret;
}
        nf_hook_slow()从nf_hooks中找出到执行的勾子队列,依次执行,然后根据返回值决定是否继续(由nf_iterate()完成)。参数中的pf和hook代表了注册勾子函数时给的参数PF和HOOKNUM,它们共同决定勾子函数要插入的nf_hook的哪个队列中。 
        作为过滤报文的勾子函数的返回值是值得注意的地方,可取值如下:
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
        先以nf_iterate()函数为例,elem->hook()表示执行勾子函数,执行结构为verdict;
unsigned int nf_iterate(……)
{
unsigned int verdict;
list_for_each_continue_rcu(*i, head) {
struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
if (hook_thresh > elem->priority)
continue;
verdict = elem->hook(hook, skb, indev, outdev, okfn);
if (verdict != NF_ACCEPT) {
if (verdict != NF_REPEAT)
return verdict;
*i = (*i)->prev;
}
}
return NF_ACCEPT;
}
        根据nf_iterate()返回,会有以下情况:
              1. 如果结果为NF_ACCEPT,表示勾子函数允许报文继续向下处理,此时应该继续执行队列上的下一个勾子函数,因为这些勾子函数都是对同一类报文在相同位置的过滤,前一个通后,并不能返回,而要所有函数都执行完,结果仍为NF_ACCEPT时,则可返回它;
              2. 如果结果为NF_REPEAT,表示要重复执行勾子函数一次;所以勾子函数要编写得当,否则报文会一直执行一个返回NF_REPEAET的勾子函数,当返回值为NF_REPEAT时,不会返回;
              3. 如果为其它结果,则不必再执行队列上的其它函数,直接返回它;如NF_STOP表示停止执行队列上的勾子函数,直接返回;NF_DROP表示丢弃掉报文;NF_STOLEN表示报文不再往上传递,与NF_DROP不同的是,它没有调用kfree_skb()释放掉skb;NF_QUEUE检查给定协议(pf)是否有队列处理函数,有则进行处理,否则丢掉。
        了解了这些值再来看nf_hook_slow()中对于nf_iterate()返回值的处理就明了了:
if (verdict == NF_ACCEPT || verdict == NF_STOP) {
ret = 1;
} else if (verdict == NF_DROP) {
kfree_skb(skb);
ret = -EPERM;
} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
verdict >> NF_VERDICT_BITS))
goto next_hook;
}
        最后还是以bridge来说明下hooks参数的意义,上面已经讲过,它决定了在协议流程的何处调用勾子函数;因为使用NetFilter的目的是在内核态处理报文,而哪些地方可以处理报文只能是内核已经定义好的。一般来说,内核会在报文发送和接收的关键位置添加勾子函数处理,查找代码中NF_HOOK即可知。下面以bridge,为例,来看下在哪些地方用到了,以及这些值的含义:
NetFilter的存在使得在内核空间对报文进行用户定义的要求处理变得可能、简单。一般来说,编写好struct nf_hook_ops,其中hook/pf/ hook是必给的参数,然后使用nf_register_hook进行注册就可以了。整个过滤文件可以写了一个内核模块,用insmod进行动态加载。
本文只是一个内核网络协议的实践的例子,先说明添加的目的,下篇开始具体的实现。

        内核版本:2.6.34;在支持802.1主机上,报文的一般格式: 

        现在需要支持一种新的协议[二层] – BRCM协议,与IP等协议不同,它位于2层,拥有6字节的头部和4字节的尾部,添加的层次决定了比起添加其它协议要复杂一些,新的报文格式如下,而我们的目的就是要网络协议栈能正常处理这样的报文: 

        实际上BRCM是一种交换机的内部协议,用处是让交换机管理端口能通过BRCM获取报文来自于交换机的哪个端口,或者指定报文从交换机哪个端口出去;当然,这不是我们关心的内容,我们只需要为它挑选一个协议号0x8744,其余内容置0就可以了。因此,brcm头部会填写成 88 74 00 00 00 00,brcm尾部会填写成 00 00。一个新协议的报文内容用wireshark等捕包工具查看的形式如下:
                 [源/目的mac]02 03 04 05 06 07 10 11 12 13 14 15
                 [BRCM报头]88 74 00 00 00 00
                 [Vlan报头]81 00 00 01
                 [报文内容]……..

        先从设备的概念来看下添加BRCM协议后的层次图: 

        eth1代表实际的B4401物理网卡;eth1.X/brcm0.x代表VLAN创建的虚拟网卡,后面的数字X是vlan号;brcm0代表BRCM创建的虚拟网卡,数字0表示测试用;从图中可以看到,brcm协议的添加是通过添加brcmX虚拟网卡接口实现的。

        如果BRCM协议添加正确,那么最终的结果应该是:

         在接收报文时,通过虚拟设备BRCM会脱去brcm的6字节的头部与4字节尾部,当然,协议做的远远不只这些,但这是核心。 

        在发送报文时,通过虚拟设备BRCM会添加6字节的头部与4字节的尾部,当然,协议做的远远不只这些,但这是核心。

        要做的工作大致是以下几项:
               1. 确定brcm_dev的存储数据结构
               2. 编写struct ptype_base brcm_packet_type
               3. 添加ioctl调用供用户空间调用,至少包括brcm_dev的创建和删除
               4. 添加notifier机制、netlink机制、proc机制
               5. 添加编译用的Kconfig, Makefile等,并修改Menuconfig
        数据结构是核心,每一步也是一个网络协议模块的核心。下一篇开始brcm协议的添加实现。

内核版本:2.6.34

实现思路:
      报文在网络协议栈中的流动,对于接收来讲,是对报文的脱壳的过程,由于报文是已知的输入,只要逐个解析协议号;对于发送来讲,是各层发送函数的嵌套调用,由于没有已知的输入,只能按事先设计好的协议进行层层构造。但无论报文怎样的流动,核心是报文所在设备(skb->dev)的变化,相当于各层之间传递的交接棒。
      按照上述思路,brcm协议接收的处理作为模块brcm_packet_type加入到ptype_base中就可以了;brcm协议发送的处理则复杂一点,发送的嵌套调用完全是依赖于设备来推动的,因此要有一种新创建的设备X,插入到vlan设备和网卡设备之间。
因此,至少要有brcm_packet_type来加入ptype_base和register_brcm_dev()来向系统注册设备X。进一步考虑,设备X在全局量init_net中有存储,但我们还需要知道设备X与vlan设备以及网卡设备是何种组织关系,所以在这里设计了brcm_group_hash来存储这种关系。为了对设备感兴趣的事件作出响应,添加自己的notifier到netdev_chain中。另外,为了用户空间具有一定控制能力(如创建、删除),还需要添加brcm相关的ioctl调用。为了让它看起来更完整,一种新的设备在proc中也应有对应项,用来调试和查看设备。
 

从最简单开始
      要让网络协议栈能够接收一种新协议是很简单的,由于已经有报文作为输入,我们要做的仅仅是编写好brcm_packet_type,然后在注册模块时只用做一件事:dev_add_pack。

static int __init brcm_proto_init(void)
{
dev_add_pack(&brcm_packet_type);
}
static struct packet_type brcm_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_BRCM),
.func = brcm_skb_recv, /* BRCM receive method */
};
int brcm_skb_recv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *ptype, struct net_device *orig_dev)
{
struct brcm_hdr *bhdr;
struct brcm_rx_stats *rx_stats;
skb = skb_share_check(skb, GFP_ATOMIC);
if(!skb)
goto err_free;
bhdr = (struct brcm_hdr *)skb->data;
rcu_read_lock();
skb_pull_rcsum(skb, BRCM_HLEN);
// set protocol
skb->protocol = bhdr->brcm_encapsulated_proto;
// reorder skb
skb = brcm_check_reorder_header(skb);
if (!skb) 
goto err_unlock;
netif_rx(skb);
rcu_read_unlock();
return NET_RX_SUCCESS;
err_unlock:
rcu_read_unlock();
err_free:
kfree_skb(skb);
return NET_RX_DROP;
}

      注册这个模块后,协议栈就能正常接收带brcm报头的报文的,代码中ETH_P_BRCM是brcm的协议号,BRCM_HLEN是brcm的报头长度。正是由于有报文作为输入,接收变得十分简单。
      但这仅仅是能接收而已,发送的报文还是不带brcm报头的,而且接收的这段代码也很粗略,没有变更skb的设备,没有记录流量,没有对brcm报头作有意义的处理,下面逐一进行添加。

设备的相关定义
      一种设备就是net_device类型,而每种设备都有自己的私有变量,它存储在net_device末尾,定义如下,其中real_dev指向下层设备,这是最基本属性,其余可以视需要自己设定,brcm_rx_stats则是该设备接收流量统计:

struct brcm_dev_info{
struct net_device  *real_dev;
u16 brcm_port;
unsigned char  real_dev_addr[ETH_ALEN];
struct proc_dir_entry *dent;
struct brcm_rx_stats __percpu  *brcm_rx_stats;
};
struct brcm_rx_stats {
unsigned long rx_packets;
unsigned long rx_bytes;
unsigned long multicast;
unsigned long rx_errors;
};

 设备间的关系问题
      如果brcm仅仅是只有一个设备,则无需数据结构来存储这种关系,一个全局全变的brcm_dev就可以了。这里的设计考虑的是复杂的情况,可以存在多个下层设备,多个brcm设备,之间没有固定的关系。所以需要一种数据结构来存储这种关系- brcm_group_hash。下面是一个简单的图示: 

      各个数据结构定义如下:

static struct hlist_head brcm_group_hash[BRCM_GRP_HASH_SIZE];
struct brcm_group {
struct hlist_node hlist;
struct net_device *real_dev;
int nr_ports;
int killall;
struct net_device *brcm_devices_array[BRCM_GROUP_ARRAY_LEN];
struct rcu_head  rcu;
};

      brcm_group_hash作为全局变量存在,以hash表形式组织,brcm_group被插入到brcm_group_hash中,brcm_group存储了它与下层设备的关系(eth与brcm),real_dev指向e下层设备,而brcm设备则存储在brcm_devices_array数组中。
     下面完成由下层设备转换成brcm设备的函数,brcm_port是报头中的值,可以自己设定它的含义,这里设定它表示报文来自于哪个端口。

struct net_device *find_brcm_dev(struct net_device *real_dev, u16 brcm_port)
{
struct brcm_group *grp = brcm_find_group(real_dev);
if (grp) 
brcm_dev = grp->brcm_devices_array[brcm_port];
return NULL;
}

     因为在接收报文时,报文到达brcm层开始处理时,skb->dev指向的仍是下层设备,这时通过skb->dev查到brcm_group->real_dev相匹配的hash项,然后通过报文brcm报头的信息,确定brcm_group->brcm_devices_array中哪个brcm设备作为skb的新设备;
     而在发送报文时,报文到达brcm层开始处理时,skb->dev指向的是brcm设备,为了继续向下传递,需要变更为它的下层设备,在设备数据net_device的私有数据部分,一般会存储一个指针,指向它的下层设备,因此skb->dev只要变更为brcm_dev_info(dev)->real_dev。
 

流量统计
      在数据结构中,brcm设备的私有数据brcm_dev_info中brcm_rx_stats记录接收的流量信息;而dev->_tx[index]则会记录发送的流量信息。
      在接收函数brcm_skb_rcv()中对于成功接收的报文会增加流量统计:

rx_stats = per_cpu_ptr(brcm_dev_info(skb->dev)->brcm_rx_stats,
smp_processor_id());
rx_stats->rx_packets++;
rx_stats->rx_bytes += skb->len;

      在发送函数brcm_dev_hard_start_xmit()中对于发送的报文会增加相应流量统计:

if (likely(ret == NET_XMIT_SUCCESS)) {
txq->tx_packets++;
txq->tx_bytes += len;
} else
txq->tx_dropped++;

      而brcm_netdev_ops->ndo_get_stats()即brcm_dev_get_stats()函数,则会将brcm网卡设备中记录的发送和接收流量信息汇总成通用的格式net_device_stats,像ifconfig等命令使用的就是net_device_stats转换后的结果。 

完整收发函数
      有了这些后接收函数brcm_skb_recv()就可以完整了,其中关于报头brcm_hdr的处理可以略过,由于是空想的协议,含义是可以自己设定的:

int brcm_skb_recv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *ptype, struct net_device *orig_dev)
{
struct brcm_hdr *bhdr;
struct brcm_rx_stats *rx_stats;
int op, brcm_port;
skb = skb_share_check(skb, GFP_ATOMIC);
if(!skb)
goto err_free;
bhdr = (struct brcm_hdr *)skb->data;
op = bhdr->brcm_tag.brcm_53242_op;
brcm_port = bhdr->brcm_tag.brcm_53242_src_portid- 23;
rcu_read_lock();
// drop wrong brcm tag packet
if (op != BRCM_RCV_OP || brcm_port < 1 
|| brcm_port > 27) 
goto err_unlock;
skb->dev = find_brcm_dev(dev, brcm_port);
if (!skb->dev) {
goto err_unlock;
}
rx_stats = per_cpu_ptr(brcm_dev_info(skb->dev)->brcm_rx_stats,
smp_processor_id());
rx_stats->rx_packets++;
rx_stats->rx_bytes += skb->len;
skb_pull_rcsum(skb, BRCM_HLEN);
switch (skb->pkt_type) {
case PACKET_BROADCAST: /* Yeah, stats collect these together.. */
/* stats->broadcast ++; // no such counter :-( */
break;
case PACKET_MULTICAST:
rx_stats->multicast++;
break;
case PACKET_OTHERHOST:
/* Our lower layer thinks this is not local, let's make sure.
* This allows the VLAN to have a different MAC than the
* underlying device, and still route correctly.
*/
if (!compare_ether_addr(eth_hdr(skb)->h_dest,
skb->dev->dev_addr))
skb->pkt_type = PACKET_HOST;
break;
default:
break;
}
// set protocol
skb->protocol = bhdr->brcm_encapsulated_proto;
// reorder skb
skb = brcm_check_reorder_header(skb);
if (!skb) {
rx_stats->rx_errors++;
goto err_unlock;
}
netif_rx(skb);
rcu_read_unlock();
return NET_RX_SUCCESS;
err_unlock:
rcu_read_unlock();
err_free:
kfree_skb(skb);
return NET_RX_DROP;
}

      同时,发送函数brcm_dev_hard_start_xmit()可以完整了,同样,其中关于brcm_hdr的处理可以略过:

static netdev_tx_t brcm_dev_hard_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
int i = skb_get_queue_mapping(skb);
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
struct brcm_ethhdr *beth = (struct brcm_ethhdr *)(skb->data);
unsigned int len;
u16 brcm_port;
int ret;
/* Handle non-VLAN frames if they are sent to us, for example by DHCP.
*
* NOTE: THIS ASSUMES DIX ETHERNET, SPECIFICALLY NOT SUPPORTING
* OTHER THINGS LIKE FDDI/TokenRing/802.3 SNAPs...
*/
if (beth->h_brcm_proto != htons(ETH_P_BRCM)){
//unsigned int orig_headroom = skb_headroom(skb);
brcm_t brcm_tag;
brcm_port = brcm_dev_info(dev)->brcm_port;
if (brcm_port == BRCM_ANY_PORT) {
brcm_tag.brcm_op_53242 = 0;
brcm_tag.brcm_tq_53242 = 0;
brcm_tag.brcm_te_53242 = 0;
brcm_tag.brcm_dst_53242 = 0;
}else {
brcm_tag.brcm_op_53242 = BRCM_SND_OP;
brcm_tag.brcm_tq_53242 = 0;
brcm_tag.brcm_te_53242 = 0;
brcm_tag.brcm_dst_53242 = brcm_port + 23;
}
skb = brcm_put_tag(skb, *(u32 *)(&brcm_tag));
if (!skb) {
txq->tx_dropped++;
return NETDEV_TX_OK;
}
}
skb_set_dev(skb, brcm_dev_info(dev)->real_dev);
len = skb->len;
ret = dev_queue_xmit(skb);
if (likely(ret == NET_XMIT_SUCCESS)) {
txq->tx_packets++;
txq->tx_bytes += len;
} else
txq->tx_dropped++;
return ret;
}

注册设备
      接收通过dev_add_pack(),就可以融入协议栈了,前面几篇的分析已经讲过通过ptype_base对报文进行脱壳。现在要融入的发送,函数已经完成了,既然发送是一种嵌套的调用,并且是由dev来推过的,那么发送函数的融入一定在设备进行注册时,作为设备的一种发送方法。
      创建一种设备时,一定会有设备的XXX_setup()初始化,大部分设备都会用ether_setup()来作初始化,再进行适当更改。下面是brcm_setup():

void brcm_setup(struct net_device *dev)
{
ether_setup(dev);
dev->priv_flags  |= IFF_BRCM_TAG;
dev->priv_flags  &= ~IFF_XMIT_DST_RELEASE;
dev->tx_queue_len = 0;
dev->netdev_ops  = &brcm_netdev_ops;
dev->destructor  = free_netdev;
dev->ethtool_ops = &brcm_ethtool_ops;
memset(dev->broadcast, 0, ETH_ALEN);
}

      其中发送函数就在brcm_netdev_ops中,每层设备都会这样调用:dev->netdev_ops->ndo_start_xmit()。     

static const struct net_device_ops brcm_netdev_ops = {
.ndo_change_mtu		= brcm_dev_change_mtu,
.ndo_init		= brcm_dev_init,
.ndo_uninit		= brcm_dev_uninit,
.ndo_open		= brcm_dev_open,
.ndo_stop		= brcm_dev_stop,
.ndo_start_xmit =  brcm_dev_hard_start_xmit,
.ndo_validate_addr	= eth_validate_addr,
.ndo_set_mac_address	= brcm_dev_set_mac_address,
.ndo_set_rx_mode	= brcm_dev_set_rx_mode,
.ndo_set_multicast_list	= brcm_dev_set_rx_mode,
.ndo_change_rx_flags	= brcm_dev_change_rx_flags,
//.ndo_do_ioctl		= brcm_dev_ioctl,
.ndo_neigh_setup	= brcm_dev_neigh_setup,
.ndo_get_stats		= brcm_dev_get_stats,
};
       而设备的初始化应该发生在创建设备时,也就是向网络注册它时,也就是register_brcm_dev(),注册一个新设备,需要知道它的下层设备real_dev以及唯一标识brcm设备的brcm_port。首先确定该设备没有被创建,然后用alloc_netdev_mq创建新设备new_dev,然后设置相关属性,特别是它的私有属性brcm_dev_info(new_dev),然后添加它到brcm_group_hash中,最后发生真正的注册register_netdevice()。

static int register_brcm_dev(struct net_device *real_dev, u16 brcm_port)
{
struct net_device *new_dev;
struct net *net = dev_net(real_dev);
struct brcm_group *grp;
char name[IFNAMSIZ];
int err;
if(brcm_port >= BRCM_PORT_MASK)
return -ERANGE;
// exist yet
if (find_brcm_dev(real_dev, brcm_port) != NULL)
return -EEXIST;
snprintf(name, IFNAMSIZ, "brcm%i", brcm_port);
new_dev = alloc_netdev_mq(sizeof(struct brcm_dev_info), name,
brcm_setup, 1);
if (new_dev == NULL)
return -ENOBUFS;
new_dev->real_num_tx_queues = real_dev->real_num_tx_queues;
dev_net_set(new_dev, net);
new_dev->mtu = real_dev->mtu;
brcm_dev_info(new_dev)->brcm_port = brcm_port;
brcm_dev_info(new_dev)->real_dev = real_dev;
brcm_dev_info(new_dev)->dent = NULL;
//new_dev->rtnl_link_ops = &brcm_link_ops;
grp = brcm_find_group(real_dev);
if (!grp)
grp = brcm_group_alloc(real_dev);
err = register_netdevice(new_dev);
if (err < 0)
goto out_free_newdev;
/* Account for reference in struct vlan_dev_info */
dev_hold(real_dev);
brcm_group_set_device(grp, brcm_port, new_dev);
return 0;
out_free_newdev:
free_netdev(new_dev);
return err;
}

ioctl
      由于brcm设备可以存在多个,并且和下层设备不是固定的对应关系,因此它的创建应该可以人为控制,因此通过ioctl由用户进行创建。这里只为brcm提供了两种操作-添加与删除。一种设备添加一定是与下层设备成关系的,因此添加时需要手动指明这种下层设备,然后通过__dev_get_by_name()从网络空间中找到这种设备,就可以调用register_brcm_dev()来完成注册了。而设备的删除则是直接删除,直接删除unregister_brcm_dev()。
     

static int brcm_ioctl_handler(struct net *net, void __user *arg)
{
int err;
struct brcm_ioctl_args args;
struct net_device *dev = NULL;
if (copy_from_user(&args, arg, sizeof(struct brcm_ioctl_args)))
return -EFAULT;
/* Null terminate this sucker, just in case. */
args.device1[23] = 0;
args.u.device2[23] = 0;
rtnl_lock();
switch (args.cmd) {
case ADD_BRCM_CMD:
case DEL_BRCM_CMD:
err = -ENODEV;
dev = __dev_get_by_name(net, args.device1);
if (!dev)
goto out;
err = -EINVAL;
if (args.cmd != ADD_BRCM_CMD && !is_brcm_dev(dev))
goto out;
}
switch (args.cmd) {
case ADD_BRCM_CMD:
err = -EPERM;
if (!capable(CAP_NET_ADMIN))
break;
err = register_brcm_dev(dev, args.u.port);
break;
case DEL_BRCM_CMD:
err = -EPERM;
if (!capable(CAP_NET_ADMIN))
break;
unregister_brcm_dev(dev, NULL);
err = 0;
break;
default:
err = -EOPNOTSUPP;
break;
}
out:
rtnl_unlock();
return err;
}

        这些是brcm协议模块的主体部分了,当然它还不完整,在下篇中继续完成brcm协议的添加,为它完善一些细节:proc文件系统, notifier机制等等,以及内核Makefile的编写,当然还有协议的测试。相关源码在下篇中打包上传。

内核版本:2.6.34
接上篇《添加网络协议》。
        为了用户方便查看brcm设备的工作状态,使用proc文件系统是很好的方式。一个网络协议模块可以注册到网络空间中register_pernet_subsys(),这个函数会为子空间分配一个id号,通过id可以在网络空间中找到分配给该子空间的内存:init_net->gen->ptr[id - 1]。而我们正是利用这块内存去存储proc中的相关信息:struct brcm_net,它记录了brcm设备在proc文件系统中的位置。

struct brcm_net {
/* /proc/net/brcm */
struct proc_dir_entry *proc_brcm_dir;
/* /proc/net/brcm/config */
struct proc_dir_entry *proc_brcm_conf;
};

        在加载brcm模块时会注册子空间,brcm_init_net创建在proc中的相关项,并记录路径在brcm_net中;brcm_exit_net删除在proc中的相关项;brcm_net_id记录分配给子空间的id,这样通过init_net->gen->ptr[brcm_net_id - 1]就可以操作brcm_net了。

err = register_pernet_subsys(&brcm_net_ops);
static struct pernet_operations brcm_net_ops = {
.init = brcm_init_net,
.exit = brcm_exit_net,
.id = &brcm_net_id,
.size = sizeof(struct brcm_net),
};

        注意到在brcm_init_net和brcm_exit_net中添加和删除的仅仅是/proc/net/brcm目录和config文件,而在使用中brcm设备是可以动态创建的,因此这部分代码应该发生在添加和删除brcm设备时,而不是在brcm模块注册和删除时。最简单的是直接添加在register方法或unregister方法中,但内核提供了更好的机制:事件,将对proc的操作分离出来,因为proc的操作实际上属于附加的操作而不是必须的操作。下面就来看event机制。
        前面几篇已经有描述过event机制,这里的事件都是关于设备的事件,使用的是register_netdevice_notifier()来,注册notifier到netdev_chain链表上。在加载brcm模块时注册notifier,brcm_notifier_block包含了brcm设备对所关心的事件作出的反应。

err = register_netdevice_notifier(&brcm_notifier_block);
static struct notifier_block brcm_notifier_block __read_mostly = {
.notifier_call = brcm_device_event,
};

        设备对哪些事件会感兴趣,首先,brcm设备对注册和注销是感兴趣的,要操作proc文件系统;其次,对于发往brcm下层设备的事件,要考虑这些事件造成的连带影响(比如eth1被down掉,则其上的brcm设备也应该被down掉),一般是下层设备的事件对其上的所有brcm设备都进行相应操作。
        所以在brcm_device_event有两类进入的设备dev会进行操作,一类是brcm设备,它仅仅是操作proc文件系统。判断是否为brcm设备,是的话则由__brcm_device_event() 处理。 

if (is_brcm_dev(dev))
__brcm_device_event(dev, event);
static void __brcm_device_event(struct net_device *dev, unsigned long event)
{
switch (event) {
case NETDEV_CHANGENAME:
brcm_proc_rem_dev(dev);
if (brcm_proc_add_dev(dev) < 0)
pr_warning("BRCM: failed to change proc name for %s\n",
dev->name);
break;
case NETDEV_REGISTER:
if (brcm_proc_add_dev(dev) < 0)
pr_warning("BRCM: failed to add proc entry for %s\n",
dev->name);
break;
case NETDEV_UNREGISTER:
brcm_proc_rem_dev(dev);
break;
}
}

        如何是brcm的下层设备,如根据brcm_group_hash中的映射关系,对下层设备相关的所有brcm设备进行操作:

switch (event) {
case NETDEV_CHANGE:
/* Propagate real device state to vlan devices */
for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
brcmdev = brcm_group_get_device(grp, i);
if (!brcmdev)
continue;
netif_stacked_transfer_operstate(dev, brcmdev);
}
break;
case NETDEV_CHANGEADDR:
/* Adjust unicast filters on underlying device */
for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
brcmdev = brcm_group_get_device(grp, i);
if (!brcmdev)
continue;
flgs = brcmdev->flags;
if (!(flgs & IFF_UP))
continue;
brcm_sync_address(dev, brcmdev);
}
break;
case NETDEV_CHANGEMTU:
for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
brcmdev = brcm_group_get_device(grp, i);
if (!brcmdev)
continue;
if (brcmdev->mtu <= dev->mtu)
continue;
dev_set_mtu(brcmdev, dev->mtu);
}
break;
case NETDEV_DOWN:
/* Put all VLANs for this dev in the down state too.  */
for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
brcmdev = brcm_group_get_device(grp, i);
if (!brcmdev)
continue;
flgs = brcmdev->flags;
if (!(flgs & IFF_UP))
continue;
brcm = brcm_dev_info(brcmdev);
dev_change_flags(brcmdev, flgs & ~IFF_UP);
netif_stacked_transfer_operstate(dev, brcmdev);
}
break;
case NETDEV_UP:
/* Put all VLANs for this dev in the up state too.  */
for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
brcmdev = brcm_group_get_device(grp, i);
if (!brcmdev)
continue;
flgs = brcmdev->flags;
if (flgs & IFF_UP)
continue;
brcm = brcm_dev_info(brcmdev);
dev_change_flags(brcmdev, flgs | IFF_UP);
netif_stacked_transfer_operstate(dev, brcmdev);
}
break;
case NETDEV_UNREGISTER:
/* Delete all BRCMs for this dev. */
grp->killall = 1;
for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
brcmdev = brcm_group_get_device(grp, i);
if (!brcmdev)
continue;
/* unregistration of last brcm destroys group, abort
* afterwards */
if (grp->nr_ports == 1)
i = BRCM_GROUP_ARRAY_LEN;
unregister_brcm_dev(brcmdev, &list);
}
unregister_netdevice_many(&list);
break;
}

        到这里,协议的添加就大致完成了,当然还包括一些头文件的修改,宏变量的添加等就不一一详述,具体可见最后的附件。
        为了编译进内核,还需要修改以下文件:
              $(linux)/net/Kconfig  $(linux)/net/Makefile

        最后,在make menuconfig选择添加brcm协议
              Networking Support -> Networking options     
  

        同时,需要一个简单的用户空间工具来配置我们的brcm设备,就像vconfig用来配置vlan设备一样;编写的简单的bconfig工具,命令格式:
                "Usage: add [interface-name] [brcm_port]\n"
                "       rem [dev-name]";

        内核编译完成后就该进行测试了,如果开启了内核调试信息,启动内核就看到以下信息: 

        然后启用网卡,可以查看到添加了brcm设备后的状态: 

        可以使用原生套接字自己打上brcm头后发送报文让协议栈接收,或者用wireshark等捕获协议栈发出的报文,下图即是捕获到的报文: 

        这是主机发出的arp报文,可以看到,在源mac后接的不是vlan报头,而是我们添加的brcm报文,协议号是8744。
        查看proc中信息:
 

        :patch补丁 && 重要的源文件 && bconfig工具源码

              http://download.csdn.net/source/3548117

这篇关于NETDEV 协议 四的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1051865

相关文章

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

【Go】go连接clickhouse使用TCP协议

离开你是傻是对是错 是看破是软弱 这结果是爱是恨或者是什么 如果是种解脱 怎么会还有眷恋在我心窝 那么爱你为什么                      🎵 黄品源/莫文蔚《那么爱你为什么》 package mainimport ("context""fmt""log""time""github.com/ClickHouse/clickhouse-go/v2")func main(

2024.9.8 TCP/IP协议学习笔记

1.所谓的层就是数据交换的深度,电脑点对点就是单层,物理层,加上集线器还是物理层,加上交换机就变成链路层了,有地址表,路由器就到了第三层网络层,每个端口都有一个mac地址 2.A 给 C 发数据包,怎么知道是否要通过路由器转发呢?答案:子网 3.将源 IP 与目的 IP 分别同这个子网掩码进行与运算****,相等则是在一个子网,不相等就是在不同子网 4.A 如何知道,哪个设备是路由器?答案:在 A

Modbus-RTU协议

一、协议概述 Modbus-RTU(Remote Terminal Unit)是一种基于主从架构的通信协议,采用二进制数据表示,消息中的每个8位字节含有两个4位十六进制字符。它主要通过RS-485、RS-232、RS-422等物理接口实现数据的传输,传输距离远、抗干扰能力强、通信效率高。 二、报文结构 一个标准的Modbus-RTU报文通常包含以下部分: 地址域:单个字节,表示从站设备

网络原理之TCP协议(万字详解!!!)

目录 前言 TCP协议段格式 TCP协议相关特性 1.确认应答 2.超时重传 3.连接管理(三次握手、四次挥手) 三次握手(建立TCP连接) 四次挥手(断开连接)  4.滑动窗口 5.流量控制 6.拥塞控制 7.延迟应答 8.捎带应答  9.基于字节流 10.异常情况的处理 小结  前言 在前面,我们已经讲解了有关UDP协议的相关知识,但是在传输层,还有

DNS协议基础笔记

1.定义 DNS(Domain Name System,域名系统)是互联网的一项核心服务,它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。 2.域名解析过程 当用户在浏览器中输入一个域名,浏览器首先会检查自己的缓存中是否有该域名对应的 IP 地址。本地 DNS 服务器收到查询请求后,首先会检查自己的缓存中是否有该域名对应的 IP 地址。根域名服务器收到查询请

4G模块、WIFI模块、NBIOT模块通过AT指令连接华为云物联网服务器(MQTT协议)

MQTT协议概述 MQTT(Message Queuing Telemetry Transport)是一种轻量级的消息传输协议,它被设计用来提供一对多的消息分发和应用之间的通讯,尤其适用于远程位置的设备和高延迟或低带宽的网络。MQTT协议基于客户端-服务器架构,客户端可以订阅任意数量的主题,并可以发布消息到这些主题。服务器(通常称为MQTT Broker)则负责接受来自客户端的连接请求,并转发消

HTTP协议 HTTPS协议 MQTT协议介绍

目录 一.HTTP协议 1. HTTP 协议介绍 基本介绍: 协议:  注意: 2. HTTP 协议的工作过程 基础术语: 客户端: 主动发起网络请求的一端 服务器: 被动接收网络请求的一端 请求: 客户端给服务器发送的数据 响应: 服务器给客户端返回的数据 HTTP 协议的重要特点: 一发一收,一问一答 注意: 网络编程中,除了一发一收之外,还有其它的模式 二.HTT