APP下载

TCP/IP 网络中故障节点的诊断方法

2008-07-14于长虹张伟锋

电脑知识与技术 2008年18期

于长虹 张伟锋

摘要:本文描述了一种在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-),男,网络工程师,主要研究方向:网络管理技术;张伟锋,洛阳师范学院信息技术学院。