【发布时间】:2018-11-28 17:35:06
【问题描述】:
我编写了一个小型测试程序,以便通过原始套接字发送自己的数据包。
我想从头开始创建数据包。
目标操作系统是 FreeBSD/Mac OSX x86_64。
我的编译器是带有 Apple LLVM 10 的 gcc。
我使用 sudo 权限运行程序。
由于某种原因,sendto() 总是返回“无效参数”错误,我不知道为什么。我很想解决这个问题。
我设置了IP_HDRINCL 标志并通过bind 调用绑定到特定网络接口。然而,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 */
};
【问题讨论】:
-
请提供Minimal Complete and verificable example。如果您知道错误可能出在哪里,请隔离那段代码,不要在此处张贴整面墙。
-
我想过。而且我真的不知道错误可能在哪里。我没有发布整段代码。但是在网上搜索 sendto 错误消息时,我已经阅读了所有内容。可能与套接字创建或数据包本身有关,这几乎就是一切。换句话说,如果您知道如何缩小潜在问题的范围,请告诉我。
-
不,在您自己的代码中定义 sockaddr_ll 不是一个好主意。如果您的系统未提供定义,则可能不受支持。
-
您是否尝试过转储您生成的数据包,并将其与您使用 Wireshark 捕获的真实数据样本进行比较,看看您是否发现任何遗漏?例如,
saddr_dl有很多您不需要设置的属性,例如sdl_type、sdl_nlen和sdl_slen。 -
socket(PF_INET, SOCK_RAW, IPPROTO_RAW);让您可以访问 IP 级别。要访问以太网级别,在 linux 中,您需要使用PF_PACKET并获取socket(PF_PACKET, SOCK_RAW, SOCK_DATAGRAM);类型的套接字。恐怕您是在 IP 数据报之上格式化以太网数据包。有关此类套接字的文档,请参阅 packet(7)。此外,检查每个系统调用中的错误是个好主意。
标签: c sockets networking packet bsd