于长虹 张伟锋
摘要:本文描述了一种在TCP/IP网络中进行故障节点诊断的程序实现,该方法基于VxWorks操作系统的网络测试仪环境,但此程序算法的实现,并不依赖于底层的操作系统及硬件环境,经过少量修改可以在任何提供TCP/IP协议栈的操作系统中实现,比如Linux,Windows等。
关键词:ICMP;TCP;UDP;路由追踪
中图分类号:TP393文献标识码:A文章编号:1009-3044(2008)18-2pppp-0c
1 背景
网路故障的一般表现是网速变慢或者无法访问互联网或内网服务器,在现场进行网络故障诊断时,往往需要借助各种工具软件如Sniffer、ping、traceroute等进行逐步排查,最后经过分析,选择怀疑的网络节点,然后在局端或现场对怀疑的网络节点进行各种连通性、替代性测试,方法步骤繁杂,而且往往无法准确诊断。
经过分析,故障诊断的过程,可以使用专用的设备,并编写相应的诊断程序,自动完成网络故障节点的测试和判断。
2 算法和设计
当测试节点到达目的网络位置的链路存在问题时,一般可能是:物理链路断开(线缆或节点设备故障);目的地址的相应端口没有开放,或者中间链路经过的设备(交换机,路由器等)禁止了协议或端口;终端设备故障。故,处理流程首先是找到测试节点到达连接服务器节点的路径,确定经过的网络节点位置,然后对节点中的各个位置实施连通性测试,最后根据测试结果判断故障节点位置和原因。
2.1 网络路由的查询
该部分的功能类似于Linux系统中提供的命令traceroute,不同的是,该部分功能进行路由诊断依赖的协议不仅仅是ICMP。
ICMP的原理是链路上的节点设备都要在转发该 ICMP 回显请求报文之前将报文头部的 TTL 值减 1,当报文的 TTL 值减少到 0 时,节点设备向源发回 ICMP 超时信息。该诊断实用程序通过向目的地发送具有不同生存时间 (TTL) 的 ICMP报文,确定至目的地的路由。通过发送 TTL 为 1 的第一个回显报文并且在随后的发送中每次将 TTL 值加 1,直到目标响应或达到最大 TTL 值,可以确定链路经过的路由。通过检查链路中间节点设备发回的 ICMP 超时信息,可以确定故障节点。
如果使用ICMP协议无法完成测试,则改为使用UDP协议和TCP协议分别进行路由侦测。源发出UDP数据包,源端口使用随机的大于32768的高段端口号,目的端口从33434开始依此递增,直至33434+29,同时TTL从1开始依此递增,直至1+29=30。节点设备送回的 ICMP超时报文,使得源可以侦测到链路上每一个节点。
2.2 网络节点诊断
向节点发送TCP握手信号,如果该节点可以通过connect连接成功,表示节点可以正常连接,如果回应RST,表示该节点禁止了该端口的访问,如果该节点长时间不回复SYN,也可以认为该节点禁止端口。
因此,依据上述现象可以很容易判断当前故障节点——离测试者最近的故障点,可以被认定为当前网络故障点。
2.3 故障节点位置的判断策略
如果路由寻找完整,一般能够找到节点。在所有不回应SYN包或者回应RST包的
节点中,应该是离源最近跳数的节点设备将端口关闭。
如果路由寻找不完整,有可能找不到所找的故障点。如果在找到的n个节点中,只有非最远离源的一个节点不回应,或回应RST包,则不能确定故障节点;如果是包括最远离源在内的一个或多个节点不回应或回应RST包,则最右端节点可能为故障节点,但并不能确定在整个路由中的故障节点所在,因为路由不完整。
2.4 不能覆盖的异常情况
如果使用ICMP和UDP都无法寻找到完整路由,则有可能找不到故障节点,但这种情况非常少,因为根据UDP的测试原理,除非中间节点将大于32768的端口全部封掉,否则都可以得到完整的路由路径。
3 代码片段和程序流程
3.1 整体框架代码
int f_procon_scan_showerr(char *re_info)
{char err_node[16];
inet_ntoa_b(info_scan.node[info_scan.err_num],err_node);
if(err_tcpscan == ERR_PORTSCAN_ROUTE_HALF){
sprintf(re_info,"路由信息不完整,故障点可能是:%s",err_node);
}else if(err_tcpscan == ERR_PORTSCAN_ROUTE_NO){
sprintf(re_info,"未找到达到目的地址的路径,无法诊断故障");
}else{
sprintf(re_info,"路由信息完整,故障点是:%s",err_node);
}
return OK;
}
static int quitflag=0;
int f_procon_scan_tcp(void)
{char * re_info; /* 测试完后返回的信息 */
int re_find;
int i,rv;
char buf[512];
int numBytes,count;
int on,len;
int ctrlSock;
struct sockaddr_in ctrlAddr;
struct router_node bak_router_node;
sprintf(buf,"正在使用ICMP获取路由信息...");
server_virtual_display_output(buf,0);
bzero((char *)&info_scan,sizeof(info_scan));
re_find = find_node(0);/*使用ICMP协议*/
if(info_scan.number == 0){/* 没有正确找到到目的地址的路由信息,尝试使用udp协议查找*/
sprintf(buf,"正在使用UDP获取路由信息...");
server_virtual_display_output(buf,0);
bzero((char *)&info_scan,sizeof(info_scan));
re_find = find_node(1);/*使用UDP协议*/
if(info_scan.number == 0){
err_tcpscan = ERR_PORTSCAN_ROUTE_NO;
return OK;
}
}else if(re_find == ERROR){/*获取不完整路径,尝试用udp获取*/
sprintf(buf,"正在使用UDP获取路由信息...");
server_virtual_display_output(buf,0);
memcpy(&bak_router_node,&info_scan,sizeof(info_scan));
bzero((char *)&info_scan,sizeof(info_scan));
re_find = find_node(1);/*使用UDP协议*/
if(info_scan.number == 0){/*udp没有获取到路径,则恢复icmp的路径*/
memcpy(&info_scan,&bak_router_node,sizeof(bak_router_node));
re_find=ERROR;
}else if(re_find == ERROR){/*udp获取的也是不完整路径,则进行比较,选最多的*/
if(info_scan.number memcpy(&info_scan,&bak_router_node,sizeof(bak_router_node)); } } } if(re_find == ERROR){ err_tcpscan = ERR_PORTSCAN_ROUTE_HALF; }else{ err_tcpscan = ERR_PORTSCAN_ROUTE_OK; } quitflag=1; info_scan.node[info_scan.number].s_addr=self_ip; for(i=info_scan.number-1;i>=0;i--){ /*创建socket*/ ctrlSock = socket (AF_INET, SOCK_STREAM, 0); if (ctrlSock < 0){ sprintf(buf,"无法创建socket"); server_virtual_display_output(buf,0); return (ERROR); } /*设置socket为非阻塞模式*/ on=TRUE; if(ioctl(ctrlSock,FIONBIO,(int)&on)<0){ printf("set socket to no block is error
"); } ctrlAddr.sin_family= AF_INET; ctrlAddr.sin_addr.s_addr = info_scan.node[i].s_addr; ctrlAddr.sin_port= htons(s_procon_info.port); if(connect(ctrlSock,(struct sockaddr *)&ctrlAddr, sizeof (ctrlAddr))< 0){ if(!((errno==EINPROGRESS) || (errno==EALREADY))){ shutdown(ctrlSock,2); close(ctrlSock); continue; } } rv=server_wait_for_write_timeout(ctrlSock,&quitflag); if(rv!=0){ shutdown(ctrlSock,2); close(ctrlSock); break; } shutdown(ctrlSock,2); close(ctrlSock); } if(i<0){/*没有一个通的,或最近的也不通,则认为是最近的有问题*/ i=0; }else if(i!=info_scan.number-1){/*不是最后一个不同,则认为就是他了*/ i++; }else if(err_tcpscan == ERR_PORTSCAN_ROUTE_OK){/*最后一个竟然也是通的,则认为是服务器本身了*/ i++; } info_scan.err_num=i; return OK; } 3.2 查找路由节点函数 static int find_node(int flag) {int dst_ip, gateway; int i,num; int result = OK; S_TRACERT_INTERFACES info_tracert; S_TRACERT_INTERS *intrs; struct in_addr ip_tra; dst_ip = s_procon_info.ip_remote.s_addr; /* tracert ip is test ip */ switch(s_netcon_info.mode){ case D_NETCON_MODE_STATIC_IP: gateway = s_netcon_info.sta_ip.ip_router.s_addr; self_ip = s_netcon_info.sta_ip.ip_local.s_addr; break; case D_NETCON_MODE_DHCPC: gateway = s_netcon_info.dhcpc.ip_router[0].s_addr; self_ip = s_netcon_info.dhcpc.ip_local.s_addr; break; case D_NETCON_MODE_PPPOEH: gateway = s_netcon_info.pppoeh.ip_remote.s_addr; self_ip = s_netcon_info.pppoeh.ip_local.s_addr; break; } f_tracert_routine(dst_ip,gateway,flag); /* exec tracert for find node */ /* 如果tracert未结束,查看是否出现超时找不到路由情况,如果是,终止测试 */ /* 如果tracert停止,看是否追踪到最终的路由 */ while(!v_tracert_end){ f_tracert_show((char *)&info_tracert); /* 取信息,判断 */ for(i=0;i intrs=&info_tracert.tracert_info[i]; ip_tra.s_addr = intrs->ip; if(intrs->ip == 0){ info_tracert.number -= 1; f_tracert_end(); result = ERROR; } } taskDelay(sysClkRateGet()/2); } f_tracert_show((char *)&info_tracert); info_scan.number = info_tracert.number; /* save the node infomation to my struct */ for(i=0;i intrs=&info_tracert.tracert_info[i]; info_scan.node[i].s_addr = intrs->ip; } num = info_scan.number - 1; if(info_scan.node[num].s_addr == 0){ info_scan.number -= 1; } return result;
}
3.3 涉及到的数据结构
保存状态的结构体。
static struct router_node{
int number; /* 到目的地址能找到的节点总数 如果为0,表示未找到路由*/
int err_num; /* 询查所有节点,最后一个对端口无回应的节点序号*/
struct in_addr node[31]; /* 用Tracert查到的路由节点地址 */
char f_send[30]; /* 向相应节点成功发送TCP SYN包标志 ,成功置 1*/
charf_recv[30]; /* 成功接收各节点回复包标志,接收到置 1 */
int send_seq[30]; /* 发送的各SYN包的SEQ号,用来判断接收包 */
unsigned char flag[30]; /* 如果接收返回包,保存返回包的TCP FLAG字段 */
}info_scan;
错误状态如下:
#define ERR_PORTSCAN_ROUTE_NO 0x01
#define ERR_PORTSCAN_ROUTE_OK 0x02
#define ERR_PORTSCAN_ROUTE_HALF 0x03
#define ERR_PORTSCAN_SEND_SYN 0x11 //向网络节点发送SYN同步包出错
4 应用案例
现有一计算机终端,无法登录其开通的网络多媒体点播服务系统,但可以登录其它网站,使用网络测试仪的网络故障诊断软件来诊断该案例。
首先,通过用户界面,填入多媒体点播系统的IP地址(如202.102.249.174)极其端口号(1026),然后点击测试,诊断软件首先查找从局域网络到达202.102.249.174的路由如下:
1<1 ms<1 ms<1 ms192.168.15.1
2 *** Request timed out.
3 2 ms 1 ms 1 mshn.kd.ny.adsl [125.42.110.1]
4<1 ms<1 ms<1 mspc17.zz.ha.cn [61.168.254.17]
5<1 ms<1 ms<1 mspc58.zz.ha.cn [61.168.252.58]
6<1 ms<1 ms<1 ms202.102.249.174
然后,软件将根据算法,从最后一个节点开始诊断,发现直到hn.kd.ny.adsl时,1026端口的连接测试不能通过,从而确定,问题是因为hn.kd.ny.adsl设备禁止了1026端口。向局端工程师确认,并修改多媒体登录系统的端口为其它端口(8080),可以登录,问题解决。
5 后记
使用该算法的网络测试仪产品已经研制成功,该产品同时具备了ping、sniffer等更多的网络功能,可以更好的替代网络维护人员随声携带的笔记本电脑和其它设备,简便地进行网络故障的诊断。
参考文献:
[1](美)科默(Comer,D.E.),林瑶,蒋慧,等,译.用TCP/IP进行网际互联(第1卷):原理、协议与结构.北京:电子工业出版社,2001,5.
[2](美)W.Richard Stevens,范建华,等,译.TCP/IP详解.北京:机械工业出版社,2000,4.
[3](美)DonnaL.Harrington,童小林,等,译.CCNP实战指南:故障排除.北京:人民邮电出版社,2003,12.
[4](美)史蒂文斯,(美)芬纳,(美)鲁道夫,杨继张,译. UNIX网络编程.北京:清华大学出版社,2006,1.
收稿日期:2008-03-10
作者简介:于长虹(1982-),男,网络工程师,主要研究方向:网络管理技术;张伟锋,洛阳师范学院信息技术学院。