Skip to content

Instantly share code, notes, and snippets.

@chrisnc
Last active July 16, 2018 13:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisnc/b0c072ed8e9fb8cac96c to your computer and use it in GitHub Desktop.
Save chrisnc/b0c072ed8e9fb8cac96c to your computer and use it in GitHub Desktop.
UDP over SOCK_RAW
#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;
}
@chrisnc
Copy link
Author

chrisnc commented Jul 20, 2015

$ make udp_sock_raw
$ sudo ./udp_sock_raw
foo
32 bytes were sent.
bar
32 bytes were sent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment