最近在做信息安全导论的实验,实验很简单,就是实现一个ping程序,能够扫描主机是否打开的情况,但是,我也就纳了闷了,每次有个不易发现的bug(可能由于自己知识有限造成的),都得让我碰上,并且还得为这个bug操心好长时间,才能解决。。。。。。(抱怨是最浪费时间的一种行为!)ok~为了下次不再犯同样的错误,现在先记录一下吧~
我的错误是,在一切都准备好了的时候(初始化套接字,创建套接字,填充icmp数据头,设定超时时间)发送数据包,成功,但是一旦接受就会超时,为此,我找了个能发送成功的程序,一步步的对照着看看哪儿不一样,但是对照完了,发现一些核心代码是一样的。。。。。。这就蛋疼了,然后我就怀疑是不是因为这些小的错误导致的呢?我一步步的改正,首先我怀疑我的可能是因为发送和接受我放在了不同的函数里面,导致,在一个函数返回的时间里,接受函数错过了接受(现在想想真是有病乱投医啊。。。。。。。操作系统在端口应该有缓存的吧,在接受到数据后,会存到缓存里,然后才会调用。。。。。)我就把所有函合并成了一个函数,果不其然,没有任何效果,依旧是发送超时。在经历无尽的折磨以后,我发现我的变量全是定义在类里面的,而其他的程序就是在函数里面直接声明,或者就是全局变量的,难道是因为类,半信半疑我就把所有定义在类里面的变量放到了cpp文件当中,作为全局变量调用,成功了~我去了!不应该啊,这是因为啥???在一个函数调用类里面的变量后,不改变内存中值?上课时不是这么讲的啊。。。。。。苦逼的程序猿我,又一次发挥了初中时在物理课上学的控制变量法。。。。。。。。(我真丫该去学物理去。。。。。)一个一个的把变量弄回去,好了,终于发现了!!!!!!发送的数据包(实质是一个数组)没有初始化,我擦了,为毛没有初始化会出现这个情况?我发送时就是发送了icmp的头部数据,跟其他数据没有关系啊,在经过我控制变量法的不断检验下,不管缓冲区有多大,只有初始化为0,后才可能接受成功,也就是被ping的主机识别。具体原因还有待进一步的研究。
下面来对原始套接字编程进行说明一下:
总体步骤是:
1、用管理员权限打开编辑器(vc6.0或vs)
2、创建ip和icmp的头部数据结构
ip头部数据结构:
typedef struct _iphdr //定义IP固定首部
{
unsigned char h_lenver; //4位首部长度+4位IP版本号
unsigned char tos; //8位服务类型TOS
unsigned short total_len; //16位总长度(字节)
unsigned short ident; //16位标识
unsigned short frag_and_flags; //3位标志位
unsigned char ttl; //8位生存时间 TTL
unsigned char proto; //8位协议 (TCP, UDP 或其他)
unsigned short checksum;//16位IP首部校验和
unsigned int sourceIP; //32位源IP地址
unsigned int destIP; //32位目的IP地址
}IpHeader;
icmp头部数据结构:
typedef struct _icmphdr
{
unsigned char i_type; //类型
unsigned char i_code; //代码
unsigned short i_cksum; //检验和
unsigned short i_id; //标识符
unsigned short i_seq; //序列号
unsigned long i_timestamp; //当前时间 =(unsigned long)::GetTickCount();
}IcmpHeader;
3、初始化原始套接字
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
if(WSAStartup(wVersionRequested, &wsaData) != 0)
{
int flag = ::WSAGetLastError();
CString str;
str.Format(_T("%d"), flag);
MessageBox(_T("套接字初始化失败!")+str);
}
4、声明套接字
sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
5、设置套接字选项(这里是不需要自己设置ip的简单icmp程序)
int nTime = 1000;
int ret = ::setsockopt(sRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTime, sizeof(nTime));
6、设置发送和接受的函数选项
SOCKADDR_IN dest;
dest.sin_family = AF_INET;
dest.sin_port = htons(0);
dest.sin_addr.S_un.S_addr = inet_addr(pBuf); /*设置要ping的主机ip(pBuf)*/
SOCKADDR_IN from;
int nLen = sizeof(from);
6、发送并接受信息
for (int i = 0; i < 4; i++)
{
icmp->i_cksum = 0;
icmp->i_seq = i;
icmp->i_timestamp = ::GetTickCount();
icmp->i_id = ::GetCurrentProcessId();
icmp->i_cksum = cheNum((USHORT*)buff, sizeof(IcmpHeader));
nRet = ::sendto(sRaw, buff, sizeof(IcmpHeader), 0, (SOCKADDR *)&dest, sizeof(dest));
if(nRet == SOCKET_ERROR)
{
int flag = ::WSAGetLastError();
CString str;
str.Format(_T("%d"), flag);
MessageBox(_T(" sendto() failed: ")+str);
}
nRet = ::recvfrom(sRaw, recvBuff, 1024 , 0, (SOCKADDR *)&from, &nLen);
if(nRet == SOCKET_ERROR)
{
if(::WSAGetLastError() == WSAETIMEDOUT)
{
MessageBox(_T(" timed out/n"));
}
}
int nTick = ::GetTickCount();
IcmpHeader* pRecvIcmp = (IcmpHeader*)(recvBuff + 20);
if(pRecvIcmp->i_type != 0)
{
CString str;
str.Format(_T("%d"),pRecvIcmp->i_type);
MessageBox(_T(" nonecho type"), str+_T("recvd"));
}
else if(pRecvIcmp->i_id != ::GetCurrentProcessId())
{
MessageBox(_T(" someone else's packet!"));
}
else
{
m_state = _T("open");/*返回到edit控件中*/
UpdateData(false);
}
}
7、关闭并清理
closesocket(sRaw);
WSACleanup();
来源:oschina
链接:https://my.oschina.net/u/4311964/blog/4333932