分享

手把手教你手撸通讯协议(二)-网络的基础

 新用户0935snDB 2022-02-28

经过上一篇的手把手教你手撸通讯协议(一) S7协议解析 中,大家有没有发现缺了很大一部分篇幅,而且也只讲到了UDP的包头;由于UDP是让大家简单的看到以太网的工作方式,接下去我们通过开源的LwIP协议栈来好好了解以太网的真实工作方式,我将会在这一期结束的时候,给大家实现一个基于STM32的modbusTCP 主站的小demo

第一节:协议层简介

首先我们还是根据标准的TCP/IP协议栈来分析传输层和链路层的网络是怎样打包的;

首先我们先了解几个基础协议及网络分层:

网络接口层:定义数据帧(对电信号0/1进行的特定分组)、确认主机的物理地址(MAC地址),通过传输介质在网络上传输数据帧。网络接口有不同的实现方式,比如可以通过有线或无线的方式收发数据帧,不同的实现方式意味着不同的帧结构、传输速率等。

网络层:定义网络地址(IP地址)、区分网段、对于子网内的数据包进行MAC寻址、对于不同子网的数据包进行路由,实现网络中主机到主机的通信。主要有PPP协议、SLIP协议、ARP、ipv4等基础协议。

传输层:定义端口(Port)、标识应用程序身份、实现端口到端口的通信,TCP协议可以保证数据传输的可靠性。

应用层:定义数据格式并按照对应的格式解读数据(下层传送过来的是字节流,不能很好的被程序识别)。应用层定义了各种各样的协议来规范数据格式,常见的有 HTTP、FTP、SMTP 等。

第二节:数据包及内存存储结构

由上面的基础知识、我们根据理论知识,我们根据LwIP来进行学习。其实其实网络层级来说:TCP和UDP类似,但TCP需要实现可靠连接,网卡接收的数据包,有可能是成千上万字节,也有可能是几个字节,所以我们需要对其数据进行打包处理。

由于内存分配问题可以谈的很深、涉及到编译原理、字节对齐这些,篇幅有限,不展开。反正主要是两种方式、一种是链表、一种是内存池方式,各种系统中也都会讲到,我们主要从数据包开始说明:

struct pbuf { struct pbuf *next; void *payload; u16_t tot_len; u16_t len; u8_t type; u8_t flags; u16_t ref;};
typedef enum {  PBUF_RAM, /* pbuf data is stored in RAM */  PBUF_ROM, /* pbuf data is stored in ROM */  PBUF_REF, /* pbuf comes from the pbuf pool */  PBUF_POOL /* pbuf payload refers to RAM */} pbuf_type;

这两个看上去是不是很熟悉,就是一个链表节点。分配完成后就是这样:

文章图片1

组成链表后的形式大概是这样的:

文章图片2

这个就是数据包在内存中存储的方式了。

第三节:网络接口

在 LWIP 中,是通过一个叫做 netif 的网络结构体来描述一个硬件网络接口的。

struct netif { struct netif *next; // 指向下一个 netif 结构的指针 struct ip_addr ip_addr; // IP 地址相关配置 struct ip_addr netmask; struct ip_addr gw; err_t (* input)(struct pbuf *p, struct netif *inp); //调用这个函数可以从网卡上取得一个 // 数据包 err_t (* output)(struct netif *netif, struct pbuf *p, // IP 层调用这个函数可以向网卡发送 struct ip_addr *ipaddr); // 一个数据包 err_t (* linkoutput)(struct netif *netif, struct pbuf *p); // ARP 模块调用这个函数向网 // 卡发送一个数据包 void *state; // 用户可以独立发挥该指针,用于指向用户关心的网卡信息 u8_t hwaddr_len; // 硬件地址长度,对于以太网就是 MAC 地址长度,为 6 各字节 u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //MAC 地址 u16_t mtu; // 一次可以传送的最大字节数,对于以太网一般设为 1500 u8_t flags; // 网卡状态信息标志位 char name[2]; // 网络接口使用的设备驱动类型的种类 u8_t num; // 用来标示使用同种驱动类型的不同网络接口 };

举个例子来实现一张网卡的初始化:

static struct netif enc28j60;//声明了一个 netif 结构的变量 enc28j60 struct ip_addr ipaddr, netmask, gw; //声明了三个分别用于暂存 IP 地址、子网掩码和网关地址的变量IP4_ADDR(&gw, 192,168,0,1); IP4_ADDR(&ipaddr, 192,168,0,60); IP4_ADDR(&netmask, 255,255,255,0); netif_init(); netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, ethernetif_init, tcpip_input); netif_set_default(&enc28j60); netif_set_up(&enc28j60); err_t ethernetif_init(struct netif *netif) { netif->name[0] = IFNAME0; //初始化变量 enc28j60 的 name 字段 netif->name[1] = IFNAME1; // IFNAME 在文件外定义的,这里不必关心它的具体值  netif->output = etharp_output; //IP 层发送数据包函数 netif->linkoutput = low_level_output; // //ARP 模块发送数据包函数 low_level_init(netif); //底层硬件初始化函数 return ERR_OK; } static void low_level_init(struct netif *netif) { netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置变量 enc28j60 的 hwaddr_len 字段 netif->hwaddr[0] = 'F'; //初始化变量 enc28j60 的 MAC 地址 netif->hwaddr[1] = 'O'; //设什么地址用户自由发挥吧,但是不要与其他 netif->hwaddr[2] = 'R'; //网络设备的 MAC 地址重复。 netif->hwaddr[3] = 'E'; netif->hwaddr[4] = 'S'; netif->hwaddr[5] = 'T'; netif->mtu = 1500; //最大允许传输单元 //允许该网卡广播和 ARP 功能,并且该网卡允许有硬件链路连接 netif->flags = NETIF_FLAG_BROADCAST | \ NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; enc28j60_init(netif->hwaddr); //与底层驱动硬件驱动程序密切相关的硬件初始化函数 } 

这里我们完成了初始化一张名为enc28j60 的网卡。

接下去是网卡的接收和发送主要通过low_level_input 和 low_level_output这两个函数来实现。然后在操作系统中直接调用这两个函数就行了。

以UC/OSII的网卡数据接收为例:

第一步创建线程:

OSTaskCreate(ethernetif_input,(void *)&enc28j60, &T_ETHERNETIF_INPUT_STK[T_ETHERNETIF_INPUT_STKSIZE-1] ETH_IF_TASK_PRIO);

第二步:数据包接收

void ethernetif_input(void *arg) //创建该进程时,要将某个网络接口结构的 netif 结构指 { //针作为参数传入 struct eth_hdr *ethhdr; struct pbuf *p; struct netif *netif = (struct netif *)arg; while (1) { p = low_level_input (netif); // 接收一个数据包 if (p == NULL) // 如果数据包为空, continue; // 则循环结束,启动下次接收过程 ethhdr = p->payload; // 取得数据包内数据 switch (htons(ethhdr->type)) // 判断数据包类型 { // 只对 IP 数据包和 ARP 数据包进行处理 case ETHTYPE_IP: // IP 数据包 case ETHTYPE_ARP: // ARP 数据包 if (netif->input(p, netif)!=ERR_OK) // 将数据包发送到上层应用函数 { pbuf_free(p); p = NULL; } break; default: pbuf_free(p); p = NULL; break; } //switch } //while } //main 函数 

至此,数据包的接收可算大功告成 。

第四节:网络层

接下去我们要进行网络层协议的讲解了:

(1) ARP:全称 Address Resolution Protocol,译作地址解析协议,是位于TCP/IP协议栈底层的协议。

文章图片3

ARP的协议包格式:

struct etharp_hdr { PACK_STRUCT_FIELD(struct eth_hdr ethhdr); // 14 字节的以太网数据报头 PACK_STRUCT_FIELD(u16_t hwtype); // 2 字节的硬件类型 PACK_STRUCT_FIELD(u16_t proto); // 2 字节的协议类型 PACK_STRUCT_FIELD(u16_t _hwlen_protolen); // 两个 1 字节的长度字段 PACK_STRUCT_FIELD(u16_t opcode); // 2 字节的操作字段 op PACK_STRUCT_FIELD(struct eth_addr shwaddr); // 6 字节源 MAC 地址 PACK_STRUCT_FIELD(struct ip_addr2 sipaddr); // 4 字节源 IP 地址 PACK_STRUCT_FIELD(struct eth_addr dhwaddr); // 6 字节目的 MAC 地址 PACK_STRUCT_FIELD(struct ip_addr2 dipaddr); // 4 字节目的 IP 地址 } PACK_STRUCT_STRUCT;

我们可以把他转化成这个结构体。

接下去这个图我们可以看到ARP的工作流程:

文章图片4

说白了就是两个功能:通过ARP协议实现IP地址和MAC地址的映射,或者广播获取目标MAC地址。

嘿嘿嘿,这里有人知道为啥拨号叫PPPOE么?或者说校园网、闪讯这些校园网络是怎么进行独立收费的么。理解了这一层的协议,可以做很多事情噢。

(2)IP协议

IP层其实在上一章节也有讲到过。

最重要的是在网络标识那一标识位置确认了使用了什么协议。8位协议字段用来描述该IP数据包是来自于上层的哪个协议,如该值为1表示为ICMP协议,该值为2表示IGMP协议,该值为6表示TCP协议,该值为17表UDP协议。

前面我说们说到TCP包需要分包,接下去一个图可以很清晰的解释LwIP是怎么进行分包的:

文章图片5

这一层的东西太多了,不展开。IP层的讲解主要了解这些就够了。

再说个大家感兴趣东西:ping和tracert ,其实在连接过程中,就是用ICMP协议实现的,主要用来测试路径和时间。其实第一次接触hack也是从ICMP攻击开始的。中美黑客大战,我也是拿了脚本,贡献了一份力(ORZ)。

文章图片6

第五节:传输层

接下去我们说说传输层:这一层的东西很多很多,偷懒了。

文章图片7
文章图片8

这里先补充一点:在PLC还没分配IP地址时,我们是怎么找到设备并分配IP的?没有IP地址是怎么发现PLC或模块的地址的?

先以Rockwell的EtherNet/IP举个例子,由于Rockwell的CIP协议大多数功能都是基于标准以太网协议实现,所以可以很贴合现在这个系列。AB模块可以通过一个叫BOOTP的工具进行模块的发现和IP地址分配,很好理解,AB的PLC是使用BOOTP协议进行PLC或模块的发现的。为什么我们现在挺少听说BOOTP了呢?因为现在大家都在用DHCP的方式了Bootp其实是基于UDP协议进行设备发现的。其实我们上下位机的通讯基本靠TCP协议,而下位机之间的通讯基本是基于UDP进行通讯,UDP协议的本身协议特点可以实现模块之间的高速通讯,更适合用于现场网络,本系列主要以与上位机通讯为主,所以减少UDP这一块的解释。

西门子的协议对这一层的协议进行了一些修改,以下图为例(来自西门子官网)。这个下次再聊。

文章图片9

下级预告:本系列知识点的重点了:TCP的建立和断开

文章图片10

TCP的全称大家自行百度:主要功能是为上层提供一个可靠连接(虽然容易出线粘包问题)。

这里先给大家看一张图(别的地方截取过来的):这是TCP连接的状态转化。

文章图片11

对这一期就先到这边,TCP的内容留在下一期。

结尾

总结一下

Summary

1、LwIP协议栈主要用于嵌入式系统的以太网协开发。该协议栈为很轻量级的以太网协议栈,通过该协议栈的学习,可以很好的理解以太网是怎么工作的,采用该协议栈,我在很多项目中实现了MQTT、S7协议、ModbusTCP协议等工业协议的开发,还有一些私有协议的开发,很好的用于网络中间件的开发。

2、讲解了物理接口层、链路层、网络层、传输层的部分协议实现和打包方式。讲的比较简单,也是给大家一个可以参考的方向。

留两个问题

问题1:IP数据包失序后怎么处理?

问题2:TCP发生粘包问题如何处理(或者说S7协议、CIP协议等是怎么处理粘包问题)?

2022年2月

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多