串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)

今天咱们还是接着之前的文章往后继续:

在30天自制操作系统上编写网卡驱动[4]:寄存器控制网卡收发数据在30天自制操作系统上编写网卡驱动「3」:读取网卡的MAC地址在自制操作系统上写网卡驱动(2): 网卡的I/O配置 如何在自制操作系统写网卡驱动程序(1)

今天的pIPeline:

其实我们自己可以编写自己特有的协议按照UDP协议给数据添加端口号按照IP协议给数据添加IP地址使用以太网协议给数组指定网卡地址写代码生成DHCP discover信息并发送使用抓包软件wireshark监控发送过程

在之前的文章中,我们已经完成了对网卡8029的发送控制,可以把8029的数据发送出去了。

但是,要想使发送的数组S[60]能够与网络上的其他网卡进行沟通,必须按照一些特定的协议来整理数组S[60]。

网卡还没有分配IP地址,我们就按照DHCP协议来整理数组,希望路由器能够按照DHCP协议,给我们分配一个IP地址。

经过[4]的努力,我们已经生成了DHCP discover 数组,然后为了给数组增加端口号,又遵循UDP协议给数组添加了8个字节长度UDP header。

其实我们自己可以编写自己特有的协议

但是,现在的数组发送到网络上,仍然不会有网卡收取到。

因为虽然数组里已经用UDP协议包含了端口号,但是还没指定接收方的IP地址,接收方MAC地址,所以,没有网卡会对这个数组进行解读。因为网卡都是按照协议解读数据的,当网卡收到数据后,一查MAC地址,不是自己的MAC地址,就不会继续对数据进行解读了。

从这里,我们也明白,网卡其实可以收到网络上的任何数据,只不过,这个数据是否要进行分析,进行解读,就决定于数据里带没带我们想要的信息了。

所以,只要有网卡,我们就可以开发出sniffer的,可以收到网络上的任何数据的。

也就是说,其实我们可以不用UDP,IP,TCP,以太网等协议的,我们完全可以自己设计一些协议,来传输我们自己的信息。

只不过,现在公开的统一协议就是这些,比如我买的路由器上自带的协议,就是DHCP来分配IP地址,所以,我就只能按照DHCP协议规定的方式来制作数组了。

记住:协议是人定的,只要发送方和收取方知道协议中每个数字的意义, 就能够完成信息的交换。如果为了完成加密,现有的任何协议都可以不用。

比如我可以自己设置一些协议,专门用于自制操作系统之间的信息交换。

不过,因为网卡不是我们自己生产的,网卡传输的数据还是要按照以太网协议所规定的方式去组织。

所以,往数组里添加端口号,IP地址,MAC地址都是要遵守一些现成的协议。

添加端口号,要按照UDP协议规定的方式添加。添加 IP地址,得按照IP协议规定的方式添加。添加MAC地址,得按照以太网ethernet规定的方式添加。

按照UDP协议给数据添加端口号

在[4]中,我们按照UDP协议规定的方式,在DHCP discover数组前,加了8个字节:分别是0,0x44,0,0x43,0x1,0x2c,0,0x80.

其中0,0x44,两个字节,对应10进制为68,表示我们的发出端口为68.

其中0,0x43,两个字节,对应10进制为67,表示这条消息需要路由器上的端口为67的程序去处理.

后面四个字节分别为数据的长度(8 DHCP discover的字节数)和效验和。

具体写代码的时候,只要写一个8字节结构体,然后给这个结构体赋值就行了:

// 定义UDP Header的结构体 typedef struct{ short src_port; short dest_port; short length; short check_sum; } UDP_HEADER;

新加的这8个字节,称为UDP header.

UDP header 放在 DHCP discover数据的前面,得到的总数组,称为UDP数组。

//定义UDP数组对应的结构体,可以看到,UDP数组的前8个字节是UDP header struct UDP_PACKAGE{ UDP_HEADER header; DHCP_MESSAGE message; // 这就是我们的DHCP discover数组 }; // 定义出DHCP discover数组的结构体 typedef struct{ unsigned char op; unsigned char htype; unsigned char hlen; unsigned char hops; long xid; short secs; short flag; long ciaddr; long yiaddr; long siaddr; long giaddr; unsigned char chaddr[16]; unsigned char sname[64]; unsigned char file[128]; unsigned char magic_cookie[4]; unsigned char options[44]; unsigned char end[10]; } DHCP_MESSAGE;

那么今天,我们就继续按照IP协议对数组添加IP地址,按照以太网ethernet协议对数组添加MAC地址。

按照IP协议给数据添加IP地址

按照IP协议,在UDP数组前加上如下字段的数据即可:

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(1)

IP协议规定要添加的字段

这些字段就称为IP Header了,我们还是先按照这个 Header写一个结构体:

// IP header对应的结构体 typedef struct { unsigned char ip_and_headlength; unsigned char service_level; short length; short identify; short label_and_offset; unsigned char ttl; unsigned char protocal; short head_checksum; long src_ip; long dest_ip; } IP_HEADER; // 将IP header 放在UPD 数组前,构成IP数组 typedef struct { IP_HEADER header; struct UDP_PACKAGE udp; } IP_PACKAGE;

然后写个函数对IP header 赋值:

void init_ip_package(IP_HEADER * header){ /* unsigned char ip_and_headlength; unsigned char service_level; short length; short identify; short label_and_offset; unsigned char ttl; unsigned char protocal; short head_checksum; long src_ip; long dst_ip; struct UDP_PACKAGE udp; */ header->ip_and_headlength=0x45; header->service_level=0x00; //header->length=(sizeof(IP_PACKAGE)&0xff00)>>8 (sizeof(IP_PACKAGE)&0xff)<<8; header->length=hxl(sizeof(IP_PACKAGE)-4); //header->length=sizeof(IP_PACKAGE)-4; header->identify=0x3333; header->label_and_offset=hxl(0x1<<14);// 不分段 header->ttl=0x80; header->protocal=17;// 协议号是10进制的 header->head_checksum=0; header->src_ip=0x00000000; header->dest_ip=0xffffffff; }

ip_and_headlength=0x45,对应图中的第一个字节。这个字节的高4位是IP version ,即IP协议的版本,这里取4,就是指IPv4版本;低4位是长度值,不过这个长度值是只是添加的这个IP header的长度值,所以,称它为headlength,并且这个长度值的单位是双字,一个双字含有4个字节,那么headlength=5,即意味着这个IP header的长度为5个双字,就是5x4=20个字节。

service_level=0x00,对应图中的第2个字节,Type of Service,TOS, 字面意思是服务类型,其实这个字段固定了采用那种策略进行传输,是最小延迟,还是最高可靠度,还是最小费用,或者最大吞吐量?我们这里设置为0,即普通传输。想了解的更加详细,可以看这里:http://t.zoukankan.com/lsgxeva-p-12384641.html

length=hxl(sizeof(IP_PACAKGE)-4),对应图中的第3,4个字节,Total length,即 IP header UDP 数组的长度。这个长度我为什么减4?因为我发现结构体构造出来比原来多了4个0,所以就将这4个0减去了。另外使用的这个hxl是这样的:

unsigned short hxl(unsigned short length){ length=length&0x0000ffff; return ((length&0xff00)>>8 | (length&0xff)<<8) &0xffff; }

hxl交换的short类型的高8位和低8位。长度本身是一个字,即一个short,内存存储一个字时,是先存字的低8位,后存字的高8位。但是传输的时候,需要先传高8位,再传低8位,所以我们这里将高8位放在前面。其实这个变量定义为total_length更合适。

header->identify=0x3333,是IP header的第5,6个字节,即Identification,或者叫Fragment ID,字面意识是:身份标识符,或者分段的身份标识符。这个字段用来表示IP数组的身份?IP数组有什么身份呢?IP数组是用来传输我们想要传输的数据的,但是如果我们想要传输的数据过长,超过了IP数组所能承载的最大长度,比如total_length字段是16bits,

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(2)

16bits的最大长度是65536,如果我们想要传输的数组比这个长,还能不能用IP协议所规定的方式来给数组添加IP地址了?

答案是能。

但是,IP协议需要我们把数据分段传输,比如分成2段,每段数据前都加上IP header,然后分2次发送出去。那么分2次发送出去的数据,也是被分两次接收到的。这时候问题就来了?

接收方怎么知道这2次收到的数据应该合起来使用呢?

所以,为了能使接收方把2段数据合起来,我们需要给每次发送出去的数据一个身份:就是Identification,也可以直接叫Fragment ID。

这个值怎么给呢?为什么我们这里给了个0x3333? 因为将数组分段后,第一段的ID是随机值,第二段的ID是第一段的ID 1。

比如,如果我们要把数组分两段传输,那么数组的第一段再添加IP header的时候,identify我们随机给了一个0x3333,那么数组的第二段在添加IP header的时候,identify=0x3333 1=0x3334.

label_and_offset=hxl(0x1<<14);是IP header的第7,8个字节,即R,DF,MF,Fragment offset.这两个字节整体表明了关于数组分段传输的更多细节。它的第0到12个bit ,Fragment表示一个偏移量,什么的偏移量呢?比如我们正在给数组的第二段添加IP header,此时这个偏移量就要设置为数组的第二个段在整体数组的偏移量,其实也等于第一段的长度。

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(3)

我们这里直接给了一个零,表示偏移量为0,因为当前整体数组长度远远小于65536,所以只用一个段传输就够了,不用分段。所以我们只用把当前整体数组编号为第一个段即可。

另外,Fragment offset前的3个bit:R,DF,MF是什么呢?

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(4)

注意这里的bit是按照左边是bit0开始计算的

bit15,保留字段,称为R,设置为0

bit14,Dont Fragment,表示不分段,简称为DF,

bit13,More Fragment,表示分段为多段传输,简称为MF.

我们这里把bit14设置为1,即label_and_offset=hxl(0x1<<14);表示不分段。

ttl=0x80;第IP Header的第9个字节,Time to Live,简称ttl,表示当前IP数组经过0x80次路由器的转发后,就可以不再转发了。wiki上写的非常详细,如下:

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(5)

不再转发的路由器,还会发送一条按照ICMP协议写成的数组发送回来,告诉我们这条数据发送失败了。看来,我们后续还得写一些解析ICMP协议数组的函数来。

protocal=17;,对应IP header的第10个字节,这个为什么写17?因为我们把IP header加在了UDP数组前。UDP数组是按照UDP协议生成的。与UDP类似的协议还有很多,比如TCP,比如ICMP,比如CFTP等,如果我们把一个TCP数组前加上IP header,此时protocal应为0x06,如果是ICMP数组前加上IP header,此时protocal应为0x01,如果是CFTP,则protocal应为0x3E。这里有个表,涵盖了可以添加IP header的一些协议。当然,如有必要,咱们也可以字节定义一种协议,然后把协议号赋值给protocal .

协议与协议号对应的表为:

https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers

这里我截取了一部分:

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(6)

head_checksum=0;是IP header的第11,12个字节,它是个校验和,计算方法跟UDP中的一样。

src_ip=0x00000000;是IP header中的第13,14,15,16个字节,Source address,它应该填写发出这个IP数组的主机的IP地址,不过,因为我们目前还没有IP地址,所以我们写0,即0.0.0.0

dest_ip=0xffffffff;是IP header中的第17,18,18,20个字节,Destination address,表示主机的IP地址与dest_ip相同,或者dest_ip是专门用于广播的地址,主机才能够收到这个IP数组。我们这里需要所有的主机都能收到这个IP数组,所以把dest_ip设置为0xffffffff, 即255.255.255.255表示这个IP数组要广播出去,让所有的网卡都收到。

因为现在的网卡只有MAC地址,还没有IP地址,所以src_ip只能填写0,又因为需要路由器给分配一个IP地址,但是不知道路由器的IP,所以dest_ip就填写一个广播地址0xffffffff,这样路由器就会解析我么发出去的IP数组了。

到这里,我们把UDP数组前,加了20个字节的数据得到了IP数组。

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(7)

UDP数组加上IP头后得到IP数组

这20个字节中,后8个字节是src_ip,和dest_ip,占了20个字节中的一半。所以这里我们主要是为了给UDP数组添加IP的,那么前面的12字节分别设置了协议相关的信息,如ip version, protocal,设置了长度相关的信息,比如header length,total_length,设置了服务策略,比如ROS,设置了数组的最大路有转发次数TTL,设置了数据比较长时,需要分段传输时一些参数,比如R,DF,MF,Fragment Offset,Fragement ID等。

可以看到,这些字段设置了将IP数组以怎样的策略,怎样的分段方式,多大的路由器转发次数将IP数组从当前IP地址所在的网卡上发送到另外一个IP地址所在的网卡上。

这20个字节主要是用来传输数据的,所以这个协议才叫IP,internet protocal,互联网协议。

从这里也看到,IP协议是为了数据在网络中发送与接收的。一个任意的数据,只有加了IP 协议规定的IP头,就可以被dest_ip所指定的网卡去解析。至于这个数据是用来干嘛的,IP header里面不用做任何设定。

至此,我们给数组添加了端口号:68,67, ip地址:0.0.0.0, 和255.255.255.255,那么此时,数据就可以正确的发送给路有器的网卡了么?

还不能。

还需要添加MAC地址。

互联网上的网卡很多,到底哪块网卡可以接收我们发出的数据呢?我们是需要指定的。

指定网卡就需要用到网卡的唯一标识符:MAC地址。

使用以太网协议给数组指定网卡地址

指定网卡地址,需要按照下图的格式,在IP数组的前面添加上ethernet header:

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(8)

注意到,这里的 Destination Mac ,目的地的MAC,指定了IP数组应该又具有怎样MAC地址的网卡去解析。

Source MAC ,源MAC,就是当前咱们的网卡8029的MAC,

Ethertype or size字段,有两个意思:协议号,或者 数据长度。那么什么时候这个字段是指协议号呢?当这个字段的值小于1500的时候,那么它就是数据长度的意识。如果这个值大于等于1536,那么这个字段就表示协议号,可以表示哪些协议号呢?如下表:

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(9)

此表摘自:https://en.wikipedia.org/wiki/EtherType

为什么这个字段要这样安排呢?

EtherType values be greater than or equal to 1536. That value was chosen because the maximum length (MTU) of the data field of an Ethernet 802.3 frame is 1500 bytes and because the value is equvalent to the number 0x600 in the hexadecimal numeral system. Thus, values of 1500 and below for this field indicate that the field is used as the size of the payload of the Ethernet frame while values of 1536 and above indicate that the field is used to represent an EtherType.

因为按照ehternet协议,数据的长度的最大值为1500字节,16进制就是0x600.而协议号都是大于0x600的,比如IPv4协议,就是0x0800.

这两个不冲突,所以就用这一个字段来表以两种意义。

字段preamble是网卡自动添加的,我们不用管。

Payload指的就是IP数组。

后面的CRC,我们的8029网卡可以自动计算,也不用我们管。

所以,给IP数组添加MAC地址,实际上只用添加3个字段:Destination MAC,source MAC,ethernet type就行了,所以,我们编写了这样一个结构体:

// ethernet header typedef struct { unsigned short pre;// unsigned char dest_mac[6];// Destination Mac unsigned char src_mac[6]; // source MAc unsigned short ethernet_type; // ethernet type } ETHERNET_HEADER;

将这个结构体命名为ETHERNET_HEADER,即以太网头。

注意到,我定义的ETHERNET_HEADER是4个字段的,它还包括一个pre,这是因为我发现编译器会给这个结构体的前面自动添加两个0,以保证结构体在内存中的地址能被4整除。所以,我们用pre来表示结构体前面的两个0。

EHTERNET_HEADER 再加上IP数组就是可以用网卡发送的以太网ethernet数组,结构体为:

typedef struct { ETHERNET_HEADER header; IP_PACKAGE ip; } ETHERNET_PACKAGE;

注意到,我们的以太网数组ETHERNET_PACKAGE里。

那么我们这样对其进行初始化:

void init_mac_header(MAC_PACKAGE *mac){ unsigned char dest_mac[6]={0xff,0xff,0xff,0xff,0xff,0xff}; int i; for(i=0;i<6;i ){ mac->header.dest_mac[i]=dest_mac[i]; mac->header.src_mac[i]=GetRegisterValue(1,i 1); } mac->header.pre=0xff; mac->header.ethernet_type=0x0008; }

这里,我们将dest_mac的值设置为:0xff,0xff,0xff,0xff,0xff,0xff,0xff,即ff-ff-ff-ff-ff-ff,这个值也是个广播地址,表示所有的网卡都可以对这个ethernet数组进行解析。还是因为我们也不知道路由器的网卡地址是什么,所以需要指定dest mac是广播地址。

我们将src_mac的值是设置为8029网卡的MAC地址,而8029网卡的地址是存储在8029的寄存器中的,所以我们写了个函数GetRegisterValue去读取8029网卡的寄存器:

//读取第page页的第offset个寄存器的值 char GetRegisterValue(char page,char offset){ page_select(page); return io_in8(IOADDR offset); }

8029网卡的mac地址我们将其存储在了第1页的第1,2,3,4,5,6个寄存器里,如下图:

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(10)

Page1的 PAR0,PAR1,PAR2...PAR5就是8029网卡存储MAC地址的寄存器:

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(11)

8029网卡就是用寄存器PAR0-5里存储的MAC地址与收到的ethernet数组进行对比的,只有与PAR0-5相同的ethernet数组,8029网卡才接收。

不过,8029网卡也提供了寄存器去设置,可以接收任意的ethernet数组。

这样,我们就得到了添加过mac地址的ethernet数组,如下:

串口服务器如何通过wifi传数据(在30天自制操作系统上编写网卡驱动)(12)

可以看到,前面14字节,就是我们添加的ethernet header.

至此,我们在DHCP discover数组上添加了含有端口号的8个字节的UDP header,含有IP地址的20个字节的IP header, 含有MAC地址14个字节的ethernet header,得到具有端口号,IP地址,MAC地址的ethernet数组。

用我们的发送程序,把这个ethernet数组发送出去,路由器上的网卡就能够知道收到我们的数组,拿到DHCP discover信息,然后解析信息,然后明白我们是块新网卡,需要IP地址,随后就会给我们回复一个DHCP offer消息。

写代码生成DHCP discover信息并发送

最终生成DHCP discover信息,并发送出去的代码为:

DHCP_INFO dhcp_info={1,0}; MAC_PACKAGE *mac_p = create_dhcp_package(memman,&(dhcp_info)); int number=send_dhcp_message(((unsigned char * )mac_p ) 2,sizeof(*mac_p)-2);

这里,DHCP_INFO DHCP_info是一个结构体,存储了初始化DHCP discover信息时所需要的一些配置信息:

// DHCP_info结构体 typedef struct{ uchar message_type; // messge_type=1为discover信息 long client_ip_addr; // 当前网卡的IP地址 } DHCP_INFO;

所以,dhcp_info={1,0},指message_type=1, 即DHCP消息类型为discover, client_ip_addr=0,即当前没有ip地址.

然后将这两个信息输入到如下,init_message_package函数里就可以生成DHCP discover信息,

//初始化DHCP discover 的函数 void init_message_package(DHCP_MESSAGE * dd,DHCP_INFO * dhcp_info){ dd->op=1; dd->htype=1; dd->hlen=6; dd->hops=0; dd->xid=0x83208856; dd->secs=0; dd->flag=0; dd->ciaddr=dhcp_info->client_ip_addr;//使用dhcp_info里存储的ip地址来初始化DHCP_INFO dd->yiaddr=0; dd->siaddr=0; dd->giaddr=0; dd->chaddr[5]=GetRegisterValue(1,6); dd->chaddr[4]=GetRegisterValue(1,5); dd->chaddr[3]=GetRegisterValue(1,4); dd->chaddr[2]=GetRegisterValue(1,3); dd->chaddr[1]=GetRegisterValue(1,2); dd->chaddr[0]=GetRegisterValue(1,1); int i; //dd->sname=(unsigned char *)memman_alloc_4k(memman,64); for(i=0;i<64;i ) dd->sname[i]=0; for(i=0;i<128;i ) dd->file[i]=0; //dd->file=(unsigned char *)memman_alloc_4k(memman,128); dd->magic_cookie[0]=0x63; dd->magic_cookie[1]=0x82; dd->magic_cookie[2]=0x53; dd->magic_cookie[3]=0x63; //dd->options=options; uchar *cip = (uchar *)(&(dhcp_info->client_ip_addr)); unsigned char options[44]={ 0x35,0x01,dhcp_info->message_type,//3, 61,7,1,GetRegisterValue(1,1),GetRegisterValue(1,2),GetRegisterValue(1,3),GetRegisterValue(1,4),GetRegisterValue(1,5),GetRegisterValue(1,6),//9 50,4,cip[0],cip[1],cip[2],cip[3],//6 12,3,z,m,m,//5 60,3,r,l,k,//5 55,14,1,3,6,15,31,33,43,44,46,47,119,121,249,252//16 }; for(i=0;i<44;i ){ dd->options[i]=options[i]; } for(i=0;i<10;i ){ dd->end[i]=0x00; } dd->end[0]=0xff; }

那么在create_dhcp_package函数里,调用了init_message_package生成DHCP discover信息,调用了,init_ip_header,init_mac_header给DHCP discover信息填充了IP 地址,MAC地址等细心,在函数的第13-16行给DHCP discover信息填充了端口号,最终,create_dhcp_package函数返回了一个ETHERNET_PACKAGE,

ETHERNET_PACKAGE * create_dhcp_package(struct MEMMAN *memman,DHCP_INFO* dhcp_info){ // 给ethernet数组的结构体申请堆内存 ETHERNET_PACKAGE *mac_p= (ETHERNET_PACKAGE *) memman_alloc_4k(memman, sizeof(ETHERNET_PACKAGE)); // 初始化添加mac地址的ethernet header init_mac_header(mac_p); // 初始化ethernet数组中的 ip header init_ip_package(&((mac_p->ip).header)); // 计算IP header中的效验和 mac_p->ip.header.head_checksum=calculate_checksum((unsigned short *)(&(mac_p->ip.header)),sizeof(mac_p->ip.header)/2); // 初始化 IP 数组中的UDP header mac_p->ip.udp.header.src_port=hxl(68); mac_p->ip.udp.header.dest_port=hxl(67); mac_p->ip.udp.header.length=hxl(sizeof(mac_p->ip.udp)-4); mac_p->ip.udp.header.check_sum=0; // 初始化DHCP discover信息 init_message_package(&(mac_p->ip.udp.message),dhcp_info); // 计算UDP header中的校验和 //为了计算效验和,需要用udp伪头部狗仔一个数组 struct UDP_PERSDO_PACKAGE *udp_persdo=(struct UDP_PERSDO_PACKAGE *)memman_alloc_4k(memman,sizeof(struct UDP_PERSDO_PACKAGE)); udp_persdo->src_ip = 0x00000000; udp_persdo->dest_ip = 0xffffffff; udp_persdo->zeros=0x00; udp_persdo->protocal = 17; udp_persdo->length = sizeof(struct UDP_PERSDO_PACKAGE); udp_persdo->message=mac_p->ip.udp.message; mac_p->ip.udp.header.check_sum = calculate_checksum((short *)udp_persdo, sizeof(*udp_persdo)/2); // 因为不需要再使用了释放UPD伪头构造的数组的内存, memman_free(memman,&(udp_persdo),sizeof(*(udp_persdo))); return mac_p; }

网卡直接调用发送函数,来发送这个ETHERNET_PACKAGE就行了

DHCP_INFO dhcp_info={1,0}; MAC_PACKAGE *mac_p = create_dhcp_package(memman,&(dhcp_info)); int number=send_dhcp_message(((unsigned char * )mac_p ) 2,sizeof(*mac_p)-2);

发送函数为:send_dhcp_message,其内部为:

int send_dhcp_message(uchar * p,unsigned int length){ unsigned char i; unsigned int ii; int is_send_success; page_select(0); //write to dma //write the data addr io_out8(IOADDR 9,0x40); io_out8(IOADDR 8,0x00); //write count high io_out8(IOADDR 0x0b,length>>8); io_out8(IOADDR 0x0a,length&0xFF); //dma remote write, page0 io_out8(IOADDR,0x12); for(ii=0;ii<length;ii ){ io_out8(IOADDR 0x10,p[ii]); } io_out8(IOADDR 0x0b,0); io_out8(IOADDR 0x0a,0); io_out8(IOADDR,22);// about/ complate dma int send_length=ii; io_out8(IOADDR 4,0x40); io_out8(IOADDR 6,length>>8); io_out8(IOADDR 5,length&0xff); io_out8(IOADDR,0x1E);// txp bit =1 , start bit =1 // send 6 time for(i=0;i<6;i ){ // wait for(ii=0;ii<1000;ii ) { //CR的第3位TXP,为0,说明发送完成跳出;为1时,说明正在发送,继续for循环 // 将此位设置为1时,就会发送数据 if((io_in8(IOADDR)&0x04)==0); break; } //TSR的bit0:PTX, 为1表示没有错误的传输完成。 //此句表示如果PTX为1,跳出 if((io_in8(IOADDR 4)&0x01)!=0) break; //send success //运行到此,说明PTX还没有为1,继续发送数据 io_out8(IOADDR,0x1E); } is_send_success = io_in8(IOADDR 4)&0x01; return send_length; }

可以看到,在发送函数内部,先将ETHERNET_PACKAGE通过dma技术放入了网卡的内存,然后才用0x1E将放入网卡内存的数据发送了出去。

使用抓包软件wireshark监控发送过程

使用抓包软件,对我们的发送过程进行监控,看到底有没有发送出去,这里放视频吧

https://www.ixigua.com/7099347864899289614

演示视频:使用抓包软件监控发送过程

至此,我们完成了用寄存器控制网卡8029向网络上发送有意义的数据。

我们发送的是一个按照DHCP协议而构造的discover类型的数组,这个数组能够被路由器上的DHCP服务器收到,并理解,然后路由器就会给我们分配一个IP地址回来。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。