首页 > 解决方案 > 对于 OpenBSD 上的原始套接字 icmp,协议族不支持 sendto 地址族

问题描述

我正在尝试为 ICMP 库编写一个 ping 函数。一切似乎都在工作,直到sendto它返回Address family not supported by protocol family。我不明白这个错误。我究竟做错了什么?

#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <netdb.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static u_int16_t
checksum(u_int16_t *arr, size_t bytes)
{
    u_int32_t  sum = 0;
    u_int16_t *ptr = arr;
    while (bytes > 1) {
        sum += *ptr++;
        bytes -= 2;
    }

    if (bytes == 1) {
        *(u_int8_t *)&sum += *(u_int8_t *)ptr;
    }

    sum  = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    return (u_int16_t)(~sum);
}

ssize_t
icmp_send(const char *host, const char *data, const size_t datalen)
{
    int s, error;
    struct addrinfo hints;
    struct addrinfo *res = NULL;

    bzero(&hints, sizeof(hints));
    hints.ai_flags    = AI_CANONNAME;
    hints.ai_family   = AF_INET;
    hints.ai_socktype = SOCK_RAW;
    hints.ai_protocol = IPPROTO_ICMP;

    if ((error = getaddrinfo(host, NULL, &hints, &res))) {
        fprintf(stderr, "ping: getaddrinfo: %s\n", gai_strerror(error));
        return -1;
    }

    struct protoent *proto = getprotobyname("icmp");
    if ((s = socket(AF_INET, SOCK_RAW, proto->p_proto)) == -1) {
        fprintf(stderr, "ping: socket: %s\n", strerror(errno));
        return -1;
    }

    int on = 1;
    if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
        fprintf(stderr, "ping: setsockopt: %s\n", strerror(errno));
        return -1;
    }

    struct sockaddr_in to;
    bzero(&to, sizeof(to));
    to.sin_family = AF_INET;

    struct ip ip;
    bzero(&ip, sizeof(ip));
    ip.ip_v   = IPVERSION;
    ip.ip_hl  = sizeof(struct ip) << 2;
    ip.ip_id  = 0;
    ip.ip_ttl = 255;
    ip.ip_p   = IPPROTO_ICMP;
    ip.ip_src.s_addr = INADDR_ANY;
    ip.ip_dst = ((struct sockaddr_in *)res)->sin_addr;
    ip.ip_sum = checksum((u_int16_t *)&ip, sizeof(ip));

    struct icmp icp;
    bzero(&icp, sizeof(icp));
    icp.icmp_type = ICMP_ECHOREPLY;
    icp.icmp_code = 0;
    icp.icmp_cksum = checksum((u_int16_t *)&icp, sizeof(icp));

    size_t packetlen = sizeof(ip) + sizeof(icp) + datalen;
    char packet[packetlen];
    memset(packet, 0, packetlen);
    memcpy((char *)packet, &ip, sizeof(ip));
    memcpy((char *)packet + sizeof(ip), &icp, sizeof(icp));
    memcpy((char *)packet + sizeof(ip) + sizeof(icp), data, packetlen);

    ssize_t snd_ret = sendto(s, packet, packetlen, 0, (const struct sockaddr *)&to, sizeof(to));
    if (errno) {
        fprintf(stderr, "ping: sendto: %s\n", strerror(errno));
        return -1;
    }

    return snd_ret;
}

标签: csocketsicmpopenbsd

解决方案


res是指向addrinfo结构的指针,而不是sockaddr. sockaddr是在成员中,它的ai_addr长度是在ai_addrlen成员中。这些应该传递给sendto().

ssize_t snd_ret = sendto(s, packet, packetlen, 0, res->ai_addr, res->ai_addrlen);

推荐阅读