首页 > 编程语言 > 详细

计算机网络 Raw_Socket编程 Ping C语言

时间:2019-12-19 21:33:43      阅读:95      评论:0      收藏:0      [点我收藏+]

计算机网络做了一个附加题,用C语言Raw_Socket实现ping指令。

通过本部的Mooc学习了一下Socket编程,然后成功写了出来orz

先放一下代码:

#include <stdio.h>
#include <winsock2.h>
#include <time.h>
#include <iostream>
#include <windows.h>
#include <Mmsystem.h>
#pragma comment(lib,"winmm.lib")
#pragma comment(lib,"ws2_32.lib")

using namespace std;
const int IP_Header_min_lenght=20;
/*
    Ver 4
    Hlen 4
    1
    Servicetype 8
    1
    Total length 16
    2
    Identifier 16
    2
    flag and frag offset 16
    2
    TimeToLive 8
    1
    Prorocol 8
    CHECKSUM 16
    SRC ADDR 32
    DES ADDR 32
    160/8=20
*/
typedef struct icmp_hdr
{
    unsigned char   icmp_type;        // 类型
    unsigned char   icmp_code;        // 代码
    unsigned short  icmp_checksum;    // 校验和
    unsigned short  icmp_id;        // ID号,设为PID
    unsigned short  icmp_sequence;    // 序列号
    unsigned long   icmp_timestamp; // 时间戳
} ICMP_HDR, *PICMP_HDR;


unsigned short checksum(USHORT* buff, int size)
{
    unsigned long cksum = 0;
    while(size>1)
    {
        cksum += *buff++;
        size -= sizeof(USHORT);
    }
    // 是奇数
    if(size)
    {
        cksum += *(UCHAR*)buff;
    }
    // 将32位的chsum高16位和低16位相加,然后取反
    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);
    return (USHORT)(~cksum);
}

bool SetTimeout(SOCKET sRaw, int nTime)
{
    int ret = setsockopt(sRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTime, sizeof(nTime));
    return ret != SOCKET_ERROR;
}

double GetTickCountA()
{
    __int64 Freq = 0;
    __int64 Count = 0;
    if(QueryPerformanceFrequency((LARGE_INTEGER*)&Freq)&& Freq > 0&& QueryPerformanceCounter((LARGE_INTEGER*)&Count))
    {
        //乘以1000,把秒化为毫秒
        return (double)Count / (double)Freq * 1000.0;
    }
    return 0.0;
}
int main()
{

    char website_name[]="www.google.com";// www.baidu.com  www.google.com
    char *DestIp;
    double succ=0;
    double fail=0;
    int min_RTT=999999999;
    int max_RTT=-1;
    int sum_RTT=0;
    DestIp = (char*)malloc(20*sizeof(char));
    //初始化WindowsSocketsAPI
    WSADATA wsaData;
    WORD sockVersion = MAKEWORD(2,2);//声明版本 2.2
    WSAStartup(sockVersion, &wsaData);//启动
    //获取IP地址
    struct hostent *curr_hostent = gethostbyname(website_name);
    DestIp = inet_ntoa( *(struct in_addr*)curr_hostent->h_addr_list[0] );
    //DestIp = "192.168.88.1";
    printf("正在ping %s:[%s] \n",website_name,DestIp);
    //printf("%s\n",curr_hostent->h_addr_list[0]);
    //printf("%s\n",inet_ntoa(*(struct in_addr*)curr_hostent->h_addr_list[0]));
    if(!curr_hostent){
        puts("Get IP address error!");
        //system("pause");
       // exit(0);
    }
    //创立套接字
    SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);//AF_INET=ipv4 SOCK_RAW ICMP协议
    SetTimeout(sRaw, 1000);
    // 设置目的地址
    SOCKADDR_IN dest;
    dest.sin_family = AF_INET;//ipv4地址家族
    dest.sin_port = htons(0);//将整型变量从主机字节顺序转变成网络字节顺序
    dest.sin_addr.S_un.S_addr = inet_addr(DestIp);
    // 创建ICMP数据包并填写
    char buff[sizeof(ICMP_HDR) + 32];
    ICMP_HDR* pIcmp = (ICMP_HDR*)buff;
    pIcmp->icmp_type = 8;    // 请求一个ICMP回显
    pIcmp->icmp_code = 0;
    pIcmp->icmp_id = (unsigned short)GetCurrentProcessId();
    unsigned short nSeq = 0;
    char recvBuf[1024];
    SOCKADDR_IN from;
    int nLen = sizeof(from);
    for(int o=0;o<10;o++)
    {
        int nRet;
        pIcmp->icmp_checksum = 0;//为了后续checksum函数正常 需要先置位0
        pIcmp->icmp_timestamp = GetTickCountA();
        pIcmp->icmp_sequence = nSeq++;
        pIcmp->icmp_checksum = checksum((unsigned short*)buff, sizeof(ICMP_HDR) + 32);
        //发送包
        nRet = sendto(sRaw, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest));//发送
        if(nRet == SOCKET_ERROR)
        {
            printf(" sendto() failed: %d \n", ::WSAGetLastError());
            return -1;
        }
        //接收包
        nRet = recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen);
        if(nRet == SOCKET_ERROR)
        {
            if(WSAGetLastError() == WSAETIMEDOUT)
            {
                printf("  Request timed out\n");
                fail++;
                continue;
            }
            fail++;
            printf(" Failed. Error code: %d\n", WSAGetLastError());
            return -1;
        }
        // 解析包
        int nTick = GetTickCountA();
        if(nRet < IP_Header_min_lenght + sizeof(ICMP_HDR))
        {
            printf("  Returned too few bytes from %s \n", inet_ntoa(from.sin_addr));
            fail++;
        }
        // 接收包中包含IP头,所以加20得到ICMP头
        ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + IP_Header_min_lenght); // (ICMP_HDR*)(recvBuf + sizeof(IPHeader));
        if(pRecvIcmp->icmp_type != 0)    // 回显
        {
            if(pRecvIcmp->icmp_type == 3)    // 回显
            {
                printf("  Unreachable\n");fail++;
            }
            else if(pRecvIcmp->icmp_type == 4)    // 回显
            {
                printf("  Origin suppression \n");fail++;
            }
            else if(pRecvIcmp->icmp_type == 5)    // 回显
            {
                printf("  Redirect \n");fail++;
            }
            else if(pRecvIcmp->icmp_type == 8)    // 回显
            {
                printf("  Echo request\n");fail++;
            }
            else if(pRecvIcmp->icmp_type == 9)    // 回显
            {
                printf("  Router announcement\n");fail++;
            }
            else if(pRecvIcmp->icmp_type == 10)    // 回显
            {
                printf("  Router request \n");fail++;
            }
            else if(pRecvIcmp->icmp_type == 11)    // 回显
            {
                printf("  Time out \n");fail++;
            }
            else if(pRecvIcmp->icmp_type == 17)    // 回显
            {
                printf("  Address subnet request\n");fail++;
            }
            else if(pRecvIcmp->icmp_type == 18)    // 回显
            {
                printf("  Address subnet response\n");fail++;
            }
            return -1;
        }
        succ++;
        printf("  来自 %s的回复:  ", inet_ntoa(from.sin_addr));
        printf(" 字节=%d bytes :", nRet, inet_ntoa(from.sin_addr));
        printf(" 时间= %d ms", nTick - pRecvIcmp->icmp_timestamp);
        printf(" TTL= %d \n", *(recvBuf+8));
        min_RTT=min(min_RTT,(int)(nTick - pRecvIcmp->icmp_timestamp));
        max_RTT=max(max_RTT,(int)(nTick - pRecvIcmp->icmp_timestamp));
        sum_RTT=sum_RTT+nTick - pRecvIcmp->icmp_timestamp;
        Sleep(1000);//一秒
    }
    printf("数据包丢失率:%lf",fail*100/(succ+fail));
    cout<< "%"<<endl;
    printf("最小 RTT = %d ms \n", min_RTT);
    printf("最大 RTT = %d ms\n", max_RTT);
    printf("平均 RTT = %d ms\n", sum_RTT/10);

    return 0;
}

还是算比较基础的吧,注释里面该有的都有啦~

主要就是socket编程和IP数据报的知识。

对于socket编程:我在编写raw_socket抓包程序时去听了本部计算机网络的MOOC,其中一章就是关于socket编程的。其中讲了声明winsocket版本、建立套接字、发送与接受等等等等函数。在编写程序时我们找到对应的函数调用即可。这里我有一个之前误解的地方:我原以为是gethostbyname将站点名翻译为32位点分地址,但在实际编写程序并调试后才发现是inet_ntoa的功劳。在了解了调用socket函数创立套接字、用gethostbynameinet_ntoa获得ip地址、用sendtorecvfrom收发包后,接下来的工作就是填写数据包了。

我们需要写的是一个IP数据报,这与在创建socket时填写的AF_INET参数相一致。他在IP数据报的首部后的数据部分即为ICMP报文,中间有六个项,在接下来的核心代码注解中详细展示。我们需要创建一个ICMP报文大小+IC数据报头大小的缓冲区,填写每一段的内容后发送。这里有一个细节时为了正确计算校验和,需要先将其置零并填写完其余所有数据后再计算。在接受到返回的数据后,用与抓包程序类似的解析方式即可。

再放一些查资料找到的东西备用吧:

技术分享图片

 

 

  sin_zero用于填充结构体使之保持与struct sockaddr一样的长度。

技术分享图片

 

 (从onenote复制下来是张图片...

 

技术分享图片

 

 感谢本部mooc拯救我于苦海之中

计算机网络 Raw_Socket编程 Ping C语言

原文:https://www.cnblogs.com/mumujzl/p/12069816.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!