记一次蛋疼的Raw socket发送经历。附:Raw socket编程总结

最近在做信息安全导论的实验,实验很简单,就是实现一个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();
原文地址:https://www.cnblogs.com/MyselfDancing/p/3478431.html