Last active
July 16, 2018 13:57
-
-
Save chrisnc/b0c072ed8e9fb8cac96c to your computer and use it in GitHub Desktop.
UDP over SOCK_RAW
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <arpa/inet.h> | |
#include <netinet/in.h> | |
#include <netinet/ip.h> | |
#include <netinet/udp.h> | |
#include <sys/socket.h> | |
#define UDP_SRC 10000 | |
#define UDP_DST 15000 | |
#define MAX_PACKET_SIZE (2 << 16) | |
uint16_t checksum(const void *buf, int len) | |
{ | |
const uint8_t *data = buf; | |
uint32_t sum; | |
for (sum = 0; len >= 2; data += 2, len -= 2) | |
{ | |
sum += data[0] << 8 | data[1]; | |
} | |
if (len > 0) | |
{ | |
sum += data[0] << 8; | |
} | |
while (sum > 0xffff) | |
{ | |
sum = (sum >> 16) + (sum & 0xffff); | |
} | |
sum = htons(~sum); | |
/* | |
* From RFC 768: | |
* If the computed checksum is zero, it is transmitted as all ones (the | |
* equivalent in one's complement arithmetic). An all zero transmitted | |
* checksum value means that the transmitter generated no checksum (for | |
* debugging or for higher level protocols that don't care). | |
*/ | |
return sum ? sum : 0xffff; | |
} | |
// IP pseudo header used for checksum calculation | |
struct pseudo_hdr { | |
struct in_addr ip_src; | |
struct in_addr ip_dst; | |
uint8_t zero; | |
uint8_t ip_p; | |
uint16_t len; | |
} __attribute__((packed)); | |
// Calculate the UDP checksum given the start of the IP header. | |
uint16_t udp_checksum(const void *packet) | |
{ | |
const struct ip *ihdr = (struct ip *)packet; | |
const struct udphdr *uhdr = (struct udphdr *)(((const uint8_t *)packet) + sizeof(struct ip)); | |
#ifdef __APPLE__ | |
uint16_t udp_len = uhdr->uh_ulen; | |
#else | |
uint16_t udp_len = uhdr->len; | |
#endif | |
int cksum_len = sizeof(struct pseudo_hdr) + ntohs(udp_len); | |
uint8_t *cksum_buf = malloc(cksum_len); | |
struct pseudo_hdr *phdr = (struct pseudo_hdr *)cksum_buf; | |
phdr->ip_src = ihdr->ip_src; | |
phdr->ip_dst = ihdr->ip_dst; | |
phdr->zero = 0; | |
phdr->ip_p = ihdr->ip_p; | |
phdr->len = udp_len; | |
memcpy(cksum_buf + sizeof(struct pseudo_hdr), uhdr, ntohs(udp_len)); | |
uint16_t result = checksum(cksum_buf, cksum_len); | |
free(cksum_buf); | |
return result; | |
} | |
int main(int argc, char **argv) | |
{ | |
in_addr_t localhost = inet_addr("127.0.0.1"); | |
in_addr_t src_ip = argc > 1 ? inet_addr(argv[1]) : localhost; | |
in_addr_t dst_ip = argc > 2 ? inet_addr(argv[2]) : localhost; | |
int fd = socket(PF_INET, SOCK_RAW, IPPROTO_RAW); | |
if (fd < 0) | |
{ | |
printf("Couldn't create a raw socket.\n"); | |
return 1; | |
} | |
int hdrincl_opt = 1; | |
if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &hdrincl_opt, sizeof(hdrincl_opt)) < 0) | |
{ | |
printf("Couldn't enable IP_HDRINCL\n"); | |
close(fd); | |
return 1; | |
} | |
uint8_t *packet = malloc(MAX_PACKET_SIZE); | |
struct ip *ihdr = (struct ip *)packet; | |
ihdr->ip_hl = 5; | |
ihdr->ip_v = 4; | |
ihdr->ip_tos = 0; | |
ihdr->ip_id = 0; | |
ihdr->ip_ttl = 64; | |
ihdr->ip_p = IPPROTO_UDP; | |
ihdr->ip_src.s_addr = src_ip; | |
ihdr->ip_dst.s_addr = dst_ip; | |
struct udphdr *uhdr = (struct udphdr *)(packet + sizeof(struct ip)); | |
#ifdef __APPLE__ | |
uhdr->uh_sport = htons(UDP_SRC); | |
uhdr->uh_dport = htons(UDP_DST); | |
uhdr->uh_sum = 0; | |
#else | |
uhdr->source = htons(UDP_SRC); | |
uhdr->dest = htons(UDP_DST); | |
uhdr->check = 0; | |
#endif | |
uint8_t *payload = packet + sizeof(struct ip) + sizeof(struct udphdr); | |
#ifdef __APPLE__ | |
/* | |
* OS X lets you use an empty sockaddr with an unspecified address family and | |
* it will route to the appropriate interface based on the destination IP in | |
* the header. | |
*/ | |
struct sockaddr addr; | |
addr.sa_len = 2; | |
addr.sa_family = AF_UNSPEC; | |
memset(addr.sa_data, 0, sizeof(addr.sa_data)); | |
#else | |
/* | |
* As far as I can tell, sending to an empty/unspecified socket address won't | |
* work in Linux, so we create a sockaddr_in with the same IP address as the | |
* destination IP in the header, but don't specify the port, since we are | |
* constructing a UDP packet manually. | |
*/ | |
struct sockaddr_in addr; | |
addr.sin_family = AF_INET; | |
addr.sin_port = 0; | |
addr.sin_addr.s_addr = dst_ip; | |
memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); | |
#endif | |
size_t n = 0; | |
char *line = NULL; | |
for (;;) | |
{ | |
ssize_t len = getline(&line, &n, stdin); | |
if (len == -1) | |
{ | |
break; | |
} | |
size_t total_len = sizeof(struct ip) + sizeof(struct udphdr) + len; | |
if (total_len > MAX_PACKET_SIZE) | |
{ | |
printf("The message is too large to fit into a packet.\n"); | |
continue; | |
} | |
memcpy(payload, line, len); | |
ihdr->ip_len = htons(total_len); | |
ihdr->ip_sum = 0; | |
ihdr->ip_sum = checksum(ihdr, sizeof(struct ip)); | |
#ifdef __APPLE__ | |
uhdr->uh_ulen = htons(sizeof(struct udphdr) + len); | |
uhdr->uh_sum = 0; | |
uhdr->uh_sum = udp_checksum(packet); | |
#else | |
uhdr->len = htons(sizeof(struct udphdr) + len); | |
uhdr->check = 0; | |
uhdr->check = udp_checksum(packet); | |
#endif | |
/* | |
* For some reason, the IP header's length field needs to be in host byte order | |
* in OS X (and other BSDs maybe?). | |
*/ | |
#ifdef __APPLE__ | |
ihdr->ip_len = total_len; | |
#endif | |
ssize_t bytes_sent = sendto(fd, packet, total_len, 0, (const struct sockaddr *)&addr, sizeof(addr)); | |
if (bytes_sent == -1) | |
{ | |
printf("There was an error sending the packet.\n"); | |
continue; | |
} | |
printf("%ld bytes were sent.\n", bytes_sent); | |
} | |
free(packet); | |
free(line); | |
close(fd); | |
return 0; | |
} |
Author
chrisnc
commented
Jul 20, 2015
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment