Linux内核网络栈1.2.13-icmp.c概述
参考资料
<<linux内核网络栈源代码情景分析>>
icmp协议
在实现的过程中, ICMP协议工作再IP协议之上,但又不与TCP协议工作再一级,而是在下一级,在一般ICMP模块处理完后,还需要进一步调用TCP协议进行处理。该协议的目的主要是用于通报错误或者探测远端主机信息。
ICMP的主要流程
icmp_send函数
该函数主要是发送一个ICMP错误通报数据包
/*
* Send an ICMP message in response to a situation
*
* Fixme: Fragment handling is wrong really.
*/
void icmp_send(struct sk_buff *skb_in, int type, int code, unsigned long info, struct device *dev)
{
struct sk_buff *skb;
struct iphdr *iph;
int offset;
struct icmphdr *icmph;
int len;
struct device *ndev=NULL; /* Make this =dev to force replies on the same interface */
unsigned long our_addr;
int atype;
/*
* Find the original IP header.
*/
iph = (struct iphdr *) (skb_in->data + dev->hard_header_len); // 获取ip头部信息
/*
* No replies to MAC multicast
*/
if(skb_in->pkt_type!=PACKET_HOST) // 如果数据包目的地址不是指向本地的单播地址则直接返回
return;
/*
* No replies to IP multicasting
*/
atype=ip_chk_addr(iph->daddr); // 检查原数据包最终目的地址类型,如果是一个多波或者广播地址则不满足产品ICMP数据包的条件
if(atype==IS_BROADCAST || atype==IS_MULTICAST)
return;
/*
* Only reply to first fragment.
*/
if(ntohs(iph->frag_off)&IP_OFFSET) // 检查是否是第一个数据包分片,
return;
/*
* We must NEVER NEVER send an ICMP error to an ICMP error message
*/
if(type==ICMP_DEST_UNREACH||type==ICMP_REDIRECT||type==ICMP_SOURCE_QUENCH||type==ICMP_TIME_EXCEEDED) // 检查原数据包是否是一个ICMP错误报文,
{
/*
* Is the original packet an ICMP packet?
*/
if(iph->protocol==IPPROTO_ICMP) // 检查
{
icmph = (struct icmphdr *) ((char *) iph +
4 * iph->ihl);
/*
* Check for ICMP error packets (Must never reply to
* an ICMP error).
*/
if (icmph->type == ICMP_DEST_UNREACH ||
icmph->type == ICMP_SOURCE_QUENCH ||
icmph->type == ICMP_REDIRECT ||
icmph->type == ICMP_TIME_EXCEEDED ||
icmph->type == ICMP_PARAMETERPROB)
return;
}
}
icmp_statistics.IcmpOutMsgs++;
/*
* This needs a tidy.
*/
switch(type) // 更新统计
{
case ICMP_DEST_UNREACH:
icmp_statistics.IcmpOutDestUnreachs++;
break;
case ICMP_SOURCE_QUENCH:
icmp_statistics.IcmpOutSrcQuenchs++;
break;
case ICMP_REDIRECT:
icmp_statistics.IcmpOutRedirects++;
break;
case ICMP_ECHO:
icmp_statistics.IcmpOutEchos++;
break;
case ICMP_ECHOREPLY:
icmp_statistics.IcmpOutEchoReps++;
break;
case ICMP_TIME_EXCEEDED:
icmp_statistics.IcmpOutTimeExcds++;
break;
case ICMP_PARAMETERPROB:
icmp_statistics.IcmpOutParmProbs++;
break;
case ICMP_TIMESTAMP:
icmp_statistics.IcmpOutTimestamps++;
break;
case ICMP_TIMESTAMPREPLY:
icmp_statistics.IcmpOutTimestampReps++;
break;
case ICMP_ADDRESS:
icmp_statistics.IcmpOutAddrMasks++;
break;
case ICMP_ADDRESSREPLY:
icmp_statistics.IcmpOutAddrMaskReps++;
break;
}
/*
* Get some memory for the reply.
*/
len = dev->hard_header_len + sizeof(struct iphdr) + sizeof(struct icmphdr) +
sizeof(struct iphdr) + 32; /* amount of header to return */ // 获取总的数据长度
skb = (struct sk_buff *) alloc_skb(len, GFP_ATOMIC); // 申请内存
if (skb == NULL)
{
icmp_statistics.IcmpOutErrors++;
return;
}
skb->free = 1;
/*
* Build Layer 2-3 headers for message back to source.
*/
our_addr = dev->pa_addr; // 获取原数据网络设备的地址 从哪里来发回哪里去
if (iph->daddr != our_addr && ip_chk_addr(iph->daddr) == IS_MYADDR)
our_addr = iph->daddr;
offset = ip_build_header(skb, our_addr, iph->saddr,
&ndev, IPPROTO_ICMP, NULL, len,
skb_in->ip_hdr->tos,255); // 建立头部
if (offset < 0) // 创建失败
{
icmp_statistics.IcmpOutErrors++;
skb->sk = NULL;
kfree_skb(skb, FREE_READ);
return;
}
/*
* Re-adjust length according to actual IP header size.
*/
skb->len = offset + sizeof(struct icmphdr) + sizeof(struct iphdr) + 8; // 创建头部并初始化
/*
* Fill in the frame
*/
icmph = (struct icmphdr *) (skb->data + offset);
icmph->type = type;
icmph->code = code;
icmph->checksum = 0;
icmph->un.gateway = info; /* This might not be meant for
this form of the union but it will
be right anyway */
memcpy(icmph + 1, iph, sizeof(struct iphdr) + 8);
icmph->checksum = ip_compute_csum((unsigned char *)icmph,
sizeof(struct icmphdr) + sizeof(struct iphdr) + 8); // 计算ICMP校验值
/*
* Send it and free it once sent.
*/
ip_queue_xmit(NULL, ndev, skb, 1); // 数据发送出去
}
该函数主要是发送一个ICMP错误通报数据包,ICMP错误通报数据包一般在接受到远端一个不合法的数据包后产生的,用于通知远端发生错误的原因。
icmp_unreach函数
处理ICMP错误类型,处理相关错误后就调用传输层协议处理tcp_err等
/*
* Handle ICMP_UNREACH and ICMP_QUENCH.
*/
static void icmp_unreach(struct icmphdr *icmph, struct sk_buff *skb)
{
struct inet_protocol *ipprot;
struct iphdr *iph;
unsigned char hash;
int err;
err = (icmph->type << 8) | icmph->code; // 获取错误值
iph = (struct iphdr *) (icmph + 1); // 获得头部
switch(icmph->code & 7) // 查看具体的错误类型
{
case ICMP_NET_UNREACH:
break;
case ICMP_HOST_UNREACH:
break;
case ICMP_PROT_UNREACH:
printk("ICMP: %s:%d: protocol unreachable.\n",
in_ntoa(iph->daddr), ntohs(iph->protocol));
break;
case ICMP_PORT_UNREACH:
break;
case ICMP_FRAG_NEEDED:
printk("ICMP: %s: fragmentation needed and DF set.\n",
in_ntoa(iph->daddr));
break;
case ICMP_SR_FAILED:
printk("ICMP: %s: Source Route Failed.\n", in_ntoa(iph->daddr));
break;
default:
break;
}
/*
* Get the protocol(s).
*/
hash = iph->protocol & (MAX_INET_PROTOS -1); // 获取协议位置索引
/*
* This can't change while we are doing it.
*/
ipprot = (struct inet_protocol *) inet_protos[hash]; // 获取传输层协议函数操作集
while(ipprot != NULL)
{
struct inet_protocol *nextip;
nextip = (struct inet_protocol *) ipprot->next;
/*
* Pass it off to everyone who wants it.
*/
if (iph->protocol == ipprot->protocol && ipprot->err_handler)
{
ipprot->err_handler(err, (unsigned char *)(icmph + 1),
iph->daddr, iph->saddr, ipprot); // 调用协议错误处理函数处理
}
ipprot = nextip;
}
kfree_skb(skb, FREE_READ); // 释放数据
}
主要就是调用传输协议层处理错误信息,让上层应用处理。
icmp_redirect函数
该函数主要是处理重定向报文。
/*
* Handle ICMP_REDIRECT.
*/
static void icmp_redirect(struct icmphdr *icmph, struct sk_buff *skb,
struct device *dev, unsigned long source)
{
struct rtable *rt;
struct iphdr *iph;
unsigned long ip;
/*
* Get the copied header of the packet that caused the redirect
*/
iph = (struct iphdr *) (icmph + 1); // 初始化为原数据包IP首部
ip = iph->daddr; // 初始化为发送数据包的远端IP地址
switch(icmph->code & 7) // 获取重定向类型进行处理
{
case ICMP_REDIR_NET:
/*
* This causes a problem with subnetted networks. What we should do
* is use ICMP_ADDRESS to get the subnet mask of the problem route
* and set both. But we don't..
*/
#ifdef not_a_good_idea
ip_rt_add((RTF_DYNAMIC | RTF_MODIFIED | RTF_GATEWAY), // 网络重定向直接添加路由表项
ip, 0, icmph->un.gateway, dev,0, 0);
break;
#endif
case ICMP_REDIR_HOST: // 主机重定向
/*
* Add better route to host.
* But first check that the redirect
* comes from the old gateway..
* And make sure it's an ok host address
* (not some confused thing sending our
* address)
*/
rt = ip_rt_route(ip, NULL, NULL); // 获取ip对应的路由表内容
if (!rt)
break;
if (rt->rt_gateway != source || ip_chk_addr(icmph->un.gateway)) // 不可是多播地址或者广播地址
break;
printk("redirect from %s\n", in_ntoa(source));
ip_rt_add((RTF_DYNAMIC | RTF_MODIFIED | RTF_HOST | RTF_GATEWAY),
ip, 0, icmph->un.gateway, dev,0, 0); // 修改原表项
break;
case ICMP_REDIR_NETTOS:
case ICMP_REDIR_HOSTTOS:
printk("ICMP: cannot handle TOS redirects yet!\n");
break;
default:
break;
}
/*
* Discard the original packet
*/
kfree_skb(skb, FREE_READ);
}
该函数处理有重定向ICMP数据包,这种数据包由路由器发送,用于通知本地到达某个网络或主机的一个更好的路由路径,路由器为特定的目的主机发送重定向消息将主机重定向到一个更优的路由器或者告诉主机目的主机实际上是在同一个链路的邻居节点上。
icmp_echo函数
该函数主要是是回复一个Echo应答数据包。
/*
* Handle ICMP_ECHO ("ping") requests.
*/
static void icmp_echo(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev,
unsigned long saddr, unsigned long daddr, int len,
struct options *opt)
{
struct icmphdr *icmphr;
struct sk_buff *skb2;
struct device *ndev=NULL;
int size, offset;
icmp_statistics.IcmpOutEchoReps++;
icmp_statistics.IcmpOutMsgs++;
size = dev->hard_header_len + 64 + len; // mac首部长度 加上IP数据负载长度 返回该数据
skb2 = alloc_skb(size, GFP_ATOMIC); // 申请内存
if (skb2 == NULL) // 如果申请失败则计数返回
{
icmp_statistics.IcmpOutErrors++;
kfree_skb(skb, FREE_READ);
return;
}
skb2->free = 1;
/* Build Layer 2-3 headers for message back to source */
offset = ip_build_header(skb2, daddr, saddr, &ndev,
IPPROTO_ICMP, opt, len, skb->ip_hdr->tos,255); // 建立头部信息
if (offset < 0) // 如果出错则计数释放空间返回
{
icmp_statistics.IcmpOutErrors++;
printk("ICMP: Could not build IP Header for ICMP ECHO Response\n");
kfree_skb(skb2,FREE_WRITE);
kfree_skb(skb, FREE_READ);
return;
}
/*
* Re-adjust length according to actual IP header size.
*/
skb2->len = offset + len; // 设置长度
/*
* Build ICMP_ECHO Response message.
*/
icmphr = (struct icmphdr *) (skb2->data + offset); // 获取偏移的返回值 并初始化
memcpy((char *) icmphr, (char *) icmph, len);
icmphr->type = ICMP_ECHOREPLY;
icmphr->code = 0;
icmphr->checksum = 0;
icmphr->checksum = ip_compute_csum((unsigned char *)icmphr, len);
/*
* Ship it out - free it when done
*/
ip_queue_xmit((struct sock *)NULL, ndev, skb2, 1); // 发送数据
/*
* Free the received frame
*/
kfree_skb(skb, FREE_READ);
}
该函数主要是处理Echo请求,ping应用程序使用Echo ICMP请求数据包探测远端主机的可达性。对于Echo请求的处理是咋接受到一个远端地址指向本地的数据包后,回复一个Echo应答数据包
icmp_rcv函数
该函数是ICMP协议总入口函数,作用类似与tcp_rcv,当IP模块发现上层使用ICMP协议时,即调用icmp_rcv函数将数据包上传,该函数就将对这个具体类型的数据类型调用不同的处理函数进行处理。
/*
* Deal with incoming ICMP packets.
*/
int icmp_rcv(struct sk_buff *skb1, struct device *dev, struct options *opt,
unsigned long daddr, unsigned short len,
unsigned long saddr, int redo, struct inet_protocol *protocol)
{
struct icmphdr *icmph;
unsigned char *buff;
/*
* Drop broadcast packets. IP has done a broadcast check and ought one day
* to pass on that information.
*/
icmp_statistics.IcmpInMsgs++;
/*
* Grab the packet as an icmp object
*/
buff = skb1->h.raw; // 获取IP模块传入的数据
icmph = (struct icmphdr *) buff; // 转换成 icmph类型数据
/*
* Validate the packet first
*/
if (ip_compute_csum((unsigned char *) icmph, len)) // 计算校验和 如果不对则出错返回
{
/* Failed checksum! */
icmp_statistics.IcmpInErrors++;
printk("ICMP: failed checksum from %s!\n", in_ntoa(saddr));
kfree_skb(skb1, FREE_READ);
return(0);
}
/*
* Parse the ICMP message
*/
if (ip_chk_addr(daddr) != IS_MYADDR) // 检查ICMP报文最终目的IP地址是否为本地地址
{
if (icmph->type != ICMP_ECHO) // 如果不是ICMP_ECHO则返回
{
icmp_statistics.IcmpInErrors++;
kfree_skb(skb1, FREE_READ);
return(0);
}
daddr=dev->pa_addr; // 设置地址
}
switch(icmph->type)
{
case ICMP_TIME_EXCEEDED:
icmp_statistics.IcmpInTimeExcds++;
icmp_unreach(icmph, skb1); // 设置不可达
return 0;
case ICMP_DEST_UNREACH:
icmp_statistics.IcmpInDestUnreachs++;
icmp_unreach(icmph, skb1);
return 0;
case ICMP_SOURCE_QUENCH:
icmp_statistics.IcmpInSrcQuenchs++;
icmp_unreach(icmph, skb1);
return(0);
case ICMP_REDIRECT:
icmp_statistics.IcmpInRedirects++;
icmp_redirect(icmph, skb1, dev, saddr); // 设置为重定向
return(0);
case ICMP_ECHO:
icmp_statistics.IcmpInEchos++;
icmp_echo(icmph, skb1, dev, saddr, daddr, len, opt); // 发送icmp_echo响应报文
return 0;
case ICMP_ECHOREPLY:
icmp_statistics.IcmpInEchoReps++;
kfree_skb(skb1, FREE_READ);
return(0);
case ICMP_TIMESTAMP:
icmp_statistics.IcmpInTimestamps++;
icmp_timestamp(icmph, skb1, dev, saddr, daddr, len, opt); // 时间戳
return 0;
case ICMP_TIMESTAMPREPLY:
icmp_statistics.IcmpInTimestampReps++;
kfree_skb(skb1,FREE_READ);
return 0;
/* INFO is obsolete and doesn't even feature in the SNMP stats */
case ICMP_INFO_REQUEST:
icmp_info(icmph, skb1, dev, saddr, daddr, len, opt); // 获取信息
return 0;
case ICMP_INFO_REPLY:
skb1->sk = NULL;
kfree_skb(skb1, FREE_READ);
return(0);
case ICMP_ADDRESS:
icmp_statistics.IcmpInAddrMasks++;
icmp_address(icmph, skb1, dev, saddr, daddr, len, opt);
return 0;
case ICMP_ADDRESSREPLY:
/*
* We ought to set our netmask on receiving this, but
* experience shows it's a waste of effort.
*/
icmp_statistics.IcmpInAddrMaskReps++;
kfree_skb(skb1, FREE_READ);
return(0);
default:
icmp_statistics.IcmpInErrors++;
kfree_skb(skb1, FREE_READ);
return(0);
}
/*NOTREACHED*/
kfree_skb(skb1, FREE_READ);
return(-1);
}
该函数主要检查ICMP首部中类型,先检查数据包合法性,然后判断是什么类型报文类型再调用对应处理函数。
总结
ICMP协议实现起来主要按照协议格式实现,ICMP报文主要分为两种报文:错误通知报文以及查询报文,Echo请求和应答,地址掩码请求和应答等属于查询报文,而重定向目的端不可达属于错误通知报文。本文主要学习了ICMP的操作函数。由于本人才疏学浅,如有错误请批评指正。
上一篇: mybatis返回类型Map
下一篇: 收发文件的服务器端/客户端实现