使用原始套接字编程实现简单的ping程序

程序实现步骤:

1、初始化Windows Sockets网络环境

  WSADATA    wsa;

WSAStartup(MAKEWORD(2,2),&wsa);

2、构造目的端Socket地址

3、创建原始套接字

4、定义IP和ICMP头部数据结构

5、发送报文

6、接收报文

其中ICMP回显请求与回显应答报文结构如下图:

使用原始套接字编程实现简单的ping程序_第1张图片

typedef struct IcmpHeader{//ICMP的报头格式
        BYTE i_type;//ICMP类型码
        BYTE i_code;//子类型码
        USHORT i_cksum;//校验和
        USHORT i_id;//ICMP数据报的ID号
        USHORT i_seq;//ICMP数据报的序列号
        ULONG timestamp;//可选数据部分,可以忽略
};

IP数据报格式:

使用原始套接字编程实现简单的ping程序_第2张图片

typedef struct IpHeader{//IP报头格式
        unsigned char hdr_len;//4位头部长度
        unsigned char version;//4位版本号
        unsigned char tos;//8位服务类型
        unsigned short total_len;//16位总长度
        unsigned short identifier;//16位标示符
        unsigned short frag_and_flags;//3位标志加13位片偏移
        unsigned char tt1;//8位生存时间
        unsigned char protocol;//8位上层协议号
        unsigned short checksum;//16位校验和
        unsigned long sourceIP;//32位源IP地址
        unsigned long destIP;//32位目的IP地址
};
部分成员函数解释:

1、SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,protocol,NULL,0,0);

        AF_INET:代表通信域是TCP/IP协议簇

        SOCK_RAW:代表要创建的套接字类型是原始套接字

         protocol:指定协议类型,可取IPPROTO_ICMP(ICMP协议)\IPPROTO_IGMP(IGMP协议)\IPPROTO_TCP(TCP协议)\IPPROTO_UDP(UDP协议)\IPPROTO_IP(IP协议)\IPPROTO_RAM(原始IP)

2、int setsockopt(SOCKET s,int level,int optname,const char* optval,int optlen);

           s(套接字): 指向一个打开的套接口描述字
          level:(级别): 指定选项代码的类型。
               SOL_SOCKET: 基本套接口
               IPPROTO_IP: IPv4套接口
               IPPROTO_IPV6: IPv6套接口
               IPPROTO_TCP: TCP套接口
        optname(选项名): 选项名称
        optval(选项值): 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{}, timeval{ }
        optlen(选项长度) :optval 的大小

        返回值:标志打开或关闭某个特征的二进制选项

3、int SENDTO(int   sockfd,const   void*  msg, int   len ,  unsignde   int  flags,struct  sockaddr*  to,int   tolen);

                sockfd:发送方的数据报套接字描述符

                msg:指向发送缓存区的指针

                len:发送缓存区的长度

                flags:发送的方式,一般置0

                to:接收方的网络地址

                tolen:结构sockaddr的长度

                返回值:发送成功的话返回实际发送的字节数,错误返回-1

4、int  recvefrom(int   sockfd,const   void*  buf, int   len ,  unsignde   int  flags,struct  sockaddr*  from,int   fromlen)

           sockfd:接收方的数据报套接字描述符

                buf:指向发送缓存区的指针

                len:发送缓存区的长度

                flags:接收的方式,一般置0

                from:返回发送方的网络地址

                tolen:结构sockaddr的长度

                返回值:发送成功的话返回实际接收的字节数,错误返回-1

校验和计算方法(网际校验和算法):

     将被校验数据按16位划分(如果被校验的数据字节长度为奇数,则在尾部的字节补一个字节的0),然后对每16位分组求反码和,最后将结果取反码得到校验和。



具体代码及注释如下:

// Console_PING.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "winsock2.h"
#include "winsock.h"

#pragma comment(lib,"ws2_32.lib")

//常量定义
const int MAX_ICMP_PACKET_SIZE=1024;//ICMP报文最大长度(包括报头)
const int DEF_ICMP_DATA_SIZE=32;//ICMP报文默认数据字段长度
const int ICMP_ECHO_REQUEST=8;//ICMP类型字段,8表示请求回显


typedef struct IcmpHeader{//ICMP的报头格式
        BYTE i_type;//ICMP类型码
        BYTE i_code;//子类型码
        USHORT i_cksum;//校验和
        USHORT i_id;//ICMP数据报的ID号
        USHORT i_seq;//ICMP数据报的序列号
        ULONG timestamp;//可选数据部分,可以忽略
};

typedef struct IpHeader{//IP报头格式
        unsigned char hdr_len;//4位头部长度
        unsigned char version;//4位版本号
        unsigned char tos;//8位服务类型
        unsigned short total_len;//16位总长度
        unsigned short identifier;//16位标示符
        unsigned short frag_and_flags;//3位标志加13位片偏移
        unsigned char tt1;//8位生存时间
        unsigned char protocol;//8位上层协议号
        unsigned short checksum;//16位校验和
        unsigned long sourceIP;//32位源IP地址
        unsigned long destIP;//32位目的IP地址
};


void fill_icmp_data(char * icmp_data,int datasize);//填充ICMP数据报函数
USHORT checksum(USHORT * pBuf,int iSize);//计算校验和函数
void decode_resp(char *buf,int bytes,struct sockaddr_in *from);//接收报文后判断是否是目的端回送的报文


int _tmain(int argc, _TCHAR* argv[])
{
    //初始化
    WSADATA wsa;
    WSAStartup(MAKEWORD(2,2),&wsa);

    SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,0);//创建原始套接字
    if(sockRaw==INVALID_SOCKET)
        fprintf(stderr,"WSASocket() failed:%d",WSAGetLastError());//创建失败

    //设置socket的超时机制
    int timeout=1000;//设置延时为1秒
    int bread=setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));
    if(bread==SOCKET_ERROR)
        fprintf(stderr,"failed to set recv timeout:%d",WSAGetLastError());

    //填充数据报
    char icmp_data[MAX_ICMP_PACKET_SIZE];
    memset(icmp_data,0,MAX_ICMP_PACKET_SIZE);
    int datasize=DEF_ICMP_DATA_SIZE;
    datasize+=sizeof(IcmpHeader);
    fill_icmp_data(icmp_data,datasize);
    ((IcmpHeader*)icmp_data)->i_cksum=0;
    ((IcmpHeader*)icmp_data)->timestamp=GetTickCount();
    ((IcmpHeader*)icmp_data)->i_seq=0;
    ((IcmpHeader*)icmp_data)->i_cksum=checksum((USHORT*)icmp_data,datasize);

    //发送数据报文
    sockaddr_in dest;
    dest.sin_family=AF_INET;
    dest.sin_addr.s_addr=inet_addr("**.**.**.**");//我ping的是网关
    int bwrote=sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest));

    //发送失败
    if(bwrote==SOCKET_ERROR)
          return 0;
    if(bwrote         return 0;

    //接收回应报文
    sockaddr_in from;
    int fromLen=sizeof(from);
    char recvbuf[MAX_ICMP_PACKET_SIZE];
    int recve=recvfrom(sockRaw,recvbuf,MAX_ICMP_PACKET_SIZE,0,(struct sockaddr*)&from,&fromLen);
    if(recve<=0)
        return 0;//接收失败
    decode_resp(recvbuf,bread,&from);

    system("PAUSE");
    return 0;
}

void fill_icmp_data(char * _icmp_data,int _datasize)
{
    IcmpHeader *icmp_hdr;
    char *datapart;
    icmp_hdr=(IcmpHeader*)_icmp_data;
    icmp_hdr->i_type=ICMP_ECHO_REQUEST;//设置类型信息
    icmp_hdr->i_id=(USHORT)GetCurrentThreadId();//设置ID号为当前线程号
    datapart=_icmp_data+sizeof(IcmpHeader);
    memset(datapart,'E',_datasize-sizeof(IcmpHeader));//在ICMP数据部分填入数据
}

USHORT checksum(USHORT * pBuf,int iSize)
{
    unsigned long cksum=0;
    while(iSize>1)
    {
        cksum+=*pBuf++;
        iSize-=sizeof(USHORT);
    }
    if(iSize)
        cksum+=*(UCHAR*)pBuf;
    cksum=(cksum>>16)+(cksum&0xffff);
    cksum+=(cksum>>16);
    return (USHORT)(~cksum);
}

void decode_resp(char *buf,int bytes,struct sockaddr_in *from)
{
    IpHeader *iphdr;
    IcmpHeader *icmphdr;
    unsigned short iphdrlen;
    iphdr=(IpHeader *)buf;
    iphdrlen=iphdr->hdr_len*4;//IP报头的长度
    icmphdr=(IcmpHeader*)(buf+iphdrlen);
    if(icmphdr->i_id==(USHORT)GetCurrentThreadId())//判断是否是目的端发回的消息
        return;
    printf("IP  %s正在使用中!",inet_ntoa(from->sin_addr));
}

你可能感兴趣的:(使用原始套接字编程实现简单的ping程序)