网卡驱动

裸机 时序

1. 读寄存器:

1. *(unsigned int *)0x5000000 = reg; //从哪个寄存器读
 //0x5000000为bank1,访问bank1时他会自动产生nGCSx信号和WE信号,
 // MCU的地址总线上会出现0x50000000,数据总线 上会出现reg.
 //因为DM9000的cmd接的是地址总线的ADDR[2],故此时cmd为低电平,表示16位端口上出现的
是地址,又因为WE有效故是写,这样DM9000就会锁存SD[160]上的电平作为接下来即将操作的寄存器地址。
2.  int a = *(unsigned int *)0x5000004;//准备接受数据
//0x5000004为bank1,访问bank1时他会自动产生nGCSx信号和RE信号,
// MCU的地址总线上会出现0x50000004,数据总线 上会出现DM9000送出来的数据.
//因为DM9000的cmd接的是地址总线的ADDR[2],故此时cmd为高电平,表示16位端口上出现的
是数据,有因为RE有效故是读,这样DM9000就会把数据送到SD[160]上。


封装函数为     int DM9000_read_reg(u32 reg);

2.写寄存器:

1. *(unsigned int *)0x5000000 = reg;
2.  *(unsigned int *)0x5000004 = data;


封装函数为 void DM9000_write_reg(u32 reg, u32 data);

3.读SRAM:

从DM9000的SRAM中某个地址处读取数据
//1.告诉DM9000从SRAM中哪个位置读
DM9000_write_reg(MDRAH, ADDR_H);
DM9000_write_reg(MDRAL, ADDR_L);
//2.读数据
int a = DM9000_read_reg(MRCMD);
//3.连续读
int a = DM9000_read_reg(MRCMD);//SRAM中的地址会自动增1

4.写SRAM:

把一个数写到DM9000的SRAM(一般总共16K低3K用于发送,高13K用于接收)
//1.告诉DM9000往SRAM中哪个位置写
DM9000_write_reg(MDRAH, ADDR_H);
DM9000_write_reg(MDRAL, ADDR_L);
//2.要写的数值
DM9000_write_reg(MWCMD, data);
//3.连续写
DM9000_write_reg(MWCMD, data1); //SRAM地址会自动增1

5.将SRAM中的数发向RJ45:

这个过程是DM9000内部的一次DMA传输
//1.在SRAM的0首地址处填充要发送的数据包,即一系列的写SRAM的操作
//2.在发送控制寄存器TCR中使能时钟和CRC校验
//3.把要发送的长度放入发送长度寄存器中
DM9000_write_reg(TXPLL_L, len_L);
DM9000_write_reg(TXPLL_H, len_H);
//4.置位发送控制寄存器第0位,启动发送,发送完成后硬件自动将该位清0
DM9000_write_reg(TXPLL_H, len_H);

6.从RJ45接收数据到DM9000的SRAM中:

//1.DM9000会自动从网络接收数据包,存放到SRAM的高13K处
//2.接受完成后会自动添加4字节的包头,01h、状态、包长高字节、包长低字节
//3.DM9000自动置中断标志位并由中断引脚触发中断叫醒4412
//4.4412cpu响应中断,从DM9000的SRAM中读取包头并分析后,按包头指示操作

内核提供的接口函数:

1.数据结构:

<include/linux/netdevice.h>

struct net_device {
    char            name[IFNAMSIZ];   //物理接口名(网卡名)
    struct hlist_node   name_hlist;
    char            *ifalias;
    /*物理信息:IO地址、中断号*/
    unsigned long       mem_end;     //缓存结束
    unsigned long       mem_start;   //缓存起始
    unsigned long       base_addr;   //基地址
    int         irq;                 //中断
    netdev_features_t   features;    //可变特性,DMA,校验等一系列附加特性
    netdev_features_t   hw_features; //用户不能改变的私有特性
    unsigned int        flags;       //可变标准
    unsigned int        priv_flags;  //用户不能改变的标准
    const struct net_device_ops *netdev_ops;   //操作方法集
    const struct ethtool_ops *ethtool_ops;     //eth工具集操作方法集
    const struct header_ops *header_ops;       //协议头操作方法集
    unsigned char       if_port;               //接口类型
    unsigned int        mtu;                   //最大包长      
    unsigned short      type;                  //网络包类型
    unsigned short      hard_header_len;       //硬件协议头长度
    unsigned char   perm_addr[MAX_ADDR_LEN];   //MAC地址
    unsigned char       addr_len;              //MAC地址长度
    unsigned short          dev_id;            //指向mac地址
    struct in_device __rcu  *ip_ptr;           //ip地址
    ...
};
<include/linux/netdevcice.h>

struct net_device_ops {
    int     (*ndo_init)(struct net_device *dev);  //构造初始化对象
    void    (*ndo_uninit)(struct net_device *dev);//析构函数,回收清理
    int     (*ndo_open)(struct net_device *dev);  //打开设备
    int     (*ndo_stop)(struct net_device *dev);  //停止
    netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb,
                          struct net_device *dev);//发送
    int     (*ndo_set_mac_address)(struct net_device *dev,
                               void *addr);      //设置mac地址
    int  (*ndo_validate_addr)(struct net_device *dev);//检查MAC是否有效
    int     (*ndo_do_ioctl)(struct net_device *dev,
                            struct ifreq *ifr, int cmd);//ioctl
    int (*ndo_set_config)(struct net_device *dev,
                          struct ifmap *map);//修改网卡io地址、中断等网卡物理信息
    int         (*ndo_change_mtu)(struct net_device *dev,
                          int new_mtu);   //设置最大包长
    void    (*ndo_tx_timeout) (struct net_device *dev);  //超时重发
    ...

2.通用的函数:

步骤性的:

/*
* 功能:创建一个以太网设备对象
* 输入参数:私有数据大小
* 返回值:成功:设备句柄     失败:NULL
*/
 struct net_device *alloc_etherdev(sizeof_priv)
/*
* 功能:创建一个网络设备对象
* 输入参数:sizeof_priv:私有数据大小
*          name:网卡名字(名称+%d)
*          name_assign_type:NET_NAME_UNKNOWN
*          setup:初始化函数指针(回调函数)
* 返回值:成功:设备句柄     失败:NULL
*/
struct net_device *alloc_netdev(sizeof_priv, name,      name_assign_type, setu
/*
* 功能:释放一个网络设备对象
* 输入参数:设备句柄
* 返回值:none
*/
void free_net_devive(struct net_device *dev);
/*
* 功能:以太网设备初始化
* 输入参数:设备句柄
* 返回值:none
*/
void ether_setup(struct net_device *dev);
/*
* 功能:注册网络设备
* 输入参数:设备句柄
* 返回值:成功:0  失败:负数
*/
int register_netdev(struct net_device *dev);
/*
* 功能:注销网络设备
* 输入参数:设备句柄
* 返回值:成功:0  失败:负数
*/
int unregister_netdevice(struct net_device *dev);

设置参数的:

/*
* 功能:提取私有数据
* 输入参数:设备句柄
* 返回值:成功:私有数据首地址  失败:NULL
*/
void *netdev_priv(struct net_device *dev)
/*
* 功能:设置mac地址
* 输入参数:设备句柄
*        p:MAC地址所在首地址
* 返回值:成功:0  失败:负数
*/
int eth_mac_addr(struct net_device *dev, void *p)
/*
* 功能:随机生成mac地址,并将生成的mac地址填充到传入的dev结构体的对应域
* 输入输出参数:设备句柄
* 返回值:none
*/
void eth_hw_addr_random(struct net_device *dev)
/*
* 功能:随机生成mac地址
* 输出参数:addr:将得到的结果放在addr中
* 返回值:none
*/
void eth_random_addr(u8 *addr);
/*
* 功能:检查mac地址是否有效
* 输入参数:设备句柄
* 返回值:有效:1  无效:0
*/
int eth_validate_addr(struct net_device *dev);
/*
* 功能:修改MTU值
* 输入参数:dev:         设备句柄
*        new_mtu:       新的MTU值(最大包长)
* 返回值:成功:0  失败:负数
*/
int eth_change_mtu(struct net_device *dev, int new_mtu);

发送相关的:

类比块设备的请求队列记忆,每一个发送请求都是经过调度算法处理后放在发送队列
中,驱动层的发送回调函数只要从队列中取出任务处理每个发送请求就行。故要响应
用户层的发送请求就必须开启请求队列。
/*
* 功能:开启发送队列
* 输入参数:dev:         设备句柄
* 返回值:none
*/
void netif_start_queue(struct net_device *dev)
/*
* 功能:关闭发送队列
* 输入参数:dev:         设备句柄
* 返回值:none
*/
void netif_stop_queue(struct net_device *dev)
/*
* 功能:打开载波调制
* 输入参数:dev:         设备句柄
* 返回值:none
*/
void netif_carrier_on(struct net_device *dev)
/*
* 功能:关闭载波调制
* 输入参数:dev:         设备句柄
* 返回值:none
*/
void netif_carrier_off(struct net_device *dev)

套接字缓冲区相关的:

/*
* 功能:开辟一块套接字缓冲区
* 输入参数:unsigned int size:大小
*         gfp_t priority:   权限
* 返回值:缓冲区首地址
*/
struct sk_buff *alloc_skb(unsigned int size, gfp_t priority)
/*
* 功能:开辟一块套接字缓冲区
* 输入参数:  struct net_device dev: 网络设备句柄
*           unsigned int size:     大小
* 返回值:缓冲区首地址
*/
struct sk_buff *netdev_alloc_skb(struct net_device *dev, 
                                 unsigned int length)
/*
* 功能:释放一块套接字缓冲区
* 输入参数:  struct sk_buff *skb:缓冲区首地址 
* 返回值:none
*/
void kfree_skb(struct sk_buff *skb);
/*
* 功能:释放一块套接字缓冲区
* 输入参数:  struct sk_buff *skb:缓冲区首地址 
* 返回值:none
*/
void dev_kfree_skb_any(struct sk_buff *skb);
/*
* 功能:tail增大,有效数据区变大,但总的skb大小未变(head,end没变嘛)
* 输入参数:  struct sk_buff *skb: 缓冲区首地址 
*           unsigned int len:    要增大多少
* 返回值:返回tail指针
*/
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
/*
* 功能:data指针上移,有效数据区变大(类似入栈,压子弹),headroom空间被压缩变小(head指针没变嘛)
* 输入参数:  struct sk_buff *skb: 缓冲区首地址 
*           unsigned int len:    要上移多少
* 返回值:返回data指针
*/
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
/*
* 功能:data和tail一起下移,,有效数据区不变,但headroom空间增大
* 输入参数:  struct sk_buff *skb: 缓冲区首地址 
*           unsigned int len:    要下移多少
* 返回值:none
*/
void skb_reserve(struct sk_buff *skb, int len)
/*
* 功能:把sk_buff提交给协议层
* 输入参数:  struct sk_buff *skb: 缓冲区首地址 
* 返回值:成功:0(NET_RX_SUCCESS)  失败:1(NET_RX_DROP)
*/
int netif_rx(struct sk_buff *skb)
/*
* 功能:得到协议编号
* 输入参数:  struct sk_buff *skb:     缓冲区首地址 
*           struct net_device *dev:  网络设备句柄
* 返回值:成功:得到的编号  失败:负数
*/
__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)

例程:

1.c文件

2.h文件

源码追踪:

见思维导图

源码下载: https://git.coding.net/xxgui1992/LinuxDriver-net.git

转载网友的:SKB细说:

一. SKB_BUFF的基本概念
1. 一个完整的skb buff组成
(1) struct sk_buff–用于维护socket buffer状态和描述信息
(2) header data–独立于sk_buff结构体的数据缓冲区,用来存放报文分组,使各层协议的header存储在连续的空间中,以方便协议栈对其操作
(3) struct skb_shared_info –作为header data的补充,用于存储ip分片,其中sk_buff *frag_list是一系列子skbuff链表,而frag[]是由一组单独的page组成的数据缓冲区
skb buff结构图如下:

struct skb_buff
表示接收或发送数据包的包头信息,其成员变量在从一层向另一层传递时会发生修改。例如L3向L2传递前,会添加一个L3的头部,所以在添加头部前调用skb_reserve在缓冲区的头部给协议头预留一定的空间;L2向L3传递时候,L2的头部只有在 网络驱动处理L2的协议时有用,L3是不会关心它的信息的。但是,内核不会把L2的头部从缓冲区中删除,

sk_buff->h
sk_buff->nh
sk_buff->mac
指向TCP/IP各层协议头的指针:h指向L4(传输层),nh指向L3(网络层),mac指向L2(数据链路层)。每个指针的类型都是一个联合, 包含多个数据结构,

sk_buff->head
sk_buff->data
sk_buff->tail
sk_buff->end
表示缓冲区和数据部分的边界。在每一层申请缓冲区时,它会分配比协议头或协议数据大的空间。head和end指向缓冲区的头部和尾部,而data和 tail指向实际数据的头部和尾部。每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。数据部分会在尾部包含一 个附加的头部。
下图是TCP(L4)向下发送数据给链路层L2的过程。注意skb_buff->data在从L4向L2穿越过程中的变化

几个len的区别?
(1)sk_buff->len:表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。比如,处在网络层,len指的是ip包的长度,如果包已经到了应用层,则len是应用层头部和数据载荷的长度。

(2)sk_buff->data_len: data_len只计算分片中数据的长度,即skb_shared_info中有效数据总长度(包括frag_list,frags[]中的扩展数据),一般为0

(3)sk_buff->truesize:这是缓冲区的总长度,包括sk_buff结构和数据部分。如果申请一个len字节的缓冲区,alloc_skb函数会把它初始化成len+sizeof(sk_buff)。当skb->len变化时,这个变量也会变化。

通常,Data Buffer 只是一个简单的线性 buffer,这时候 len 就是线性 buffer 中的数据长度;
但在有 ‘paged data’ 情况下, Data Buffer 不仅包括第一个线性 buffer ,还包括多个 page buffer;这种情况下, ‘data_len’ 指的是 page buffer 中数据的长度,’len’ 指的是线性 buffer 加上 page buffer 的长度;len – data_len 就是线性 buffer 的长度。

二. sk_buff结构操作函数
内核通过alloc_skb()和dev_alloc_skb()为套接字缓存申请内存空间。这两个函数的定义位于net/core/skbuff.c文件内。通过这alloc_skb()申请的内存空间有两个,一个是存放实际报文数据的内存空间,通过kmalloc()函数申请;一个是sk_buff数据结构的内存空间,通过 kmem_cache_alloc()函数申请。dev_alloc_skb()的功能与alloc_skb()类似,它只被驱动程序的中断所调用,与alloc_skb()比较只是申请的内存空间长度多了16个字节。

内核通过kfree_skb()和dev_kfree_skb()释放为套接字缓存申请的内存空间。dev_kfree_skb()被驱动程序使用,功能与kfree_skb()一样。当skb->users为1时kfree_skb()才会执行释放内存空间的动作,否则只会减少skb->users的值。skb->users为1表示已没有其他用户使用该缓存了。

skb_reserve()函数为skb_buff缓存结构预留足够的空间来存放各层网络协议的头信息。该函数在在skb缓存申请成功后,加载报文数据前执行。在执行skb_reserve()函数前,skb->head,skb->data和skb->tail指针的位置的一样的,都位于skb内存空间的开始位置。这部份空间叫做headroom。有效数据后的空间叫tailroom。skb_reserve的操作只是把skb->data和skb->tail指针向后移,但缓存总长不变。

运行skb_reserve()前sk_buff的结构

    sk_buff

———————- ———-> skb->head,skb->data,skb->tail
| |
| |
| |
| |
| |
| |
| |
| |
| |
——————— ———-> skb->end

运行skb_reserve()后sk_buff的结构

    sk_buff

———————- ———-> skb->head
| |
| headroom |
| |
|——————— | ———-> skb->data,skb->tail
| |
| |
| |
| |
| |
——————— ———-> skb->end

skb_put()向后扩大数据区空间,tailroom空间减少,skb->data指针不变,skb->tail指针下移。

skb_push()向前扩大数据区空间,headroom空间减少,skb->tail指针不变,skb->data指针上移

skb_pull()缩小数据区空间,headroom空间增大,skb->data指针下移,skb->tail指针不变。

skb_shared_info结构位于skb->end后,用skb_shinfo函数申请内存空间。该结构主要用以描述data内存空间的信息。

——————— ———–> skb->head
| |
| |
| sk_buff |
| |
| |
| |
|———————| ———–> skb->end
| |
| skb_share_info |
| |


skb_clone和skb_copy可拷贝一个sk_buff结构,skb_clone方式是clone,只生成新的sk_buff内存区,不会生成新的data内存区,新sk_buff的skb->data指向旧data内存区。skb_copy方式是完全拷贝,生成新的sk_buff内存区和data内存区。。

三. skb 的分配细节
1. 关于 SKB 的分配细节.

LINUX 中 SKB 的分配最终是由函数 : struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,int fclone) 来完成.
SKB 可以分为 SKB 描述符与 SKB 数据区两个部分,其中描述符必须从 CACHE 中来分配 : 或者从skbuff_fclone_cache 中分配,或者从 skbuff_head_cache 中来分配.
如果从分配描述符失败,则直接反悔 NULL,表示 SKB 分配失败.

SKB 描述符分配成功后,即可分配数据区.
在具体分配数据区之前首先要对数据区的长度进行 ALIGN 操作, 通过宏 SKB_DATA_ALIGN 来重新确定 size 大小. 然后戏台调用 kmalloc 函数分配数据区 :
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
需要注意的是数据区的大小是 SIZE 的大小加上 skb_shared_info 结构的大小.

数据区分配成功后,便对 SKB 描述符进行与此数据区相关的赋值操作 :
memset(skb, 0, offsetof(struct sk_buff, truesize));
skb->truesize = size + sizeof(struct sk_buff);
atomic_set(&skb->users, 1);
skb->head = data;
skb->data = data;
skb->tail = data;
skb->end = data + size;
需要主意的是, SKB 的 truesize 的大小并不包含 skb_shared_info 结构的大小. 另外,skb 的 end 成员指针也就是skb_shared_info 结构的起始指针,系统用
一个宏 : skb_shinfo 来完成寻找 skb_shared_info 结构指针的操作.

最后,系统初始化 skb_shared_info 结构的成员变量 :
atomic_set(&(skb_shinfo(skb)->dataref), 1);
skb_shinfo(skb)->nr_frags = 0;
skb_shinfo(skb)->tso_size = 0;
skb_shinfo(skb)->tso_segs = 0;
skb_shinfo(skb)->frag_list = NULL;
skb_shinfo(skb)->ufo_size = 0;
skb_shinfo(skb)->ip6_frag_id = 0;

最后,返回 SKB 的指针.

  1. SKB 的分配时机
    SKB 的分配时机主要有两种,最常见的一种是在网卡的中断中,有数据包到达的时,系统分配 SKB 包进行包处理; 第二种情况是主动分配 SKB 包用于各种调试或者其他处理环境.

  2. SKB 的 reserve 操作
    SKB 在分配的过程中使用了一个小技巧 : 即在数据区中预留了 128 个字节大小的空间作为协议头使用, 通过移动 SKB 的 data 与 tail 指针的位置来实现这个功能.

  3. SKB 的 put 操作
    put 操作是 SKB 中一个非常频繁也是非常重要的操作, 但是, skb_put()函数其实什么也没做!
    它只是根据数据的长度移动了 tail 指针并改写了 skb->len 的值,其他的什么都没做,然后就返回了 skb->data 指针(就是 tail 指针在移动之前的位置). 看上去此函数仿佛要拷贝数据到 skb 的数据区中,其实这事儿是 insl 这个函数干的,跟 skb_put() 函数毫不相关,不过它仍然很重要.

  4. 中断环境下 SKB 的分配流程
    当数据到达网卡后,会触发网卡的中断,从而进入 ISR 中,系统会在 ISR 中计算出此次接收到的数据的字节数 : pkt_len, 然后调用 SKB 分配函数来分配 SKB :
    skb = dev_alloc_skb(pkt_len+5);
    我们可以看到, 实际上传入的数据区的长度还要比实际接收到的字节数多,这实际上是一种保护机制. 实际上,在 dev_alloc_skb 函数调用 __dev_alloc_skb 函数,而 __dev_alloc_skb 函数又调用 alloc_skb 函数时,其数据区的大小又增加了 128 字节, 这 128 字节就事前面我们所说的 reserve 机制预留的 header 空间.

四. 不同情况下构造skb数据包的实现

http://blog.csdn.net/efan_linux/archive/2009/09/23/4580024.aspx

在我这个网络接口的程序中(can0),其实难点就是怎样组包。怎样在原来数据包的基础加上自己的数据,怎样构造ip头,怎样构造udp头。

调试了两个星期,终于是调通了,在这个过程中,通过看内核源代码和自己组包的尝试,大概对组包的方法有了些了解,记录在此,留做备忘,也希望能给需要这方面信息的朋友一点帮助吧。

1,正常网卡收到数据包后的情况:

她的工作就是剥离mac头,然后给一些字段赋值,最后调用netif_rx将剥离mac头后的数据报(比如ip数据包)发送到上层协议。由协议栈处理。在此以ldd3中的snull为例,虽然snull跟硬件不相关,但这个过程都是类似的。

struct sk_buff *skb;
    struct snull_priv *priv = netdev_priv(dev);


    skb = dev_alloc_skb(pkt->datalen + 2);
    if (!skb) {
        if (printk_ratelimit())
            printk(KERN_NOTICE "snull rx: low on mem - packet dropped/n");
        priv->stats.rx_dropped++;
        goto out;
    }
    skb_reserve(skb, 2); /* align IP on 16B boundary */
    memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);

    /* Write metadata, and then pass to the receive level */
    skb->dev = dev;
   skb->protocol = eth_type_trans(skb, dev);
    skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += pkt->datalen;
    netif_rx(skb);

注意:上面代码中红色放大的地方是重要的。

因为此刻收到的数据包的格式如下:mac+ip+udp/udp+data

这时候的处理就是剥离mac头,然后需要更新的一些域值。这些都是在函数eth_type_trans函数里做的。需要注意的是,skb->dev = dev;这条语句是很重要的,如果没有此语句,将会导致系统错误而死机(至少在我的板子上是这样的)。

注意:eth_type_trans()函数主要赋值的是:

skb->mac.raw,skb->protocol和skb->pkt_type。见下面的代码有无mac头的情况。

2,完全从一个字符串开始构造一个新的skb数据包。

以前只是看过如何修改数据包,自己构造数据包,这还是头一次,刚开始确实给我难住了,来来经过看内核代码和自己摸索,我自己写的代码如下:

/假设:data是一个指向字符串的指针,data_len是data的长度/

struct ipv6hdr *ipv6h;
struct udphdr *udph;
struct sk__buff * new_skb;

int length = data_len + sizeof(struct ipv6hdr) + sizeof(udphdr);
new_skb = dev_alloc_skb(length);
if(!new_skb)
    {
        printk("low memory.../n"):
        return -1;
    }

skb_reserve(new_skb,length);

memcpy(skb_push(new_skb,data_len),data,data_len);

new_skb->h.uh = udph = (struct udphdr *)skb_push(new_skb,sizeof(struct udphdr));
memcpy(udph,&udph_tmp,sizeof(struct udphdr)); //注意,此刻我的udph_tmp是在另一个过程中截获的数据包的udp头,如果完全是自己构造数据包,则需要自己填充udp数据头中的字段。


udph->len = .............. ; //此处需要给udph->len赋值。注意udph->len是__u16的。存储时是高低位互换的,所以你应该先将你要更新的数字编成16进制的数,然后高低位互换,在赋值给udh->len。

udplen = new_skb->len;

new_skb->nh.ipv6h = ipv6h = (struct ipv6hdr *)skb_push(new_skb,sizeof(struct ipv6hdr));
memcpy(ipv6h,&ipv6h_tmp,sizeof(struct ipv6hdr)); //同udp头注释。

ipb6h->payload_len = ..........; //此处同udph->len.需要注意的是,此处所指的长度并不包括ipv6头的长度,而是去掉ipv6头后的长度。

udph->check = 0;
udph->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, udplen, IPPROTO_UDP, csum_partial((char *)udph, udplen, 0));

///////////////注意,如果是ipv4,则还需要计算ip校验和,但此处是ipv6,不用计算ip检验和,所以此处没有ipv6头的校验。//////////////////////////

new_skb->mac.raw = new_skb->data; //因为无mac头
new_skb->protocol = htons(ETH_P_IPV6); //表明包是ipv6数据包
new_skb->pkt_type = PACKET_HOST; //表明是发往本机的包

new_skb->dev = &can_control; //此处很重要,如果没有这条语句,则内核跑死。至少在我板子上是这样的。can_control是我的net_device结构体变量。

netif_rx(new_skb);

3,当需要改变原有skb的数据域的情况。

此时,有两种办法:
可以先判断skb的tailroom,如果空间够大,则我们可以把需要添加的数据放在skb的tailroom里。如果tailroom不够大,则需要调用skb_copy_expand函数来扩充tailroom或者headroom。

例如我们需要在skb的后面加上一个16个字节的字符串,则代码类似如下:

if(skb_tailroom(skb) < 16)
{
    nskb = skb_copy_expand(skb, skb_headroom(skb), skb_tailroom(skb)+16,GFP_ATOMIC);
    if(!nskb)
    {
        printk("low memory..../n");       
        dev_kfree_skb(skb);
        return -1;
    }

    else
    {
        kfree_skb(skb); // 注意,如果此时是钩子函数钩出来的,则skb不能在这里释放,否则会造成死机。

        skb = nskb;
    }

    memcpy(skb_put(skb,16),ipbuf,16); //ipbuf为要加到skb后面的字符串


    udplen = skb->len - sizeof(struct ipv6hdr);

    udph->len += 0x1000; //换成十进制为 + 16

    ipv6h->payload_len += 0x1000;

    udph->check = 0;
    udph->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, udplen, IPPROTO_UDP, csum_partial((char *)udph,udplen,0));   

    skb->mac.raw = new_skb->data; //因为无mac头

    skb->protocol = htons(ETH_P_IPV6); //表明包是ipv6数据包

    skb->pkt_type = PACKET_HOST; //表明是发往本机的包


    skb->dev = &can_control; //此处很重要,如果没有这条语句,则内核跑死。至少在我板子上是这样的。can_control是我的net_device结构体变量。


  netif_rx(skb);
}

注意:当调用skb_copy_expand或者修改了skb的数据域后,一定要更新udph->len和ipv6h->payload_len。否则上层应用(比如udp套接字)收到的数据包还是原来的数据包而不是修改后的数据包,因为udph->len的原因。

猜你喜欢

转载自blog.csdn.net/u010243305/article/details/55256361