OpenBSD 上原始套接字 icmp 的协议族不支持 sendto 地址族

sendto Address family not supported by protocol family for raw socket icmp on OpenBSD

我正在尝试为 ICMP 库编写一个 ping 函数。一切似乎都在 sendto 之前正常工作,那时它 returns 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;
}

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

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