帧中继环境下Ping的实现
一. ICMP协议和Ping 命令
为了让互联网中的路由器报告错误或提供有关意外情况的信息,TCP/IP中加入了一个特殊用途的报文机制。这个机制叫做Internet控制报文协议ICMP(Internet Control Message Protocol),它是IP的一部分,并在每个IP实现中必须有它。最初的设计虽然是为了允许路由器向主机报告投递出错的原因,但是ICMP并没有限制仅在路由器上使用。尽管限制某些ICMP报文的使用,但是任一台机器可以向任何其他机器发送ICMP报文。因此,主机可以使用ICMP与路由器或另一台主机通信。
当一台主机创建了一个I P 数据报时,主机会产生一个覆盖整个头部的校验和。无论何时收到一个数据报,校验和都被用于验证头部是否无损地到达。为了验证校验和,计算机对接收到的数据报的整个头部(包括校验和域)重新计算校验和。如果I P 头部中的某一位在传输过程中被破坏,则计算出的校验和不为零。当改变头部中的域时(例如将生存时间TIME TO LIVE 域递减),路由器必须在转发该数据报前,重新计算校验和。
发现校验和错时的处理非常简单:数据报必须立即丢弃,而不作进一步的处理。接收者不能相信数据包头部中的任何域,因为接收者不知道哪一位被改变了。甚至也不能发一个出错消息给发送者,因为接收者不能相信包头中的源地址。同样,接收者也不能转发被损坏的数据包,因为它也不能相信包头中的目的地址。因此,接收者除了将被损坏的数据包丢弃外别无选择。
ICMP报文是放在一个IP数据报的数据部分中通过互联网的。ICMP报文的最终目的不是一个应用程序或是目的机器上的用户,而是该机上处理它的Internet协议软件模块。总之,ICMP在两台机器上的Internet协议软件之间提供了通信。
ICMP中的回送请求(echo request)和回送应答(echo reply)报文为网络管理者或用户提供了一个识别网络问题的调试工具。主机或路由器向指定目的站发送ICMP回送请求报文,任何收到回送请求的机器形成回送应答并把它返回最初的发送者。回送请求包含一个可选数据区;应答包含了在请求中所发送数据的一个拷贝。回送请求及其关联的应答可用来测试目的站是否可达和是否响应。因为请求和应答都是在IP数据报中传送的,所以应答的成功接收就证实传送系统的主要部分是正常的。
在许多系统上,用户所使用的发送ICMP回送请求的命令叫做Ping。较复杂的Ping的发送一系列ICMP回送请求,捕获响应并提供丢失数据报的统计。不太复杂的版本仅仅发送一个ICMP回送请求并等待应答。
二. IP和ICMP报文格式
ICMP回送请求和回送应答报文格式如下:
IP报文格式如下:
程序中利用函数CheckSum()来计算和验证IP的首部校验和以及ICMP的校验和。将要计算校验和的数据部分看作为一个16位的整数序列,并定义校验和是数据中所有16位整数的和的二进制反码。在函数CheckSum()中使用了32位(长)算法来累加得到一个和,然后通过向这个整数和中增加所有进位位的方法,将结果折合成一个16位的值。最后,CheckSum()返回结果的反码。注意,每次调用CheckSum()计算校验和之前,要先将校验和域清空,再计算赋值。
三. 程序思路
该实验的实现是在只是前面提到的ARP/InARP的基础上增加了对Ping的处理,仍然采用多线程结构,实现互斥操作。
在主线程中,增加了用户可以使用Ping命令,并做出相应处理。写线程仍然是从数据包队列中取出一个数据包,并在写事件到来时将其发送出去。 读线程在读取一个数据包后判断其包类型时,增加了对ICMP回送请求和回送应答的处理。
四. 主要的数据结构
Ping的实现是ARP/InARP实现的基础上增加了以下一些数据结构:
struct S_PING_PKT
// 定义了Ping报文的格式,包括帧中继报文头,
// IP数据报头,ICMP报文格式;
/
{
//FrameRelay head
unsigned char frameHead[2];
// IP head
unsigned char ip_ver; // 0x45
unsigned char ip_tos; // 0
unsigned short ip_tl; // 0x0020
unsigned short ip_id;
unsigned short ip_ffo; // 0x0000
unsigned char ip_ttl; // 32
unsigned char ip_pro; // 1
unsigned short ip_chk;
unsigned char ip_sip[4];
unsigned char ip_tip[4];
// ICMP head
unsigned char type; // 8=echo request 0=Echo reply
unsigned char code; // 0
unsigned short chk;
unsigned short id;
unsigned short seq;
//可选数据
unsigned char data[32];
};
struct S_pingState; //用于记录发送出去的Ping报文的状态信息,包括Ping的目的IP地址,该报文是否已发送,是否已经接收到应答;
六. 程序结构与主要流程
(1) 主线程: 接收用户输入,在用户使用Ping命令时,将用户输入的目的IP地址存放到S_pingState结构中去,调用函数SendPing()
函数SendPing()实现如下:
(2) 读线程
函数handlePingRequest():
生成一个相应的回送请求,并暂存到待发送数据队列中
函数handlePingReply():
根据收到的应答的序列号,将相应的S_pingState中的已接收标志置为1
七.程序中其他一些处理
1. 并发控制:由于本程序是一个多线程的程序,多个线程运行时是共享代码和数据的。如果不对这种共享作一些控制的话肯定会出现不可预料的结果。本程序对并发的处理采用 MFC 中的并发锁机制,由于线程会共享的数据被封装在两个类里面(C_QUEUE_INFO和C_DLCI_INFO),线程操作数据都是通过这两个类的实例进行的,所以只要在两个类的代码里实现并发锁就可以了。
2. 需要特别注意的是数据链路帧的帧头不是固定的,有的情况下(发送配置类原语)是8个字节,真正发送数据的时候是2个字节。
3. 协议中对数字的处理可能会和大家最初设想的很不一样。譬如主机的IP地址,大家很可能会认为是和标准Socket里面那样是一个以网络字节序存储的结构体来表示的,结果经过分析发现不是,而是用了四个字节,每个字节存储相应的点分十进制数字。如 100.1.1.1 在协议数据包中是这样构成的:[100] [1] [1] [1],其中[ ]表示一个字节。
4. 校验和的处理也很奇怪。传输时也不是采用网络字节序,而是采用了位串的方式。
参考资料:
RFC791, J. Postel. Internet Protocol, Sep-01-1981.
RFC826, D.C. Plummer. Ethernet Address Resolution Protocol, Nov-01-1982.
RFC1293, T. Bradley, C. Brown, Inverse Address Resolution Protocol. January 1992.
RFC1294, T. Bradley, C.Brown, A. Malis. Multiprotocol Interconnect over Frame Relay. January 1992.
CCITT X.36
附录:
A. 帧中继的一些原语
#define DL_DATA_IND 4
#define DL_DATA_REQ 4
/*
ID (04)
DSAP
USER_DATA
*/
#define NUM_DL_PARA 12
#define DL_PARA_REQ 7
#define DL_PARA_IND 7
/*
ID (07)
DSAP
..........
**********
PARA
VALUE_LOW
VALUE_HIGH
**********
..........
*/
/* ------------PARA TABLE--------------
PAR 1: CONFIG
BIT 0: 1-ON,0-OFF
BIT 1: 1-LMI:Q.933 Annex A, 0-LMI:NONE
PAR 2: FIRST_DLCI
PAR 3: NUM_DLCI
PAR 4: NUM_PVC
PAR 5: N203
PAR 6: N391
PAR 7: N392
PAR 8: N393
PAR 9: T391
PAR 10: T392
PAR 11: BAUD RATE
0---XXX
1---XXX
2---XXX
3---75
4---150
5---300
6---600
7---1200
8---2400
9---4800
10---9600
11---19200
12---48000
13---64000
14---128000
15---256000
PAR 12:CLOCK 1-INTER; 0-EXTERN */
CONFIG: 3--使用Q.933协议中附件A的链路管理协议 (缺省值)
1--不使用上述链路管理协议
DLCI NUM: 永久虚电路的个数
FIRST DLCI: 第一条永久虚电路的DLCI编号,缺省为16
N391: 完全状态(所有PVC状态)轮询计数器
N392: 差错/恢复计数器(N392应小于或等于N393)。
N393: 事件监测计数器(如果N393置为一个比N391小很多的值, 则链路可能在差错和无差错状态之间切换而未能被DTE或网络觉察)。
T391: 链路完整性校验轮询计时器
T392: 轮询校验计时器
#define DL_WIND_IND 8
/*
ID (08)
DSAP
WIND_SIZE
*/
#define DL_PERMIT_REQ 9
/*
ID (09)
DSAP
PERMIT_SIZE
*/
#define DL_STATE_IND 10
/*
ID (10)
DSAP (0)
STATE
**********
DSAP
DLCI_LOW
DLCI_HIGH
STATE : 0---NOT READY 1---READY
**********
*/
B. 物理层原语
VL3-X25规程控制卡向高层提供了按X.211建议设计的网络服务原语。当VL3-X25规程控制卡设置成物理模式时可以使用这些原语。VL3规程控制卡原语用VL3规程控制卡驱动程序作为数据块发送和接收。用户把每个原语看成一个数据块,交给VL3规程控制卡驱动程序,VL3规程控制卡控制卡驱动程序也把原语以数据块的形式交给用户。
PH_ACT_REQ激活请求、PH_ACT_IND激活指示
格式
ID (01)
说明
PH_ACT_REQ用于激活物理链路,PH_ACT_IND用于指示物理链路已激活。
PH_DEACT_REQ撤消请求、PH_DEACT_IND撤消指示
格式
ID (02)
说明
PH_DEACT_REQ用于撤消已激活的物理链路、PH_DEACT_IND用于指示物理链路已撤消。
PH_DATA_REQ数据请求、PH_DATA_IND数据指示
格式
ID (03)
USER_DATA
说明
PH_DATA_REQ用于发数据,PH_DATA_IND用于指示收到的数据。
PH_PARA_REQ参数请求、PH_PARA_IND参数指示
格式
ID (04)
.....................
*********************
PARA
VALUE_LOW
VALUE_HIGH
*********************
.....................
说明
PH_PARA_REQ用于设置或读出参数。当原语没有参数字段时从VL3规程控制卡中读出参数。否则设置新的参数值。参数的编号和意义如下表。