于長虹 張偉鋒
摘要:本文描述了一種在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-),男,網絡工程師,主要研究方向:網絡管理技術;張偉鋒,洛陽師范學院信息技術學院。