ping源码实现

该代码实现了一个在多网口设备上自动选择网络最佳节点的逻辑。首先,它将输入的网址解析为IP地址,然后获取当前网络节点的IP。接着,创建ICMP套接字并填充数据,设置套接字属性,特别是SO_BINDTODEVICE选项,确保绑定到指定网口。通过select函数等待响应,判断接收的ICMP回应,以确定最佳路径。此外,还提到了gethostbyname函数的注意事项和处理网络断开的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目目前有个需求,需要在多网口情况下自动切换到网络最好的节点

总结一下代码逻辑:

  • 流程:

1.判断当前传入网址为ip还是域名,如果为域名情况下,讲域名解析成ip


    dstAddr = inet_addr(dst);
    if (-1 == dstAddr)
    {   

        struct hostent *he;
        he = gethostbyname(dst);
        if (NULL == he || 2 != he->h_addrtype)
        {
            return -1; 
        }
        inaddr = (struct in_addr *)he->h_addr;
        dstAddr = inaddr->s_addr;
    }

2.获取当前网络节点ip


    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock <= 0)
    {
        return ret;
    }
    strncpy(ifr.ifr_name, iface, strlen(iface));
    ret = ioctl(sock, SIOCGIFADDR, &ifr);
    if (0 == ret)
    {
        sin = (struct sockaddr_in *)&ifr.ifr_addr;
        if (0 != sin->sin_addr.s_addr)
        {
            memcpy(ipAddr, sin, sizeof(struct sockaddr_in));
            ret = 0;
        }
    }
    close(sock);

3.创建icmp套接字,并填装数据

    sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sock <= 0)
    {
        printf("sock create failed, errno=%d\n", errno);
        return ret;
    }
    icmpInfo.icmp_type = ICMP_ECHO;
    icmpInfo.icmp_code = 0;
    icmpInfo.icmp_cksum = 0;
    icmpInfo.icmp_seq = 1;
    icmpInfo.icmp_id = icmpID;
    icmpInfo.icmp_cksum = in_cksum(&icmpInfo, sizeof(icmpInfo));

4.设置套接字属性,需要bind绑定当前网口地址

    一开始没有使用SO_BINDTODEVICE这个属性,发现通过ppp拨号的4g模组始终不能ping成功,但是用命令行的ping -I ppp0可以成功,查看原码后才发现这个属性一定要设置

    将ping源码中设置的属性全部都拷贝过来

    opt = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
    opt = 1;
    setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(int));
    opt = 2000;
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &opt, sizeof(int));
    opt = 2000;
    setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &opt, sizeof(int));

    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &src, sizeof(src));

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, iface, strlen(iface));
    //注意这个属性一定要设置
    if (0 != setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)))
    {
        printf("bindto iface %s failed, errno %d\n", iface, errno);
        close(sock);
        return ret;
    }

    if (bind(sock, (struct sockaddr *)&src, sizeof(src)) < 0)
    {
        printf("bind failed\n");
        close(sock);
        return ret;
    }

5.通过select等待返回接收,这里最好要判断接收到的icmp的id是否和发出去的一样,并且select函数也可以获取到小号时间

    while (select(sock+1, &rset, NULL, NULL, &recvTime) > 0)
    {
        rlen = recvfrom(sock, recvBuffer, 1024, 0, (struct sockaddr *)&dstAddr, &addrlen); 

        if (rlen <= 0)
        {
            break;
        }
        dstIp = (struct ip *)recvBuffer;
        offset = dstIp->ip_hl << 2;
        icmpRes = (struct icmp *)(&recvBuffer[offset]);
        if ((ICMP_ECHOREPLY == icmpRes->icmp_type) && (icmpID == icmpRes->icmp_id))
        {
            ret = 0;
            *timeoutMS = waitTime - ((recvTime.tv_sec * 1000) + (recvTime.tv_usec / 1000));
            break;
        }
    }

异常处理:

        gethostbyname这个系统函数需要注意,不支持可重入,并且会阻塞(gethostbyname_r同样如此)。

        处理方法

        1.每个gethostbyname函数前加锁,/etc/resolv.conf中设置超时时间

#实际测试中gethostbyname并非1秒结束,猜测应该是和resolv.conf中的多个nameserver有关,每个nameserver都是1s
options timeout:1 attempts:1

        2.移植开源的dns客户端,如adns,移植很方便,唯一的不足就是这方面文档不多,需要自己理解

        3.如果此时网线、wifi或者4g断开,可以用route -n查看当前网关情况,如果当前网络节点已经断开,则删除相应路由,这样gethostbyname会直接报错返回,比如

route del default gw 192.168.100.254 dev eth0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值