虚拟网络适配器的实现

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 虚拟网络适配器的实现

什么是服务器?

关于服务器,有这么三层概念都被称为是对的,在不同的场景里面说,

第一种被称为服务器,就是说的物理硬件机房,服务器物理硬件这是第一种。

第二层在服务器中除了这个概念之外,还有大家比如说像windows做桌面,然后linux做服务器,那其实这个服务器另一层意思是一定意义上操作系统

第三个在操作系统里面提供一个应用程序,对外提供网络服务,对外提供网络应用服务

那现在这三层的概念又会慢慢的衍生出来了,

对外提供服务可能有些机房对外提供服务,云计算这个概念,那对外提供物理硬件这个叫laaS,

这是物理硬件这是第一种。

第二种对外提供操作系统这种叫做PaaS平台

什么意思?呢就是我买的云主机它给你提供的这个操作系统真的它有提供物理机吗?没有

大家买的云主机也好,在那个阿里云淘宝云腾讯云上面买的,他给你提供的是PaaS平台,是操作系统,

当然还有一部分你买现成的服务,在公司做这种在线教育,给你提供一个或者说你公司做银行业务,给你提供或者一些智能交通或者物联网相关行业,给你提供一个应用程序,这个被叫做SaaS平台叫做软件服务,所以这三层与之对应的它都被叫做服务器。

这三个对外提供服务核心的点它是需要有网络的支撑,服务器核心来说它是通过网络去访问的,

不管你是物理硬件的服务也好,他要对外能提供网络服务

核心的一个点就是在网络上面。

那这个网络网络东西就是说我们一款服务器

不管哪一款服务器,对外客户端不断的去访问,他访问第一层级注意,网络能够访问到服务器上面,网络能够访问到服务器上面。

首先作为一款机器而言,作为一款服务器而言,它为什么能够去响应服务?

为什么能响应网络?为什么它能够处理网络?它为什么它能处理?就像我们PC机为什么可以上网,

因为这里有一个非常重要的东西被叫做网卡,首先记清楚网卡它是一个综合类一个名词,

它不是一个独一的

网卡这东西它就好比人类一样,它是一种综合的一大类的东西,它不是某一个它是一个概名词,是一个概述的总体上

就是这网卡不管哪一家合在一起,被统称为网卡

那网卡的作用是什么?

那我上网它就提供了网络服务呗。

这是个业余的回答,那专业点的回答呢

网卡的作用是什么呢,就是对外它是负责将网络数据,就在网线里面,在光线里面,它传的光信号或者在双轨线里面传输的电信号,然后把它通过AD转化或者DA转化转化到我们的PC机的CPU,能够处理,也就是说通过的是AD/DA

光纤里面的这个光信号怎么能转换到我们CPU里面运行呢?

光纤里面的这个光它不是数字信号,它是模拟信号,那也就是说模拟信号通过网卡的转化,它把这个模拟信号转化为数字信号,然后再送给我们CPU进行处理,这就是网卡的作用,
然后当我们发送的时候,就是把数字信号集成转化为光电信号,把它发送出去,然后对端能收到

它不就跟调制解条一样吗?
把网线里面的信号转换为数字信号,它不是猫做的事情吗?
包括我们调制解调做的事情吗?

只要通过网线的传输都是模拟信号包括光纤传输,那么CPU怎么处理?都是通过网卡转化为我们的数字信号

那这里面这个调制解调,就是大家可能知道我们通过一个猫,然后家里再放一个路由器,那个猫对外进行拨号,然后把这个信号转发放到我们的路由器上面,我们再接上我们的PC机是这么一个情况,

就是一个猫,然后再加上一个路由器,然后再加上我们PC机是这样一个模式,

这里面从这个猫这个地方进行拨号到路由器这个过程中间,通过的网线它传输的也是模拟信号,

只是这模拟信号猫这东西起到一个拨号的作用,把一些高频传输频率比较高的降频,起到一个这么作用,然后在路由器传到我们PC机里面,它也是通过网卡把它转成数字信号,CPU能够处理

这里一大堆这里一大堆,

eth0,eth1,eth2

它与网卡是什么关系?

它是不是网卡?你说是吧?从一定意义上又是,你是不是吧?又强行说它不是。

我们接下来探讨一个哲学问题,这个东西你说它表面上像你说他是,他不是

记清楚它不是,首先网卡它是个物理硬件是我们能摸得到的,能看得见的,那eth0这个东西,它叫做网络适配器。

什么叫做网络适配器?

首先重点是网络,然后是适配器,适配器是什么东西?

适配器模式它能够适配,根据不同的网卡适配

也就是说这个eth0叫做网络适配器,它能够去适配所有的网卡,请注意这个意思就是eth0,eth1它不是网卡,它是网卡的适配器

它是网卡的适配器,什么叫做网卡的适配器?就是说它是通过软件来实现的适配所有网卡的一个组件

至少,绝大多数现有的网卡适配

那这个适配是通过软件通过我们代码去实现适配我们的网卡。

好,请表也就是什么意思?

也就是这个网卡这个网络识别器是通过我们代码,就相当于我们网络适配器就是一个类,然后我们某一个网卡在使用的时候,那这个网卡它是个实例

比如说我们使用某某网卡,然后通过我们的网络适配器实例出来一个对象,然后提供服务

网卡到底有多少?

做为一个厂家做一个生产网卡产品,就是做一个他们单独出厂,单独的就跟我一个路由器,一个手机一样,他能够用的

那现在作为一个eth0网络适配器,

他如何去适配所有的网卡?

现在肯定有听说过面相对象的这个东西,现在站在一个面向对象的思维上面,或者比如现在派你去做Linux网络子系统,你会如何去做?

你首先你凭自己的直觉,凭着自己的感觉,

那肯定知道面向对象一个网卡一个网卡,因为我抽出了一些特定的方法,设定固定的接口,没错,抽象的接口没错,确确实实是这样。

比如说我们抽象出来,这里有两类需要我们抽象,一类是所说的属性,

什么叫做属性?比如说一个人你的名字、身高、体重、身份证号码,这种都是属性

网卡同样如此它的属性,比如说mac地址ip地址,包括MTU,包括它的出厂,包括它的名字这些都属于网卡的属性。

还有一类呢属于方法,就是我们所说的接口,

那网卡也同样如此,有一些方法,比如我们发送接收,比如我们修改我们的一个参数,这些都是方法。

好没错,就是有两类,那我们抽象的时候我们就是两类,一类是属性一类是方法

那这两类分别把它放在那里?

内核里面提供出来两个结构体,一个是用来存属性的叫做net_device

第一类它应该存什么?

net_device,从它名字上面就可以看出net网络的意思,device设备,网络设备这里面它主要是存储它的属性,那方法呢这里有个net_device_ops存储方法,一类是属性一类是方法

关于用c如果去封装类的做法,比如说我们用c的方法去封装类,

struct只是说了表象,肯定是struct,但是这里面有两类,其核心的东西没有,

一个是属性怎么存,一类是方法怎么存。

通过回调函数,用struck是对的,回调函数也是对的,

重点是

你既然面向对象,你的属性怎么存你的方法怎么存?

当然肯定有朋友就直接上来就说了,我们一个结构体既存储属性又存储对应的方法,我相信很多朋友是这么想的,我认为这种想法一开始你可能是对的,

但是在这里挺重要的,

这个过程中间,你的属性和方法根本就没有做到一个分离,

核心的做法,就是可以用一个结构体专门定义属性,一个结构体专门定义方法,这两者之间可以做一个关联是可以的。

这里两个,一个叫做net_device,还有一个net_device_ops,

网络设备的核心

这个结构体一定要深刻的记住这里面的原理要自己能说的清楚。

就是net_device实例化一次就对应一个网络适配器

也就是在换一句话说,有多少个eth0,eth1就有多少的网络适配器,他们是一对一的,

也就是说这里的每一个属性都会被存储到这个net_device里面

这是第一个结构体,也就是说这个网卡的这个网络适配器的核心的东西就是net_device一对一,中间至于它的方法,

在我们系统里怎么加入一个适配器,这个open可能就中间就很难去体会它的初始化跟它有什么区别,

关于c里面如何面向对象,如何用方法跟它关联起来了

我们在最开始的时候,定义这么一个变量,这个变量就一个下划线,这个下划线没有什么特殊意思,它用来做什么?它用来指向我们这个 ctx

那叫就是用它来指向这个 CTS好,这一点不是重点,重点是在我们的方法里面如何去访问这个

ctx

这里就出现一个现象,比如说我们现在有个方法,ops等于这个ctx_ops的一个实例对象,

这个包含存储方法方法没写,我们只是把第一项把第一个域我们用一个*号指向它。

我们在这访问的时候,这里借助一个ops指向这个ctx_ops类地址,在这里我们怎么去指向它的第一个项?

我们再取一级指针,

也就是说指针*ops里的值存的就是struct ctx的内容了

这个用法是在很多地方有用到的

核心我们实现一个网络适配器

直接执行的时候,eth4会发现这里出现了一个小故障,就是告诉我们没有对应的接口没有对应的设备

其核心的原因是没有net_device,比如说内核里面我们现在只提供4个net_device,如果再加一个加不了,从eth0~3共4个,

那我们现在想加1个eth4进去的话,5个,现在发现net_device内核里面只有4个,这个链表里面只有4个

那我们insmod插入一个进去在试试

我们再看一下eth4有没有?

ip地址我们也是可以改的

eth4与哪个网卡对应上?

先讲讲docker这个东西,当我们在安装docker之后,在整个虚拟机里面,服务器上面,它会产生一个docker0

那这个docker0是怎么产生的呢?

跟这个一样insmod一个模块加入进去,docker0就这么产生的,

它跟哪个物理网卡对应上呢?

它同样如此,它是虚拟出来的

那接下来我们可以看一下这个技术这个东西,我们windows上面,

对应这两个VMnet它也是虚拟出来的,那么问题来了不是一个net_device对应一个网卡一个net_device对应一个网卡吗?它没错,它是可以虚拟的

那这个虚拟怎么理解呢?

就是我们可以通过软件,网络适配器的模块去定义一些,那这个网卡数据到底从哪里走呢?

请注意如果我们不做硬件的固化的话,也就是说这个网卡的地址,如果我们不去跟PCI的地址,

什么叫做PCI地址呢?这里有个lspci

如果我们写的这个模块,刚刚这个模板把它插入进去,不去跟这个物理不去跟PCI地址做一个绑定,不去跟PCI地址合在一起,我们不从PCI地址去取数据,这时候我们拿的不是固定的网卡的值,它拿的是一个网络虚拟的适配器

什么叫做PCI呢?

也就是我们所有网卡的地址,比如说现在

1个网卡、2个网卡、3个网卡、4个网卡,这4个网卡怎么工作呢?

请注意这里的网卡是说的具体的网卡,就是说

他是个实实在在的物理硬件,我们把网卡放到哪呢?放到一条总线上面,这条总线叫PCI,

这个 PCI它的特点是什么?

你插入进去它有个地址,就好比是从北京到广州的那个高速公路一样,你比如说由于某种特殊需求,你可以在上面修一个高速出口,那这个出口它就有个地址,那这个地址就叫做PCI地址,那这个 PCI地址的数据放到哪里去了呢,这个数据是送到我们CPU里面去执行的

如果我们写的这个网络适配器net_device,我们不做PCI地址的捏合绑定的话,那这时候我们做的它是一个我们内部可用的,

也就跟我们的docker0是一样的,

比如说我们现在有多个进程,

通过网络通信比如我做一个eth4专门用于我们内部进程做网络通信划分的一个子网,

如果我们跟PCI地址一绑定,那确确实实需要一个固定的网卡地址

这个docker0就是这个意思,他也没跟PCI绑定,它也没固定的物理地址,所以它是一个用在虚拟机之间通信的东西。

关于网卡这种虚拟设备重点是在哪?

不是要去做这个东西,而是清楚这个数据从网络适配器,如何把它运出来的,也就是这个 Nic接收的数据处理流程是怎样的?

核心流程分4部分

先把代码跑起来,大家再去跟大家总结的流程。

每一个网卡对应的东西其核心的东西就是net_device,

首先不管怎么样,我们先准备一个net_device对象。

第一步对了第一步

我们同样还是这么操作,所有的内核模块,

从我们讲fuse开始,到我们的无存储模块那个模块里面,

他之所以能工作,他是首先需要insmod到模块里面的。

所有模块它有两种工作场景,就是它有两个入口,nginx的那个模块它有三个工作场景,三个入口函数,一个是在解析配置文件,一个是在启动的时候,一个是在访问的时候,

那内核的模块呢?两个地方,一个是insmod插入进去的时候,它会走内核源码的代码里面,这个东西它并不是入口它没有入口

还有一步就是当我们正在运行的时候我们send,recv发送数据接收数据的时候它会走到

我们在insmod插入进来的时候,这是在为这个模块做一些准备,

我们正在运行的时候接收数据发送数据

insmod这里面核心的流程就是分配net_device,对net_device进行初始化

我们写一个单独的分配net_device

不要以为这个分配net_device它里面很复杂,这些内核里面都是提供出来一些东西的,

__init是什么意思

就是把这段代码放到这个在初始过程中间的这个 text变量,就是放到我们代码段里,

这个代码段不应该是固定的吗?

进程,如果我们写应用程序的时候,我们的代码段是放在哪里的呢?拿出一个进程空间,

从0x0804800这个地址开始,这个地方是代码段,这是我们的用户空间,那内核里面,当然也有自己固定插入代码的地方。

所以这个宏定义其核心就是对我们所说的,我们把这个东西放到代码段里。

接下来分配一个net_device内核里面提供出来一组这样的接口,这个函数它的重点是什么意思呢?

这个分配的时候特别有意思的一点

我们一个网络适配器,我们想去分配一个东西肯定是malloc出来的,没错它也是malloc出来的,但是它重点,

是在于每一个网络适配器它的工作体量是不一样的,所以在这里面它需要带一个sizeof_priv,对应来说就是一个私有化空间的size

它用来做什么呢?

其核心的作用,就是一个网络适配器,在接受数据的时候,它里面处理的这个空间存到哪里,就是它,就是在分配的时候,我们分配1个空间来做这个事情,这个地方要理解就是一个特定的空间

我们分配的这个私有空间分为两部分

一个是接收一个是发送

所以我们定义两个

这样定义请注意这不是个最优的方法,这不是一个最优的方法。

最优的方法是什么?

我们用一个环形队列,一个ringbuffer

读一块空间接受一段数据,那我们接受一块放到环形队列里面就可以了。

这里我们对数据的存储这个就ok了

这里分配完了之后,我们要做的事情,就是为它命名,我们要做的事情就是做填空题,

有哪些填空题可以做呢?

比如mtu的值还有名字,当然还有MAC地址,还包括一些方法,

不要每一项都知道,因为net_device的版本它也会迭代

只要知道几个常用的,知道这个轮廓是怎么回事,中间有些东西你知道这个点在的地方,能查到ok就可以了。

第一个就是关于这个 MAC地址,我们可以自己去设,当然也可以通过这么一方法,

紧接着对里面做的事情,

这个点它的这个这个什么意思大家就别熟悉了,这就是对于我们看到一些属性对面的一些方法,我们都填空要做填空。

就是你的细节上面会做的更好

第一个初始化init

还有一个是 open

这两个有没有区别?一个是init,一个是open,那区别在哪呢?

在一定意义上这个初始化它的作用是干嘛?是给这个网卡上电的时候用的,就是这个网卡一旦开始工作的时候他初始化走的,不是 insmod用的,

insmod用的时候是在这个地方

而在这里是当这个网卡一旦开始工作上电的时候

这个 open呢

是当我们执行这么一个动作的时候 //ifconfig eth0 up

才执行的open。

第一个是先走nic_init,再走init再走open

这个open里面做什么工作?

虚拟文件系统VFS这一层,大量的工作已经做好了,唯一能做什么?

net_device实例和我们的私有数据关联,并把接收缓冲区和发送缓冲区分配出来,

关于这个tx_buf和这个rx_buf,它分配多大合适?

为什么不在初始化的时候分配呢?

在这个地方分配也是 ok,会出现一个现象,

我们刚开始插入ko文件,我们都没有工作,这俩个buffer我们就已经给他分配了,分配的内存在中间这个空档期里是没有用的

什么叫做他能够工作呢是通过这一句,nettif_start_queue(dev)使得net_device开始工作与之对应的这个 stop,我们反过来就可以了

vfs已经做的差不多了,咱们就是只需要去做这个 open本身和我们这个模块的一些特定化工作

最好的做法,我们定制一个label。

if如果有七八个,建议可以用,循环就不推荐

这个 net_device加到哪里去了?再加一个eth5跟前面的是什么关系

跟踪register_netdev

核心点,

是把它加入到了一个链表里面,

注册是把它加入到list_netdevice表里面注册之前里面有去设置它的一些相关的参数,注册的流程理清楚

剩下的该怎么发送,怎么接受

接收它是一个被动特点,接受的话是通过中断的方式调用到这个函数,接收的时候是需要我们去设置,

两个参数,一个叫做sk_buff,一个叫做net_device,

那么这个sk_buff是一个很重要的结构体,

这是网卡,不是网络识别器,这个数据从我那个 CPU里面,那从我们内存里面,它怎么把这数据运到我们网卡里面呢?

中间通过一条PCI总线,这上面运输的数据格式就是sk_buff

这个网卡就相当于一个矿,

就不断的这种矿车 sk_buff不断的在这网卡里面把数据运出来,就好比煤矿的煤怎么到达地面上来的?通过矿车, 那这个矿车运运到哪里呢?

运到有大片堆积的地上,然后再等着大货车来把它拖走,

为什么用这个为什么不直接从地上把它运出来,拖到用户家,效率不一样

从矿底把数据运到我们地面上面,这东西叫做矿车,这个sk_buff就相当于这个矿车,

从网卡里把数据运出来,运出来把数据放到哪来?放到那个tx_buff和rx_buff里面.它就相当于地上的一块堆积,

然后这个数据最后怎么运走,通过大卡车运走,那这个大卡车是什么?通过我们send/recv把数据从里面带走

很多东西很多东西

从上往下面看,让你最乍眼的一个东西,你就会发现next和prev这个好理解,不太好理解的是这里是个union联合体,联合体里面这里是个红黑树的节点

什么意思?

你说这个sk_buff矿车是个链表,我好理解,但是你现在跟我说他是个红黑树,是个红黑树的节点,难道这些矿车它用红黑树组织起来吗?

网络的数据在运输的过程中,数据到来的时候,可以用链表把它组合起来,同样我们可以按时间戳的那个值排序,

也就是说红黑树里面从中序遍历它是顺序的,我们只管从网卡里面运出来,根本就不管它是什么时候来的,全部运出,但是这个时间戳从矿底把数据运出来之前,我们就为这每个节点的时间戳打上。

我们不管那车先全部把它运出来,然后用红黑树的方式在我们send/recv buffer里面组织起来,这样的话我们就可以按照它的时间把它组织起来。

再往下面看,这些都不重要,那个红黑树是重点,

传输头,网络头,还有一个mac头,

这个 sk_buffer运出来的是一车数据,数据更多来说它是放到内存里面的,

比如说这个数据它传输它的以太网是从哪个地方开始,

它的网络层网络头是从那个地方开始的,以及它传输层的头从那个地方开始

那没错,这里这几项就是这么意思,那就也就是这一车煤这一些数据里面,放在内存里面中间那些地方是我们以太网头,哪一段是我们数据的头,哪一段是我们的mac头,这个东西在前期就准备好了。

这东西准备好了有什么用?

我们把它放在协议栈里面,我们也能够解析出来,

这个数据从网卡里面运出来的时候他还没有到达协议栈

协议栈还没有进行解析,也就是我们的协议栈,也就是我们TCP处理的流程还在后面。

从这里运出来之后,这个 nic它只是做一个数据存储而已,就可以接下来

这块内存,运出来都是以内存的方式,从网卡里面内存运出来之后,到达nic内存存储,接下来整个协议栈处理也是用的这块内存,这个地方没有在 copy,也就是这个地方mac那一段,ip头那一段,传输层那一段,协议栈里面解析的话,IP头数据拿出来进行解析,TCP的数据进行解析一层一层的解析

网络数据里面,sk_buff这个东西全部贯穿了从网卡数据出来后一直到协议栈解析,包括在send的时候sk_buff全程陪同,这就是sk_buff

tcp.c这是TCP/IP协议栈实现的地方,

也就是说从网卡里面接收数据出来之后,就用sk_buff存着运出来,放到协议栈里面同样还是sk_buff存着,这个sk_buff数据处理的流程里面,它贯穿的层数和流程是比较长的。

这个发送的地方我们怎么做?

第一个

从私有数据,把数据给copy出来,把里面的数据拿出来,请注意这两个参数

一个叫net_device,一个叫sk_buff,为什么它是这两个参数,为什么不是其他的?

其核心

net_device是用来做什么的?是我们用来获取这个网络适配器里面存储空间的,

这个net_device是nic和网卡之间

这个nic我们通过net_device这个地方拿到什么?拿到它的私有空间,拿到它的send_buff,其核心的作用是我们拿到它的send_buff,然后把这个数据放到sk_buff里面发出去,就核心作用我们就把它放到sk_buff里面

5元组跟这个 sk_buff有什么关系,首先记清楚5元组是从哪里来的?是在协议栈上面的产生的,这个5元组的数据来源哪?sk_buff里面解析出来的,也就是说sk_buff的数据,它存储的是原数据包,一个一个的

协议栈里面把它数据解析出来,五元组里面数据哪里来的是从sk_buff里面解析出来的

这里这个发送的过程呢,也得给大家解释一下,就是这所谓的发送,我们其实所做的,就是把这数据准备好,就是说把sk_buff里面的数据填充好,

现在我们这里有个tx_buf,

指向是最开始的位置,那就根据网络7层模型里面,最外面的以太网头,里面存储的是14个字字,6个字节的原mac地址,6字节的目的mac地址以及2个字节的类型,这里14个字节,再加上后面是20个字节的IP头,再下面就是传输层的头

那这里我们的意思是什么?这个 ip的值,我们如果要去赋值的话,那我们就把在tx_buf里面它就是原数据包,

之前理解的写业务代码完全不一样,

数据流程不难不难,很有可能出现一个问题check不对

协议章里面发的时候,IP层有个check, udp有,tcp也有,很容易出错。

请注意,这一步为什么前面的这个东西等于0再进行?

我不要上面这个等于0,直接下面这么写可不可以?

肯定不行。

**ip头的那个 check它的意思是什么?**它的意思是IP头的校验值,什么意思?其实默认的时候这个IP头里面原IP地址,目的IP地址以及前面的一些信息,这个check这一位它是通过0计算的,

零也就把这个check这个值是拿出来的,它等于0才计算出来的,前面这一位是必须要有的,也就是这个check值它是参与计算的,包括后面udp里面TCP里面都是需要的,就是你计算之前先把这个check置零

接下来就是写一些业务代码,比如说去添加一些东西或删除一些东西,或者你把IP地址可以做成什么样,你比如说我们现在可以开俩个一个eth0,eth1,这两个设置不同的IP地址,

你就发现这两个网络适配器就形成了一个环,也就是这里我可以开两个,把这个IP地址一更换就可以做出来。

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/inetdevice.h>
MODULE_AUTHOR("wangbojing");
MODULE_DESCRIPTION("Kernel module for nic");
MODULE_LICENSE("GPL");
#define MAX_ETH_FRAME_SIZE    1792
#define DEF_MSG_ENABLE      0xffff
struct nic_priv {
  unsigned char *tx_buf;
  unsigned int tx_len;
  unsigned char *rx_buf;
  unsigned int rx_len;
  u32 msg_enable;
};
static struct net_device *nic_dev[2];
static void dump(unsigned char *buffer) {
  unsigned char *p, sbuf[2*(sizeof(struct ethhdr) + sizeof(struct iphdr))];
  int i;
  p = sbuf;
  for (i = 0;i < sizeof(struct ethhdr);i ++) {
    p += sprintf(p, "%02X", buffer[i]);
  }
  printk("eth %s\n", sbuf);
  p = sbuf;
  for (i = 0;i < sizeof(struct iphdr);i ++) {
    p += sprintf(p, "%02X", buffer[sizeof(struct ethhdr) + i]);
  }
  printk("iph %s\n", sbuf);
  p = sbuf;
  for (i = 0;i < 4;i ++) {
    p += sprintf(p, "%02X", buffer[sizeof(struct ethhdr) + sizeof(struct iphdr) + i]);
  }
  printk("payload %s\n", sbuf);
}
static void nic_rx(struct net_device *dev, int len, unsigned char *buf) {
  struct sk_buff *skb;
  struct nic_priv *priv = netdev_priv(dev);
  netif_info(priv, hw, dev, "%s(#%d), rx:%d\n",
                __func__, __LINE__, len);
  skb = dev_alloc_skb(len+2);
  if (!skb) {
    netif_err(priv, rx_err, dev,
                  "%s(#%d), rx: low on mem - packet dropped\n",
                  __func__, __LINE__);
    dev->stats.rx_dropped++;
    return ;
  }
  skb_reserve(skb, 2);
  memcpy(skb_put(skb, len), buf, len);
  skb->dev = dev;
  skb->protocol = eth_type_trans(skb, dev);
  skb->ip_summed = CHECKSUM_UNNECESSARY;
  dev->stats.rx_packets ++;
  dev->stats.rx_bytes += len;
  netif_rx(skb);
}
static int  nic_open(struct net_device *dev) {
  struct nic_priv *priv = netdev_priv(dev);
  netif_info(priv, ifup, dev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
  priv->tx_buf = kmalloc(MAX_ETH_FRAME_SIZE, GFP_KERNEL);
  if (priv->tx_buf == NULL) {
    netif_info(priv, ifup, dev, "%s(#%d), cannot alloc tx buf\n",
                    __func__, __LINE__);
    return -ENOMEM;
  }
  netif_start_queue(dev);
  return 0;
}
static int nic_stop(struct net_device *dev) {
  struct nic_priv *priv = netdev_priv(dev);
  netif_info(priv, ifdown, dev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
  if (priv->tx_buf) {
    kfree(priv->tx_buf);
  }
  netif_stop_queue(dev);
  return 0;
}
static void nic_hw_xmit(struct net_device *dev) {
  struct nic_priv *priv = netdev_priv(dev);
  struct iphdr *iph;
  u32 *saddr, *daddr;
  struct in_device *in_dev;
  struct in_ifaddr *if_info;
  if (priv->tx_len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
    netif_info(priv, hw, dev, "%s(#%d), too short\n",
                   __func__, __LINE__);
    return ;
  }
  dump(priv->tx_buf);
  iph = (struct iphdr*)(priv->tx_buf + sizeof(struct ethhdr));
  saddr = &iph->saddr;
  daddr = &iph->daddr;
  netif_info(priv, hw, dev, "%s(#%d), orig, src:%pI4, dst:%pI4, len:%d\n",
                __func__, __LINE__, saddr, daddr, priv->tx_len);
  in_dev = nic_dev[(dev == nic_dev[0] ? 1 : 0)]->ip_ptr;
  if (in_dev) {
    //if_info = in_dev->ifa_list;
    for (if_info = in_dev->ifa_list; if_info; if_info = if_info->ifa_next) {
      *saddr = *daddr = if_info->ifa_address;
      ((u8*)saddr)[3]++;
      netif_info(priv, hw, dev, "%s(#%d), new, src:%pI4, dst:%pI4\n",
                        __func__, __LINE__, saddr, daddr);
      break;
    }
    if (!if_info) {
      dev->stats.tx_dropped ++;
      netif_info(priv, hw, dev, "%s(#%d), drop packet\n",
                        __func__, __LINE__);
      return ;
    }
  }
  iph->check = 0;
  iph->check = ip_fast_csum((unsigned char*)iph, iph->ihl);
  dev->stats.tx_packets ++;
  dev->stats.tx_bytes += priv->tx_len;
  nic_rx(nic_dev[(dev == nic_dev[0] ? 1 : 0)], priv->tx_len, priv->tx_buf);
}
static netdev_tx_t nic_start_xmit(struct sk_buff *skb, struct net_device *dev) {
  struct nic_priv *priv = netdev_priv(dev);
  netif_info(priv, drv, dev, "%s(#%d), orig, src:%pI4, dst:%pI4\n",
                __func__, __LINE__, &(ip_hdr(skb)->saddr), &(ip_hdr(skb)->daddr));
  priv->tx_len = skb->len;
  if (likely(priv->tx_len < MAX_ETH_FRAME_SIZE)) {
    if (priv->tx_len < ETH_ZLEN) {
      memset(priv->tx_buf, 0, ETH_ZLEN);
      priv->tx_len = ETH_ZLEN;
    }
    skb_copy_and_csum_dev(skb, priv->tx_buf);
    dev_kfree_skb_any(skb);
  } else {
    dev_kfree_skb_any(skb);
    dev->stats.tx_dropped ++;
    return NETDEV_TX_OK;
  }
  nic_hw_xmit(dev);
  return NETDEV_TX_OK;
}
static int  nic_validate_addr(struct net_device *dev) {
  struct nic_priv *priv = netdev_priv(dev);
  netif_info(priv, drv, dev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
  return eth_validate_addr(dev);
}
static int nic_change_mtu(struct net_device *dev, int new_mtu) {
  struct nic_priv *priv = netdev_priv(dev);
  netif_info(priv, drv, dev, "%s(#%d), priv:%p, mtu%d\n",
                __func__, __LINE__, priv, new_mtu);
  return eth_change_mtu(dev, new_mtu);
}
static int nic_set_mac_addr(struct net_device *dev, void *addr) {
  struct nic_priv *priv = netdev_priv(dev);
  netif_info(priv, drv, dev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
  return eth_mac_addr(dev, addr); 
}
static int nic_header_create (struct sk_buff *skb, struct net_device *dev,
         unsigned short type, const void *daddr,
         const void *saddr, unsigned int len) {
  struct nic_priv *priv = netdev_priv(dev);
  struct ethhdr *eth = (struct ethhdr*)skb_push(skb, ETH_HLEN);
  struct net_device *dst_netdev;
  netif_info(priv, drv, dev, "%s(#%d)\n",
                __func__, __LINE__);
  dst_netdev = nic_dev[(dev == nic_dev[0] ? 1 : 0)];
  eth->h_proto = htons(type);
  memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
  memcpy(eth->h_dest, dst_netdev->dev_addr, dst_netdev->addr_len);
  return dev->hard_header_len;
}
static const struct header_ops nic_header_ops = {
  .create = nic_header_create,
};
static const struct net_device_ops nic_netdev_ops = {
  //.ndo_init =  insmod sample.ko
  // open =  ifconfig eth2 192.168.3.123 up
  .ndo_open = nic_open,
  .ndo_stop = nic_stop,
  .ndo_validate_addr = nic_validate_addr,
  .ndo_start_xmit = nic_start_xmit,
  .ndo_change_mtu = nic_change_mtu,
  .ndo_set_mac_address = nic_set_mac_addr,
};
static struct net_device *nic_alloc_netdev(void) {
  struct net_device *netdev = alloc_etherdev(sizeof(struct nic_priv));
  if (!netdev) {
    pr_err("%s(#%d): alloc dev failed", __func__, __LINE__);
    return NULL;
  }
  eth_hw_addr_random(netdev);
  netdev->netdev_ops = &nic_netdev_ops;
  netdev->flags |= IFF_NOARP;
  netdev->features |= NETIF_F_HW_CSUM;
  netdev->header_ops = &nic_header_ops;
  return netdev;
}
static int __init nic_init(void) {
  int ret = 0;
  struct nic_priv *priv;
  pr_info("%s(#%d): install module\n", __func__, __LINE__);
  nic_dev[0] = nic_alloc_netdev();
  if (!nic_dev[0]) {
    printk("%s(#%d): alloc netdev[0] failed\n", __func__, __LINE__);
    return -ENOMEM;
  }
  nic_dev[1] = nic_alloc_netdev();
  if (!nic_dev[1]) {
    printk("%s(#%d): alloc netdev[1] failed\n", __func__, __LINE__);
    goto alloc_2nd_failed;
  }
  ret = register_netdev(nic_dev[0]);
  if (ret) {
    printk("%s(#%d): reg net driver failed. ret: %d\n", __func__, __LINE__, ret);
    goto reg1_failed;
  }
  ret = register_netdev(nic_dev[1]);
    if (ret) {
        printk("%s(#%d): reg net driver failed. ret:%d\n", __func__, __LINE__, ret);
        goto reg2_failed;
    }
  priv = netdev_priv(nic_dev[0]);
  priv->msg_enable = DEF_MSG_ENABLE;
  priv = netdev_priv(nic_dev[1]);
  priv->msg_enable = DEF_MSG_ENABLE;
  return 0;
reg2_failed:
  unregister_netdev(nic_dev[0]);
reg1_failed:
  free_netdev(nic_dev[1]);
alloc_2nd_failed:
  free_netdev(nic_dev[0]);
  return ret;
}
static void __exit nic_exit(void) {
  int i = 0;
  pr_info("%s(#%d): remove module\n", __func__, __LINE__);
  for (i = 0;i < ARRAY_SIZE(nic_dev);i ++) {
    unregister_netdev(nic_dev[i]);
    free_netdev(nic_dev[i]);
  }
}
module_init(nic_init);
module_exit(nic_exit);
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
MODULE_INFO(vermagic, VERMAGIC_STRING);
__visible struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
  .name = KBUILD_MODNAME,
  .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
  .exit = cleanup_module,
#endif
  .arch = MODULE_ARCH_INIT,
};
#ifdef RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif
static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
  { 0xad5b1da, __VMLINUX_SYMBOL_STR(module_layout) },
  { 0x38f19120, __VMLINUX_SYMBOL_STR(free_netdev) },
  { 0xc69fce39, __VMLINUX_SYMBOL_STR(unregister_netdev) },
  { 0x1cb03f31, __VMLINUX_SYMBOL_STR(register_netdev) },
  { 0x79aa04a2, __VMLINUX_SYMBOL_STR(get_random_bytes) },
  { 0xcbc7e656, __VMLINUX_SYMBOL_STR(alloc_etherdev_mqs) },
  { 0x6e4d4963, __VMLINUX_SYMBOL_STR(kmem_cache_alloc_trace) },
  { 0x9fd17501, __VMLINUX_SYMBOL_STR(kmalloc_caches) },
  { 0x37a0cba, __VMLINUX_SYMBOL_STR(kfree) },
  { 0xdb7305a1, __VMLINUX_SYMBOL_STR(__stack_chk_fail) },
  { 0xf12a7a4, __VMLINUX_SYMBOL_STR(netif_rx) },
  { 0xe1b8e70d, __VMLINUX_SYMBOL_STR(eth_type_trans) },
  { 0x3be67951, __VMLINUX_SYMBOL_STR(skb_put) },
  { 0x16f559a3, __VMLINUX_SYMBOL_STR(netdev_err) },
  { 0x22e1a0ee, __VMLINUX_SYMBOL_STR(__netdev_alloc_skb) },
  { 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) },
  { 0x91715312, __VMLINUX_SYMBOL_STR(sprintf) },
  { 0x3a96a0d9, __VMLINUX_SYMBOL_STR(__dev_kfree_skb_any) },
  { 0xcac4997a, __VMLINUX_SYMBOL_STR(skb_copy_and_csum_dev) },
  { 0xb25e2000, __VMLINUX_SYMBOL_STR(eth_mac_addr) },
  { 0x7fa473f1, __VMLINUX_SYMBOL_STR(eth_validate_addr) },
  { 0x6a7faa1f, __VMLINUX_SYMBOL_STR(eth_change_mtu) },
  { 0x69acdf38, __VMLINUX_SYMBOL_STR(memcpy) },
  { 0x288bba22, __VMLINUX_SYMBOL_STR(netdev_info) },
  { 0x4c41102d, __VMLINUX_SYMBOL_STR(skb_push) },
  { 0xbdfb6dbb, __VMLINUX_SYMBOL_STR(__fentry__) },
};
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
MODULE_INFO(srcversion, "E638D6C15D36C09100DC2FB");

总结

第一

扣住net_device

第二个

把sk_buff理解清楚

整个的流程包含哪些东西,总共一起4个函数

第一个初始化,初始化里面分配net_device,注册regist

第二发送就是数据来源于哪里,sk_buff怎么来的,sk_buff从协议栈里面来的,把它copy到tx_buff里面,它的工作就这样,接收也这样

也就是说现在的nic的整个流程是属于网卡和协议栈之间的关系,

也是对他数据

好,这就是nic的流程。

相关文章
|
4月前
|
存储 安全 网络安全
云计算与网络安全的博弈:保护数据在虚拟世界中的安全移动应用开发之旅:从新手到专家
【8月更文挑战第27天】随着云计算技术的飞速发展,企业和个人用户越来越多地将数据和服务迁移到云端。然而,这一转变同时带来了新的安全挑战。本文旨在探讨云计算环境下的网络安全问题,并分析如何通过技术和策略保障信息安全。我们将从云服务的基础知识出发,逐步深入到网络安全和信息安全的高级概念,最后讨论如何实施有效的安全措施来抵御网络威胁。文章不仅涵盖了理论框架,还提供了实际案例分析,旨在为读者提供一套全面的云计算安全指南。
|
4月前
|
JavaScript 网络协议 API
【Azure API 管理】Azure APIM服务集成在内部虚拟网络后,在内部环境中打开APIM门户使用APIs中的TEST功能失败
【Azure API 管理】Azure APIM服务集成在内部虚拟网络后,在内部环境中打开APIM门户使用APIs中的TEST功能失败
|
5月前
|
安全 网络安全 网络虚拟化
什么是VPN虚拟专业网络技术?
虚拟专用网络(VPN)技术利用互联网服务提供商(ISP)和网络服务提供商(NSP)的网络基础设备,在公用网络中建立专用的数据通信通道。VPN的主要优点包括节约成本和提供安全保障。
100 6
|
4月前
【Azure App Service】列举为App Service集成虚拟网络(VNET)操作时所需要的最小权限
【Azure App Service】列举为App Service集成虚拟网络(VNET)操作时所需要的最小权限
|
4月前
|
缓存 负载均衡 NoSQL
【Azure Redis】Azure Redis添加了内部虚拟网络后,其他区域的主机通过虚拟网络对等互连访问失败
【Azure Redis】Azure Redis添加了内部虚拟网络后,其他区域的主机通过虚拟网络对等互连访问失败
|
4月前
|
安全 网络安全
【Azure 环境】当本地网络通过ER专线与Azure云上多个虚拟网络打通,如何通过特定的网络策略来限制本地部分网段访问云上虚拟机22端口?
【Azure 环境】当本地网络通过ER专线与Azure云上多个虚拟网络打通,如何通过特定的网络策略来限制本地部分网段访问云上虚拟机22端口?
|
4月前
|
网络协议 安全
【Azure 应用服务】Azure Function集成虚拟网络,设置被同在虚拟网络中的Storage Account触发,遇见Function无法触发的问题
【Azure 应用服务】Azure Function集成虚拟网络,设置被同在虚拟网络中的Storage Account触发,遇见Function无法触发的问题
|
4月前
|
SQL 网络协议 安全
【Azure API 管理】APIM集成内网虚拟网络后,启用自定义路由管理外出流量经过防火墙(Firewall),遇见APIs加载不出来问题
【Azure API 管理】APIM集成内网虚拟网络后,启用自定义路由管理外出流量经过防火墙(Firewall),遇见APIs加载不出来问题
|
4月前
|
存储 缓存 NoSQL
【Azure Redis 缓存】Azure Cache for Redis 专用终结点, 虚拟网络, 公网访问链路
【Azure Redis 缓存】Azure Cache for Redis 专用终结点, 虚拟网络, 公网访问链路
|
4月前
|
域名解析 网络协议 API
【Azure 应用服务】App Service与APIM同时集成到同一个虚拟网络后,如何通过内网访问内部VNET的APIM呢?
【Azure 应用服务】App Service与APIM同时集成到同一个虚拟网络后,如何通过内网访问内部VNET的APIM呢?