lwip从逻辑上看也是分为4层:链路层、网络层(IP、ARP、(ICMP、IGMP这两个协议是网络层的补充协议,并不严格属于网络层))、传输层(TCP、UDP)、应用层,基本等同TCP/IP,只是各层之间可以进行交叉存取,没有严格划分。
协议汇总:
1. ARP协议:根据IP地址获取物理地址MAC的一个TCP/IP协议
一个典型的lwip系统包含3个进程:首先是上层应用程序进程,然后是lwip协议栈进程,最后是底层硬件数据包接收进程
动态内存管理:
采用ucos-ii内存管理系统,即申请一块内存,分割成整数个大小相同的内存块
一. 链路层
当主机A要与主机B通信时,ARP协议可以将主机B的IP地址解析成主机B的MAC地址,工作流程如下:
第一步:主机A先检查自己的ARP缓冲,看是否存在主机B匹配的MAC地址,如果没有,就会向外广播一个ARP请求包
第二步:其他主机收到后,发现请求的IP地址与自己的IP地址不匹配,则丢弃ARP请求
第三步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射到本地ARP缓存中
第四步:主机B将包含其MAC地址的ARP回复发回给主机A
第五步:主机A收到从主机B发来的ARP回复时,会将主机B的IP地址和MAC地址映射更新到本地ARP缓存中。主机B的MAC地址一旦确定,主机A就可以向主机B发送IP通信了
连接链路层和网络层的纽带:以太网数据包接收进程tcpip_thread
static void tcpip_thread(void *arg)
{
struct tcpip_msg *msg; // 消息来自于网卡中断
while(1)
{
// 该任务阻塞在这里接收要处理的消息,当有数据包到来时,网卡芯片中断函数接收数据,并post消息,中断退出后,该任务获取消息
sys_timeouts_mbox_fetch(&mbox, (void **)&msg);
// 判断本条消息的类型,只关注数据包消息TCPIP_MSG_INPKT
switch (msg->type)
{
case TCPIP_MSG_INPKT: // 数据包消息
if(msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET))
ethernet_input(msg->msg.inp.p,msg->msg.inp.netif); // 如果支持ARP,先进行ARP处理,再判断是否递交IP层,对于IP数据包,这里2个选择最终都要调用ip_input进入IP层
else
ip_input(msg->msg.inp.p, msg->msg.inp.netif); // 否则直接递交IP层,ip_input为IP层主要函数,解析见下文,这里直接调用ip_input存在问题,有误,需要先以太网数据包指针,使掠过包头,指向IP协议包头
memp_free(MEMP_TCPIP_MSG_INPKT, msg); // 释放消息内存
break;
case TCPIP_MSG_TIMEOUT: // 超时消息
sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
memp_free(MEMP_TCPIP_MSG_API, msg);
break;
default:
break;
}
}
}
err_t ethernet_input(struct pbuf *p,struct netif *netif)
{
struct eth_hdr *ethhdr; // 以太网数据包头结构体
u16_t type;
s16_t ip_hdr_offset = SIZEOF_ETH_HDR; // 包头固定值14字节
ethhdr = (eth_hdr *)p->payload;
type = htons(ethhdr->type);
switch(type)
{
case ETHTYPE_IP: // IP数据包
etharp_ip_input(netif,p); // 使用收到的IP包更新ARP缓存表,详见《lwip之ARP协议》
pbuf_header(p, -ip_hdr_offset); // 调整以太网数据包指针,使掠过包头,指向IP协议包头
ip_input(p,netif); // 提交IP层,ip_input为IP层主要函数,解析见下文
case ETHTYPE_ARP: // ARP数据包
etharp_arp_input(netif,(struct eth_addr *)netif->hwaddr,p); // ARP数据包处理,第二个形参是本机MAC,详见《lwip之ARP协议》
break;
default:
break;
}
}
注:消息结构体struct tcpip_msg {
enum tcpip_msg_type type; // 本条消息的类型:TCPIP_MSG_INPKT - 数据包消息,TCPIP_MSG_TIMEOUT - 超时消息
sys_sem_t *sem; // 事件控制块ECB
union{
struct api_msg *apimsg;
struct netifapi_msg *netifapimsg;
struct {
struct pbuf *p;
struct netif *netif;
} inp; // inp结构体最重要,内含数据包内容结构、网络接口结构
struct {
tcpip_callback_fn function;
void *ctx;
} cb;
struct {
u32_t msecs;
sys_timeout_handler h;
void *arg;
} tmo;
}msg;
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
二. 网络层
lwip使用一个ip_hdr的结构体来描述IP协议包头:
struct ip_hdr{
u16_t _v_hl_tos; // 包含4位版本号(IPv4 - 4,IPv6 - 6)、4位IP包头长(通常为5*4,即本结构体大小)、8位服务类型
u16_t _len; // 整个IP数据包长度
u16_t _id; // 16位标识用于标识IP层发出的每一份IP报文,自增
u16_t _offset; // 包含3位标志和13位片偏移,IP数据包分片时使用
u8_t _ttl; // TTL描述该IP数据包最多能被转发的次数,自减
u8_t _proto; // 协议字段用于描述该IP数据包的上层协议,0x01 - ICMP,0x02 - IGMP,0x06 - TCP,0x17 - UDP
u16_t _chksum; // 16位的IP首部校验和
ip_addr_p_t src; // 源IP
ip_addr_p_t dest; // 目的IP
}
ip_input为IP层主干函数,完成了IP层数据包处理(核心工作就是IP地址匹配;得到完整数据包),然后将合适的数据包提交给上层,这里的p->payload已经越过了14字节包头,指向了IP头
err_t ip_input(struct pbuf *p,struct netif *inp)
{
struct ip_hdr *iphdr; // 指向IP包头的指针
struct netif *netif; // 指向netif硬件网络接口设备描述符的指针
u16_t iphdr_hlen; // IP包头的长度,通常是固定20字节
u16_t iphdr_len; // 整个IP包长,包含IP包头、上层协议头、数据
// 取出 IP数据包头
iphdr = (struct ip_hdr *)p->payload;
// 检查IP包头中的版本号字段,IPv4 - 4,IPv6 - 6
if(IPH_V(iphdr) != 4)
{
pbuf_free(p);
return ERR_OK;
}
// 提取IP包头中的头长度字段,通常固定值20字节
iphdr_hlen = IPH_HL(iphdr);
iphdr_hlen *= 4;
// 提取IP包头中的IP包总长度字段,确保小于递交上来的pbuf包中的总长度
iphdr_len = ntohs(IPH_LEN(iphdr));
if(iphdr_len > p->len || iphdr_len > p->tot_len)
{
pbuf_free(p);
return ERR_OK;
}
// 校验IP数据包头
if (inet_chksum(iphdr, iphdr_hlen) != 0)
{
pbuf_free(p);
return ERR_OK;
}
// 对IP数据报进行截断,得到完整无冗余IP数据包
pbuf_realloc(p, iphdr_len);
// 遍历netif_list链表(系统存在2个网卡设备,意味着有2个netif分别用于描述它们,也意味着本机有2个IP地址,所以此时就需要遍历),检测IP数据包中的目的IP是否与本机相符,不符则丢弃或转发
ip_addr_copy(current_iphdr_dest, iphdr->dest);
ip_addr_copy(current_iphdr_src, iphdr->src);
int first = 1;
netif = inp;
do{
// 通过netif->flag标志位判断该网卡设备是否配置且使能,同时判断本机IP是否有效
if ((netif_is_up(netif)) && (!ip_addr_isany(&(netif->ip_addr))))
{
// 如果目的IP地址与本机IP地址匹配或者目的IP地址是广播类型,意味着成功匹配,退出遍历
if(ip_addr_cmp(¤t_iphdr_dest, &(netif->ip_addr)) || ip_addr_isbroadcast(¤t_iphdr_dest, netif))
{
break;
}
}
if (first)
{
first = 0;
netif = netif_list;
}
else
{
netif = netif->next;
}
if (netif == inp)
{
netif = netif->next;
}
}while(netif != NULL);
// 如果该数据包中的源IP地址是广播IP,则直接丢弃
if ((ip_addr_isbroadcast(¤t_iphdr_src, inp)) || (ip_addr_ismulticast(¤t_iphdr_src)))
{
pbuf_free(p);
return ERR_OK;
}
// 遍历完成以后,如果依旧没有找到匹配的netif结构体,说明该数据包不是给本机的,转发或丢弃(这里直接丢弃)
if (netif == NULL)
{
pbuf_free(p);
return ERR_OK;
}
// 判断该IP包是否是分片数据包
// 如果是分片数据包,则需要将该分片包暂存,等接收完所有分片包后,统一将整个数据包提交给上层
if ((IPH_OFFSET(iphdr) & PP_HTONS(IP_OFFMASK | IP_MF)) != 0)
{
// 在这里重组接收到的分片包,如果还没接收完整,p=NULL
p = ip_reass(p);
// 如果分片包还没接收完整,本函数结束
if (p == NULL)
{
return ERR_OK;
}
// 如果分片包接收完整,这时的p已经是一个完整的数据包结构体了
// 再从p中获取完整的IP包
iphdr = (struct ip_hdr *)p->payload;
}
// 能到达这一步的数据包必然是未分片的或经过分片重组完整后的数据包
current_netif = inp;
current_header = iphdr;
if (raw_input(p, inp) == 0)
{
// 根据IP数据包头中的协议字段判断该数据包应该被递交给上层哪个协议
switch (IPH_PROTO(iphdr))
{
case IP_PROTO_UDP: // UDP协议
udp_input(p, inp); // 从这里进入传输层,解析见下文
break;
case IP_PROTO_TCP: // TCP协议
tcp_input(p, inp); // 从这里进入传输层,解析见下文
break;
case IP_PROTO_ICMP: // ICMP协议
icmp_input(p, inp);
break;
case IP_PROTO_IGMP: // IGMP协议
igmp_input(p, inp, ¤t_iphdr_dest);
break;
default: // 如果都不是
// 如果不是广播数据包,返回一个协议不可达ICMP数据包给源主机
if (!ip_addr_isbroadcast(¤t_iphdr_dest, inp) && !ip_addr_ismulticast(¤t_iphdr_dest))
{
p->payload = iphdr;
icmp_dest_unreach(p, ICMP_DUR_PROTO);
}
pbuf_free(p);
}
}
current_netif = NULL;
current_header = NULL;
ip_addr_set_any(¤t_iphdr_src);
ip_addr_set_any(¤t_iphdr_dest);
}
IP层的补充协议:ICMP、IGMP
这时候主机A学到了主机B的MAC地址,就把这个MAC封装到ICMP协议中向主机B发送,报文格式如下:
包头14字节 :因为ICMP协议包属于网络层协议,所以帧类型是0x0800
+ ICMP协议头(主要是二级协议类型、源IP、目的IP) :二级协议类型ICMP对应值0x01
+ ICMP协议主体(主要是一个类别) :类别取值0x00 - 这是一条回应信息 0x03 - 目的不可达 0x08 - 请求回应信息
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------