После проблем с вычислением контрольных сумм я наконец нашел решение, используя Berkeley Packet Filters .
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#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 <net/bpf.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <netinet/udp.h>
#define INTERFACE "en0"
#define DESTMAC "ED:5B:B6:29:43:D5"
#define SRCMAC "5E:F1:28:36:5E:DB"
#define DESTIP "192.168.178.27"
#define DESTPORT 23455
#define SRCPORT 23456
Функции контрольной суммы
uint32_t crc32(const void *m, size_t len)
{
const unsigned char *message = m;
size_t i;
int j;
unsigned int byte, crc, mask;
i = 0;
crc = 0xFFFFFFFF;
while (i < len) {
byte = message[i]; // Get next byte.
crc = crc ^ byte;
for (j = 7; j >= 0; j--) { // Do eight times.
mask = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
i = i + 1;
}
return ~crc;
}
uint32_t eth_cks(const void *data, size_t frame_len)
{
if (data == NULL) return 0;
const unsigned char * const eth_frame = data;
char str[(frame_len - 4) * 2 + 1]; // +1 for null terminator.
memset(str, 0, (frame_len - 4) * 2 + 1);
for (int i = 0; i < (frame_len - 4) * 2; i += 2)
sprintf(&str[i], "%02x", eth_frame[i / 2]);
size_t len = strlen(str), len2 = (len + 1) / 2;
unsigned char arr2[len2];
for (size_t i = 0; i < len; i += 2)
arr2[i / 2] = strtoul((char[3]) {str[i], str[i + 1], '\0'}, 0, 16);
return crc32(arr2, len2);
}
uint16_t ip4_cks(const void *vdata, size_t length)
{
// Cast the data pointer to one that can be indexed.
char* data = (char *)vdata;
// Initialise the accumulator.
uint64_t acc = 0xffff;
// Handle any partial block at the start of the data.
unsigned int offset = ((uintptr_t)data)&3;
if (offset)
{
size_t count = 4 - offset;
if (count > length) count = length;
uint32_t word = 0;
memcpy(offset + (char *)&word, data, count);
acc += ntohl(word);
data += count;
length -= count;
}
// Handle any complete 32-bit blocks.
char* data_end = data + (length &~3);
while (data != data_end)
{
uint32_t word;
memcpy(&word, data, 4);
acc += ntohl(word);
data += 4;
}
length &= 3;
// Handle any partial block at the end of the data.
if (length)
{
uint32_t word = 0;
memcpy(&word, data, length);
acc += ntohl(word);
}
// Handle deferred carries.
acc = (acc & 0xffffffff) + (acc >> 32);
while (acc >> 16)
acc = (acc & 0xffff) + (acc >> 16);
// If the data began at an odd byte address
// then reverse the byte order to compensate.
if (offset&1)
acc = ((acc & 0xff00) >> 8) | ((acc & 0x00ff) << 8);
// Return the checksum in network byte order.
return htons(~acc);
}
uint16_t udp_cks(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;
}
Мы создадим пакет в нашей основной функции.
int main(int argc, char **argv)
{
int bpf_f = 0;
struct ifreq ifreq_ip = { {0} }, ifreq_bpf = { {0} };
unsigned char *packet;
struct ether_header *eth;
struct ifaddrs *ifaddr = NULL;
unsigned int pckt_len = 0, if_c = 0;
uint32_t *fcs;
if (!(packet = malloc(PKT_SIZ)))
{
perror("Could not allocate memory for packet");
exit(EXIT_FAILURE);
}
memset(packet, 0, PKT_SIZ);
fcs = (uint32_t *)(packet + PKT_SIZ - SIZEOF_UINT32_T);
Теперь мы ищем доступный bpf устройство, которое может быть читаемым и доступным для записи .
char buf[11] = { 0 };
for (int ext = 1; ext < 99; ++ext)
{
sprintf(buf, "/dev/bpf%i", ext);
if ((bpf_f = open(buf, O_RDWR)) != -1)
break;
}
if (!bpf_f)
{
fprintf(stderr, "Could not open BPF device.\n");
exit(EXIT_FAILURE);
}
// Bind BPF to physical device.
// Copy the interface name to the request.
strcpy(ifreq_bpf.ifr_name, ifaddr->ifa_name);
if (ioctl(bpf_f, BIOCSETIF, &ifreq_bpf) == -1)
{
perror("Could not bind BPF to physical device");
exit(EXIT_FAILURE);
}
Теперь можно также отключить автоматическое заполнение заголовка MAC-адреса источника.Для этого взгляните на IOCTL из bpf (особенно BIOCGHDRCMPLT
и его set аналог).
EthernetКонструкция заголовка
eth = (struct ether_header *)packet;
// Copy the Source (local) NIC MAC address to the packet (ethernet header). It's already in network format.
memcpy(eth->ether_shost, ether_aton(SRCMAC)->octet, ETHER_ADDR_LEN);
// Copy the destination NIC MAC address to the packet (ethernet header).
// ether_aton converts a human-readable NIC MAC to network format.
В настоящее время это работает только для локальных адресов.Мы не проверяем, является ли указанный IP-адрес частью подсети.
memcpy(eth->ether_dhost, ether_aton(DESTMAC)->octet, ETHER_ADDR_LEN);
// Specifiy the ethernet overlaying type.
eth->ether_type = htons(ETHERTYPE_IP);
printf("Source MAC: %s\n", ether_ntoa((const struct ether_addr *)eth->ether_shost));
printf("Desti. MAC: %s\n", ether_ntoa((const struct ether_addr *)eth->ether_dhost));
printf("Eth Type: 0x%x%s\n", ntohs(eth->ether_type), ntohs(eth->ether_type) == ETHERTYPE_IP ? " (IP)" : "");
// Get IF assigned IP address.
strncpy(ifreq_ip.ifr_name, ifreq_bpf.ifr_name, IFNAMSIZ - 1);
in_socket_t sock;
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("Could not create socket");
exit(EXIT_FAILURE);
}
#if defined(SIOCGIFADDR)
if (ioctl(sock, SIOCGIFADDR, &ifreq_ip) == -1)
{
fprintf(stderr, "Could not get IP address for interface %s: %s\n", ifreq_ip.ifr_name, strerror(errno));
}
#else
#error "Not yet implemented"
#endif
close(sock);
pckt_len += sizeof(*eth);
Конструкция заголовка IP
struct ip *iph = (struct ip *)(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));
Конструкция заголовка UDP
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 = ip4_cks(packet + sizeof(*eth), sizeof(*iph));
udph->uh_sum = udp_cks(udph, ntohs(udph->uh_ulen), iph->ip_src.s_addr, iph->ip_dst.s_addr);
*fcs = eth_cks(packet, PKT_SIZ);
if (write(bpf_f, packet, PKT_SIZ) == -1)
{
perror("Could not write to bpf");
exit(EXIT_FAILURE);
}
free(packet);
packet = NULL;
close(bpf_f);
return 0;
}