c - 为什么 sendto() 返回 'Invalid argument' ?

标签 c sockets networking packet bsd

我编写了一个小型测试程序,以便通过原始套接字发送自己的数据包。
我想从头开始创建数据包。
目标操作系统是 FreeBSD/Mac OSX x86_64。
我的编译器是 gcc with Apple LLVM 10。
我以 sudo 权限运行该程序。

出于某种原因,sendto() 总是返回“无效参数”错误,我不知道为什么。我很想解决这个问题。

我设置了 IP_HDRINCL 标志并通过 bind 调用绑定(bind)到特定的网络接口(interface)。但是,sendto 似乎对它接收到的数据包不满意。

无论如何,到目前为止我的代码是这样的:

#include <stdio.h>
#include <stdlib.h>         // EXIT_FAILURE EXIT_SUCCESS
#include <stdbool.h>        // bool
#include <string.h>         // strlen(), memcpy()
#include <sys/socket.h>     // socket()
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>     // IPPROTO_TCP
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/in_systm.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <netinet/if_ether.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <net/if_dl.h>
#include <ifaddrs.h>
#include <net/ethernet.h>
#include <netinet/udp.h>


#define DESTMAC     "ed:5b:b6:29:43:d5" 
#define DESTIP      "192.168.178.25" 
#define DESTPORT    23452
#define SRCPORT     23451
#define PKT_SIZ     64

#ifndef AF_PACKET
#   ifdef PF_LINK
#       define AF_PACKET PF_LINK
#   elif defined (AF_LINK)
#       define AF_PACKET AF_LINK
#   endif
#endif



typedef int in_socket_t;

uint16_t udp_checksum(struct udphdr *p_udp_header, size_t len, uint32_t src_addr, uint32_t dest_addr)
{
    const uint16_t *buf = (const uint16_t*)p_udp_header;
    uint16_t *ip_src = (void*)&src_addr, *ip_dst = (void*)&dest_addr;
    uint32_t sum;
    size_t length = len;

    // Calculate the sum
    sum = 0;
    while (len > 1)
    {
        sum += *buf++;
        if (sum & 0x80000000)
            sum = (sum & 0xFFFF) + (sum >> 16);
        len -= 2;
    }

    if (len & 1)
        // Add the padding if the packet lenght is odd
        sum += *((uint8_t*)buf);

    // Add the pseudo-header
    sum += *(ip_src++);
    sum += *ip_src;

    sum += *(ip_dst++);
    sum += *ip_dst;

    sum += htons(IPPROTO_UDP);
    sum += htons(length);

    // Add the carries
    while (sum >> 16)
        sum = (sum & 0xFFFF) + (sum >> 16);

    // Return the one's complement of sum
    return (uint16_t)~sum;
}

unsigned short checksum(unsigned short *buf, int _16bitword)
{
    unsigned long sum; 
    for (sum = 0; _16bitword > 0; _16bitword--)
        sum += htons(*(buf)++);
    sum = ((sum >> 16) + (sum & 0xFFFF));
    sum += (sum >> 16);
    return (unsigned short)~sum;
}

int main(int argc, const char **argv)
{
    in_socket_t sock_r;
    struct ifreq ifreq_i = { 0 }, ifreq_c = { 0 }, ifreq_ip = { 0 };
    unsigned char *packet = NULL;
    struct ether_header *eth = NULL;
    struct ifaddrs *ifaddr = NULL;
    unsigned int if_c = 0, pckt_len = 0;
    struct ether_addr *eth_daddr;
    ssize_t send_len;
    const int on_f = 1;

    if ((packet = (unsigned char *) malloc(PKT_SIZ)) == NULL)
    {
        perror("Could not allocate packet memory");
        exit(EXIT_FAILURE);
    }
    memset(packet, 0, PKT_SIZ);

    /// 1. Ethernet Header Construction
    puts("PHASE 1: Ethernet Header Construction");
    eth = (struct ether_header *)packet;

    if ((sock_r = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
    {
        perror("Could not create socket");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(sock_r, IPPROTO_IP, IP_HDRINCL, &on_f, sizeof(on_f)) == -1)
    {
        perror("Could not request manually including header within data");
        exit(EXIT_FAILURE);
    }

    // Get IF Index
    strncpy(ifreq_i.ifr_name, "en7", IFNAMSIZ - 1);
#if !defined(SIOCGIFNAME)
    if (!(ifreq_i.ifr_intval = if_nametoindex(ifreq_i.ifr_name)))
    {
        fprintf(stderr, "Could not get interface name for interface %s: %s\n", ifreq_i.ifr_name, strerror(errno));
        exit(EXIT_FAILURE);
    }
#else 
#error "Not yet implemented."
#endif

    // Get IF MAC Address.
    strncpy(ifreq_c.ifr_name, ifreq_i.ifr_name, IFNAMSIZ - 1);
#ifndef SIOCGIFHWADDR
    if (getifaddrs(&ifaddr) == -1)
    {
        perror("Could not get interface address");
        exit(EXIT_FAILURE);
    }
    for (; ifaddr->ifa_next; ifaddr = ifaddr->ifa_next, if_c++)
    {
        if (!strcmp(ifaddr->ifa_name, ifreq_c.ifr_name) && ifaddr->ifa_addr && ifaddr->ifa_addr->sa_family == AF_PACKET)
        {
            // Copy the Source (local) NIC MAC address to the packet (ethernet header). It's already in network format.
            memcpy(eth->ether_shost, (unsigned char *) LLADDR((struct sockaddr_dl *) ifaddr->ifa_addr), ETHER_ADDR_LEN);
        }
    }
    freeifaddrs(ifaddr - if_c);
#elif defined(SIOCGIFHWADDR)
    if (ioctl(sock_r, SIOCGIFHWADDR, &ifreq_c) == -1)
    {
        fprintf(stderr, "Could not get MAC address for interface %s: %s\n", ifreq_c.ifr_name, strerror(errno));
        exit(EXIT_FAILURE);
    }
#else
#error "Not yet implemented."
#endif

    // Get IF assigned IP Address.
    strncpy(ifreq_ip.ifr_name, ifreq_c.ifr_name, IFNAMSIZ - 1);
#if defined(SIOCGIFADDR)
    if (ioctl(sock_r, SIOCGIFADDR, &ifreq_ip) == -1)
    {
        fprintf(stderr, "Could not get IP address for interface %s: %s\n", ifreq_ip.ifr_name, strerror(errno));
        exit(EXIT_FAILURE);
    }
#else
#error "Not yet implemented."
#endif
    // Copy the destination NIC MAC address to the packet (ethernet header). 
    // ether_aton converts a human-readable NIC MAC to network format.
    // TODO: Remember that we don't want to have a fixed destination NIC MAC, we'll probably receive it with an ARP request in the future. 
    if ((eth_daddr = ether_aton(DESTMAC)) == NULL)
    {
        perror("Could not convert destination NIC MAC address to network format");
        exit(EXIT_FAILURE);
    }
    memcpy(eth->ether_dhost, eth_daddr->octet, ETHER_ADDR_LEN);

    eth->ether_type = htons(ETHERTYPE_IP);

    // Calculate total packet length. 
    pckt_len += sizeof(*eth);

    printf("Source Host: %s\n", ether_ntoa((const struct ether_addr *)eth->ether_shost));
    printf("Desti. Host: %s\n", ether_ntoa((const struct ether_addr *)eth->ether_dhost));
    printf("Ether. Type: 0x%0x%s\n", ntohs(eth->ether_type), ntohs(eth->ether_type) == 0x800 ? " (IP)" : "");


    if (bind(sock_r, (struct sockaddr *)ifaddr->ifa_addr, sizeof(struct sockaddr)) == -1) 
    {
        perror("Could not bind to specific interface");
        exit(EXIT_FAILURE);
    }


    /// 2. IP Header Construction
    puts("\nPHASE 2: IP Header Construction");
    struct ip *iph = (struct ip *)(packet + pckt_len);
    // printf("IP Header %p (eth hdr siz: %i) begins at %p.\n", packet, pckt_len, packet + pckt_len);
    iph->ip_hl = sizeof(struct ip) >> 2;
    iph->ip_v = IPVERSION;      // 4
    iph->ip_tos = 16;
    iph->ip_id = htons(10201);  // any unique ID.
    iph->ip_ttl = 64;
    iph->ip_p = IPPROTO_UDP;     // UDP (User datagram protocol)
    iph->ip_src.s_addr = ((struct sockaddr_in *)&ifreq_ip.ifr_ifru.ifru_addr)->sin_addr.s_addr;
    if (!inet_aton(DESTIP, &iph->ip_dst)) 
    {
        perror("Could not interpret destination IP address");
        exit(EXIT_FAILURE);
    }

    // Calculate total packet length. 
    pckt_len += sizeof(*iph);

    printf("Header length: %i\n", iph->ip_hl);
    printf("Version      : %i%s\n", iph->ip_v, iph->ip_v == IPVERSION ? " (IPv4)" : "");
    printf("Type of Serv.: %i\n", iph->ip_tos);
    printf("Identificati.: %i\n", ntohs(iph->ip_id));
    printf("Time to live : %i\n", iph->ip_ttl);
    printf("Protocol     : %i%s\n", iph->ip_p, iph->ip_p == IPPROTO_UDP ? " (UDP)" : "");
    printf("Source Addre.: %s%s\n", inet_ntoa(iph->ip_src), " (local IP)");
    printf("Dest. Address: %s\n", inet_ntoa(iph->ip_dst));



    /// 3. UDP Header Construction
    struct udphdr *udph = (struct udphdr *)(packet + pckt_len);
    udph->uh_sport = htons(SRCPORT);
    udph->uh_dport = htons(DESTPORT);
    udph->uh_sum = 0;

    // Calculate total packet length. 
    pckt_len += sizeof(*udph);

    // Actual UDP Payload: 
    packet[pckt_len++] = 0xAA;
    packet[pckt_len++] = 0xBB;
    packet[pckt_len++] = 0xCC;
    packet[pckt_len++] = 0xDD;
    packet[pckt_len++] = 0xEE;

    // Fill out remaining length header fields: 
    // UDP length field
    udph->uh_ulen = htons(pckt_len - sizeof(*iph) - sizeof(*eth));
    // IP length field
    iph->ip_len = htons(pckt_len - sizeof(*eth));

    // Finally, calculate the checksum.
    iph->ip_sum = checksum((unsigned short *)(packet + sizeof(*eth)), sizeof(*iph) / 2);
    udph->uh_sum = udp_checksum(udph, pckt_len, iph->ip_src.s_addr, iph->ip_dst.s_addr);

    struct sockaddr_dl saddr_dl = { 0 };
    memset(&saddr_dl, 0, sizeof(struct sockaddr_dl));
    saddr_dl.sdl_index = ifreq_i.ifr_intval;
    saddr_dl.sdl_family = AF_LINK;
    saddr_dl.sdl_type = IFRTYPE_FUNCTIONAL_WIRED; // APPLE_IF_FAM_ETHERNET
    saddr_dl.sdl_nlen = strlen(ifreq_i.ifr_name);
    saddr_dl.sdl_len = sizeof(struct sockaddr_dl);
    saddr_dl.sdl_alen = ETHER_ADDR_LEN;
    memcpy(saddr_dl.sdl_data, eth_daddr->octet, ETHER_ADDR_LEN);

    puts("\nPHASE 4: SENDING PACKET");
    printf("Packet length: %i\n", pckt_len);
    printf("Interface index: %i; %i\n", saddr_dl.sdl_index, ifreq_i.ifr_intval);

    // Send the packet.
    if ((send_len = sendto(sock_r, packet, PKT_SIZ, 0, (const struct sockaddr *)&saddr_dl, sizeof(saddr_dl))) == -1) 
    {
        perror("Could not send packet");
        exit(EXIT_FAILURE);
    }
    printf("Successfully sent packet with data length: %lu\n", send_len);

    return 0;
}

不要担心错误检查,由于代码已经非常大,我将其省略。

这是其中一个结果:

PHASE 1: Ethernet Header Construction
Source Host: 5E:F1:28:36:5E:DB
Desti. Host: ED:5B:B6:29:43:D5
Ether. Type: 0x800

PHASE 2: IP Header Construction
IP Header 0x5fcde63019a0 (eth hdr siz: 14) begins at 0x5fcde63019ae.
Header length: 5
Version      : 4 (IPv4)
Type of Serv.: 16
Identificati.: 10201
Time to live : 68
Protocol     : 17 (UDP)
Source Addre.: 192.168.178.21 (local IP)
Dest. Address: 192.168.178.25

PHASE 4: SENDING PACKET
Interface Index: 7
Packet length: 47
Destination mac: ed:5b:b6:29:43:d5
Could not send packet: Invalid argument

编辑

我正在将 sockaddr_dl 结构转换为 sockaddr。它们都在它们的头文件中定义如下:

struct sockaddr {
    __uint8_t   sa_len;     /* total length */
    sa_family_t sa_family;  /* [XSI] address family */
    char        sa_data[14];    /* [XSI] addr value (actually larger) */
};
struct sockaddr_dl {
    u_char  sdl_len;    /* Total length of sockaddr */
    u_char  sdl_family; /* AF_LINK */
    u_short sdl_index;  /* if != 0, system given index for interface */
    u_char  sdl_type;   /* interface type */
    u_char  sdl_nlen;   /* interface name length, no trailing 0 reqd. */
    u_char  sdl_alen;   /* link level address length */
    u_char  sdl_slen;   /* link layer selector length */
    char    sdl_data[12];   /* minimum work area, can be larger;
                   contains both if name and ll address */
};

最佳答案

socket(PF_INET, SOCK_RAW, IPPROTO_RAW); 使您可以访问 IP 级别。要访问以太网级别,在 linux 中,您需要使用 PF_PACKET 并获取类型为 socket(PF_PACKET, SOCK_RAW, SOCK_DATAGRAM); 的套接字。

恐怕您正在对 IP 数据报之上的以太网数据包进行格式化。有关此类套接字的文档,请参阅 packet(7)

此外,在每个 系统调用中检查错误绝对是个好主意。这是了解底层情况的好方法。

此外,如果您向您发送数据包(目标以太网地址与源以太网地址相同),您将永远无法将数据包返回给您。在发送此类数据包之前,软件应检测目标地址和源地址是否相同。由于所有系统都尽量不消耗带宽,通常以太网卡会将您的数据包广播出去。但是没有那么多以太网在发送数据包时监听以太网。您可能会被这种行为所欺骗,但这是常见的做事方式(不要发送指向您的数据包)。

EINVAL 错误最可能的原因可能是您将 -1 作为套接字参数传递,只是因为您假设您的 socket(2 ) 调用进行得很顺利,但实际上并没有,你认为你不需要在那里检查错误。但是您在 IP pdu 的有效负载中格式化以太网数据包。有很多事情可能会出错。您传递给套接字接口(interface)的 sockaddr 可能无效、长度错误、内部格式错误。所有这些都偏离了无效的协议(protocol)系列,这会使您收到错误的套接字类型,甚至更糟的是错误。

另一件事:不要犹豫,发布 complete (with all the headers you used) and out of the box verifiable example .这是必须遵循的。您可能使用了错误的头文件,这可能是来源或错误,但如果您在发布前将其剪切,我们将无法得知。如果您甚至添加用于构建示例的 Makefile 会更好。这样就没有人可以说好吧,我试过你的示例代码,但它甚至没有编译。你把工作留给我们所有人,他们首先要将你的代码修复成某种可用的代码,可能会纠正你的错误。您在解释中假设您所做的一切都是正确的,因此您认为您不需要发布无趣的数据,如头文件等,并且您正在不断地添加错误,使调试代码变得完全不可能。

关于c - 为什么 sendto() 返回 'Invalid argument' ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53525113/

相关文章:

c - 刷新 GTK 中的表

任何人都可以解释以下 C 程序的输出吗?

python - 将 Python 对象传递给另一个 Python 进程

linux - 使用 rtnetlink 套接字在 linux 路由表中安装新路由

ios - 尝试清理 iOS OAuth 代码

c - 信号量值初始化为-1?

c - 参数是否被视为自动变量?

c# - 如何用 WCF 替换现有的 .Net 套接字服务器

java - 如何手动启用 LibGDX 网络?

c++ - 如何确定子线程总数?