30天自制操作系统写网卡驱动[4]:寄存器控制网卡发送和接收数据
30天自制操作系统写网卡驱动之《3》:读取网卡MAC地址
在自制操作系统上编写网卡驱动(二):网卡的I/O配置
如何在自制操作系统中编写网卡驱动(一)
今天的:
事实上,我们可以编写自己独特的协议。 根据UDP协议在数据中添加端口号。 根据IP协议将IP地址添加到数据中。 使用以太网协议指定阵列的网卡地址。 编写代码生成 DHCP 信息并发送。 使用抓包软件监控发送过程。
上一篇文章我们已经完成了网卡8029的发送控制,可以发送8029的数据了。
然而,为了使发送的数组S[60]能够与网络上的其他网卡进行通信,必须根据一些特定的协议来组织数组S[60]。
网卡还没有分配IP地址,所以我们按照DHCP协议来组织阵列,希望路由器能够按照DHCP协议为我们分配IP地址。
经过[4]的努力,我们生成了一个DHCP数组,然后为了给数组添加端口号,我们按照UDP协议在数组中添加了一个8字节长度的UDP。
事实上,我们可以编写自己独特的协议
然而,现在阵列被发送到网络,没有网卡会接收它。
因为虽然使用UDP协议已经将端口号包含在数组中,但是还没有指定接收者的IP地址和MAC地址,所以没有网卡会解释这个数组。 因为网卡是按照协议来解释数据的,所以当网卡收到数据后,会检查MAC地址,发现不是自己的MAC地址,所以就不会继续解释数据了。
从这里我们也明白了,网卡其实可以接收网络上的任何数据,但是这个数据是否需要分析解读,就取决于数据中是否包含我们想要的信息。
因此,只要有一张网卡,我们就可以开发一款可以接收网络上任何数据的网卡。
也就是说,我们其实可以使用UDP、IP、TCP、以太网等协议。 我们可以自己设计一些协议来传输我们自己的信息。
然而,这些是现在公开的统一协议。 比如我买的路由器自带的协议是DHCP来分配IP地址。 因此,我只能按照DHCP协议指定的方式来制作数组。
请记住:协议是由人制定的。 只要发送方和接收方知道协议中每个数字的含义,就可以完成信息的交换。 如果为了完成加密,任何现有的协议都可以使用。
比如我可以自己设置一些协议,专门用于自制操作系统之间的信息交换。
但由于网卡不是我们生产的,因此网卡传输的数据仍然必须按照以太网协议规定的方式组织。
因此,向阵列添加端口号、IP地址和MAC地址必须遵守一些现有协议。
按照UDP协议规定的方法添加端口号。
添加IP地址时,必须按照IP协议规定的方法添加。
添加MAC地址时,必须按照以太网规定的方式添加。
根据UDP协议为数据添加端口号
在[4]中,我们按照UDP协议规定的方法在DHCP数组前面添加了8个字节:0, 0x44, 0, 0x43, 0x1, 0x2c, 0, 0x80。
其中0、0x44,两个字节,对应十进制的68,表示我们的出端口是68。
其中0、0x43是两个字节,对应十进制的67,表示该报文需要由路由器上67端口的程序处理。
接下来的四个字节是数据的长度(8 + DHCP 的字节数)和校验和。
编写具体代码时,只需要编写一个8字节的结构体,然后给这个结构体赋值即可:
// 定义UDP header的结构体
typedef struct{
short src_port;
short dest_port;
short length;
short check_sum;
} UDP_HEADER;
新增加的8个字节称为UDP。
UDP 放在 DHCP 数据前面,得到的总数组称为 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地址,按照以太网协议向阵列添加MAC地址。
根据IP协议为数据添加IP地址
根据IP协议,只需在UDP数组前添加如下字段数据即可:
IP协议指定要添加的字段
这些字段称为 IP。 我们先按照这个写一个结构:
// 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赋值:
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;
}
=0x45,对应图中的第一个字节。 该字节的高4位是IP,即IP协议的版本。 这里,4指的是IPv4版本; 低4位是长度值,但这个长度值只是加上IP的长度值,所以叫is,这个长度值的单位是双字。 一个双字包含4个字节,所以=5,也就是说这个IP的长度是5个双字,也就是5x4=20个字节。
=0x00,对应图中第二个字节,Type of,TOS,字面意思是服务类型。 事实上,这个字段规定了采用哪种策略进行传输,是最小延迟、最高可靠性、最小成本还是最大吞吐量? 我们这里设置为0,即正常传输。 如果您想了解更多详情,可以看这里:
=hxl(()-4),对应图中第3、4个字节,Total,即IP+UDP数组的长度。 为什么我要把这个长度减少 4? 因为我发现构造出来的结构体比原来多了4个零,所以我减去了这四个零。 另外,使用的hxl是这样的:
unsigned short hxl(unsigned short length){
length=length&0x0000ffff;
return ((length&0xff00)>>8 | (length&0xff)<<8) &0xffff;
}
通过hxl交换short类型的高8位和低8位。 长度本身就是一个词,即短。 当一个字存入存储器时,先存该字的低8位,后存该字的高8位。 但传输时需要先传输高8位,再传输低8位,所以这里我们先传输高8位。 其实这个变量定义得更合适。
->= 是IP的第5、6个字节,即,或者ID。 字面意思是:身份标识符,或者分段身份标识符。 这个字段是用来表示IP阵列的身份的? IP阵列的身份是什么? IP数组用来传输我们要传输的数据,但是如果我们要传输的数据太长,超过了IP数组可以承载的最大长度,比如字段是,
最大长度是65536,如果我们要传输的数组比这个长,我们还能用IP协议规定的方法给数组添加IP地址吗?
答案是肯定的。
但是IP协议要求我们将数据分段传输,比如分成2段,在每段数据前加上IP,然后分2段发送出去。 那么两次发送的数据也两次收到。 现在问题来了?
接收方如何知道两次收到的数据应该一起使用?
因此,为了让接收方能够将两份数据结合起来,我们需要给每一个发送的数据赋予一个标识:即也可以直接称为ID。
如何赋予这个值? 为什么我们在这里给一个? 因为对数组进行分段后,第一个段的ID是一个随机值,第二个段的ID是第一个段的ID+1。
例如,如果我们想将数组分两部分传输,那么当向数组的第一部分添加IP时,我们随机给1,那么当向数组的第二部分添加IP时,=+1=。
=hxl(0x1