Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Created October 28, 2024 17:58
Show Gist options
  • Save x-yuri/e3d44aa33e35b55e741ada184bb23eba to your computer and use it in GitHub Desktop.
Save x-yuri/e3d44aa33e35b55e741ada184bb23eba to your computer and use it in GitHub Desktop.
Making a STUN request in C

Making a STUN request in C

a.c:

#include <stdlib.h>
#include <sys/socket.h>
#include <err.h>
#include <stdio.h>
#include <sys/random.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

#include "crc.h"

#define ATTR_MAPPED_ADDRESS      0x0001
#define ATTR_XOR_MAPPED_ADDRESS  0x0020
#define ATTR_SOFTWARE            0x8022
#define ATTR_FINGERPRINT         0x8028
#define ATTR_RESPONSE_ORIGIN     0x802b

#define STUN_SERVER_IP "xx.yyy.xx.yyy"
#define STUN_SERVER_PORT 3478
#define BINDING_REQUEST_TYPE 0x0001
#define BINDING_SUCCESS_RESPONSE_TYPE 0x0101
#define MAGIC_COOKIE 0x2112a442
#define CRC_XOR_VALUE 0x5354554e

struct header {
    uint16_t type;
    uint16_t len;
    uint32_t cookie;
    uint8_t tx_id[12];
};

struct attr_header {
    uint16_t type;
    uint16_t len;
};

struct address_attr {
    struct attr_header header;
    uint8_t reserved;
    uint8_t family;
    uint16_t port;
    uint32_t addr;
};

struct software_attr {
    struct attr_header header;
    char value[];
};

struct fingerprint_attr {
    struct attr_header header;
    uint32_t value;
};

char *invocation_name;
const char *attr_names[] = {
    "MAPPED-ADDRESS",
    "XOR-MAPPED-ADDRESS",
    "SOFTWARE",
    "FINGERPRINT",
    "RESPONSE-ORIGIN",
};

static void process_address_attr(
    const struct address_attr *,
    size_t,
    const struct header *,
    uint32_t *,
    uint16_t *
);
static const char *attr_name(uint16_t);
_Noreturn static void handle_error(const char*, ...);

static void process_response(
    const struct header *req,
    const uint8_t *resp,
    size_t n_resp
) {
    if (n_resp < sizeof(struct header))
        handle_error("the response must be at least 20 bytes long (%u)", n_resp);

    const struct header *header = (const struct header *)resp;
    printf("\n");
    printf("type: 0x%04x\n", ntohs(header->type));
    printf("len: %u\n", ntohs(header->len));
    printf("cookie: 0x%08x\n", ntohl(header->cookie));
    printf("tx id: 0x%08x%08x%08x\n", ntohl(*(unsigned int *)header->tx_id),
                                      ntohl(*(unsigned int *)(header->tx_id + 4)),
                                      ntohl(*(unsigned int *)(header->tx_id + 8)));
    if (ntohs(header->type) != BINDING_SUCCESS_RESPONSE_TYPE)
        handle_error("unexpected response message type (0x%04x)", ntohs(header->type));
    if (ntohl(header->cookie) != MAGIC_COOKIE)
        handle_error("incorrect cookie value (0x%08x)", ntohl(header->cookie));
    if (memcmp(req->tx_id, header->tx_id, sizeof header->tx_id) != 0)
        handle_error("transaction ids differ (0x%08x%08x%08x, 0x%08x%08x%08x)",
            ntohl(*(unsigned int *)req->tx_id),
            ntohl(*(unsigned int *)(req->tx_id + 4)),
            ntohl(*(unsigned int *)(req->tx_id + 8)),
            ntohl(*(unsigned int *)header->tx_id),
            ntohl(*(unsigned int *)(header->tx_id + 4)),
            ntohl(*(unsigned int *)(header->tx_id + 8)));

    size_t n_processed = sizeof(struct header);
    const uint8_t *p = resp + sizeof(struct header);
    uint32_t mapped_addr = 0;
    uint16_t mapped_port = 0;
    while (n_resp > n_processed) {
        if (n_resp - n_processed < sizeof(struct attr_header))
            handle_error("an attribute header takes %u bytes, %u bytes left",
                sizeof(struct attr_header), n_resp - n_processed);
        const struct attr_header *attr_header = (const struct attr_header *)p;
        printf("\n");
        printf("type: 0x%04x (%s)\n", ntohs(attr_header->type), attr_name(ntohs(attr_header->type)));
        printf("len: %u\n", ntohs(attr_header->len));
        switch (ntohs(attr_header->type)) {
            case ATTR_MAPPED_ADDRESS:
                process_address_attr((struct address_attr *)p,
                                     n_resp - n_processed,
                                     header,
                                     &mapped_addr, &mapped_port);
                n_processed += sizeof(struct address_attr);
                p += sizeof(struct address_attr);
                break;
            case ATTR_XOR_MAPPED_ADDRESS:
                process_address_attr((struct address_attr *)p,
                                     n_resp - n_processed,
                                     header,
                                     &mapped_addr, &mapped_port);
                n_processed += sizeof(struct address_attr);
                p += sizeof(struct address_attr);
                break;
            case ATTR_SOFTWARE: {
                if (n_resp - n_processed < sizeof(struct software_attr) + ntohs(attr_header->len)) {
                    handle_error("SOFTWARE attribute takes %u bytes, %u bytes left",
                        sizeof(struct software_attr) + ntohs(attr_header->len),
                        n_resp - n_processed);
                }
                const struct software_attr *attr = (const struct software_attr *)p;
                printf("software: %.*s\n", ntohs(attr_header->len), attr->value);
                n_processed += sizeof(struct software_attr) + ntohs(attr_header->len);
                p += sizeof(struct software_attr) + ntohs(attr_header->len);
                break;
            } case ATTR_FINGERPRINT: {
                if (n_resp - n_processed < sizeof(struct fingerprint_attr)) {
                    handle_error("FINGERPRINT attribute takes %u bytes, %u bytes left",
                        sizeof(struct fingerprint_attr),
                        n_resp - n_processed);
                }
                const struct fingerprint_attr *attr = (const struct fingerprint_attr *)p;
                printf("CRC-32: 0x%08x\n", ntohl(attr->value));
                if (ntohl(attr->value) != (crc(resp, p - resp) ^ CRC_XOR_VALUE))
                    handle_error("CRC-32 doesn't check out");
                n_processed += sizeof(struct fingerprint_attr);
                p += sizeof(struct fingerprint_attr);
                if (n_processed != n_resp)
                    handle_error("FINGERPRINT must be the last attribute");
                break;
            } case ATTR_RESPONSE_ORIGIN:
                process_address_attr((const struct address_attr *)p,
                                     n_resp - n_processed,
                                     header,
                                     &mapped_addr, &mapped_port);
                n_processed += sizeof(struct address_attr);
                p += sizeof(struct address_attr);
                break;
            default: handle_error("unknown attr type (0x%04x)", ntohs(attr_header->type));
        }
    }
}

static void process_address_attr(
    const struct address_attr *attr,
    size_t n_left,
    const struct header *header,
    uint32_t *mapped_addr,
    uint16_t *mapped_port
) {
    if (n_left < sizeof(struct address_attr)) {
        const char *name = attr_name(ntohs(attr->header.type));
        handle_error("%s attribute takes %u bytes, %u bytes left",
            name,
            sizeof(struct address_attr),
            n_left);
    }
    printf("protocol family: 0x%02x\n", attr->family);

    uint16_t port = ntohs(attr->header.type) == ATTR_XOR_MAPPED_ADDRESS
        ? ntohs(attr->port) ^ (ntohl(header->cookie) >> 16)
        : ntohs(attr->port);
    printf("port: %u\n", port);

    uint32_t addr;
    char ip[INET_ADDRSTRLEN];
    const char *rc;
    if (ntohs(attr->header.type) == ATTR_XOR_MAPPED_ADDRESS) {
        addr = ntohl(attr->addr) ^ ntohl(header->cookie);
        uint32_t ipbin = htonl(addr);
        rc = inet_ntop(AF_INET, &ipbin, ip, sizeof ip);
    } else {
        addr = ntohl(attr->addr);
        rc = inet_ntop(AF_INET, &attr->addr, ip, sizeof ip);
    }
    if (rc == NULL) err(1, "inet_ntop()");
    printf("ip: %s\n", ip);

    if (ntohs(attr->header.type) == ATTR_XOR_MAPPED_ADDRESS
    || ntohs(attr->header.type) == ATTR_MAPPED_ADDRESS) {
        if (*mapped_addr && *mapped_port) {
            char this_attr[sizeof "XOR-MAPPED-ADDRESS"];
            char other_attr[sizeof "XOR-MAPPED-ADDRESS"];
            strcpy(this_attr, ntohs(attr->header.type) == ATTR_XOR_MAPPED_ADDRESS
                ? "XOR-MAPPED-ADDRESS"
                : "MAPPED-ADDRESS");
            strcpy(other_attr, ntohs(attr->header.type) == ATTR_XOR_MAPPED_ADDRESS
                ? "MAPPED-ADDRESS"
                : "XOR-MAPPED-ADDRESS");
            if (*mapped_addr != addr)
                handle_error("addresses in %s and %s doesn't match (%04x, %04x)",
                    this_attr, other_attr, addr, *mapped_addr);
            if (ntohs(attr->header.type) == ATTR_XOR_MAPPED_ADDRESS && *mapped_port != port)
                handle_error("ports in %s and %s doesn't match (%04x, %04x)",
                    this_attr, other_attr, port, *mapped_port);
        }
        *mapped_addr = addr;
        *mapped_port = port;
    }
}

static const char *attr_name(uint16_t type) {
    switch (type) {
        case ATTR_MAPPED_ADDRESS:      return "MAPPED-ADDRESS";
        case ATTR_XOR_MAPPED_ADDRESS:  return "XOR-MAPPED-ADDRESS";
        case ATTR_SOFTWARE:            return "SOFTWARE";
        case ATTR_FINGERPRINT:         return "FINGERPRINT";
        case ATTR_RESPONSE_ORIGIN:     return "RESPONSE-ORIGIN";
        default: handle_error("attr_name: unknown attr type (0x%02x)", type);
    }
}

_Noreturn static void handle_error(const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    fprintf(stderr, "%s: ", invocation_name);
    vfprintf(stderr, fmt, args);
    puts("");
    va_end(args);
    exit(1);
}

int main(int argc, char **argv)
{
    (void) argc; /* unused */
    invocation_name = argv[0];

    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd == -1) err(1, "socket()");

    struct sockaddr_in addr =  {
        .sin_family = AF_INET,
        .sin_port = htons(STUN_SERVER_PORT),
    };
    int ri = inet_pton(AF_INET, STUN_SERVER_IP, &addr.sin_addr);
    if (ri == -1) err(1, "inet_pton()");
    else if (ri == 0)
        handle_error("invalid ip address (%s)", STUN_SERVER_IP);

    struct header req = {
        .type = htons(BINDING_REQUEST_TYPE),
        .len = 0,
        .cookie = htonl(MAGIC_COOKIE),
    };
    ssize_t rs = getrandom(&req.tx_id, sizeof req.tx_id, 0);
    if (rs == -1) err(1, "getrandom()");
    else if (rs != sizeof req.tx_id)
        handle_error("not enough random bytes were generated");

    rs = sendto(fd, &req, sizeof(struct header), 0,
        (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    if (rs == -1) err(1, "sendto()");
    else if (rs != sizeof(struct header))
        handle_error("not all the bytes were sent (%u out of %u)", rs, sizeof(struct header));

    uint8_t resp[1500];
    ssize_t n_resp = recvfrom(fd, &resp, sizeof resp, 0, NULL, NULL);
    if (n_resp == -1) err(1, "recvfrom()");
    printf("n_resp: %lu\n", n_resp);
    process_response(&req, resp, (size_t)n_resp);

    return EXIT_SUCCESS;
}

crc.h:

#include <stdint.h>
#include <stddef.h>

extern uint32_t crc(const uint8_t *, size_t);

crc.c:

#include "crc.h"

/*
 * https://datatracker.ietf.org/doc/html/rfc8489#section-14.7
 * https://datatracker.ietf.org/doc/html/rfc1952#section-8
 */

unsigned long crc_table[256];
int crc_table_computed = 0;

static void make_crc_table(void)
{
  uint32_t c;
  int n, k;
  for (n = 0; n < 256; n++) {
    c = (uint32_t) n;
    for (k = 0; k < 8; k++) {
      if (c & 1) {
        c = 0xedb88320L ^ (c >> 1);
      } else {
        c = c >> 1;
      }
    }
    crc_table[n] = c;
  }
  crc_table_computed = 1;
}

static uint32_t update_crc(uint32_t crc, const uint8_t *buf, size_t len)
{
  uint32_t c = crc ^ 0xffffffff;
  size_t n;

  if (!crc_table_computed)
    make_crc_table();
  for (n = 0; n < len; n++) {
    c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
  }
  return c ^ 0xffffffff;
}

uint32_t crc(const uint8_t *buf, size_t len)
{
  return update_crc(0, buf, len);
}
$ gcc -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -std=c99 -O3 \
    a.c crc.c
$ ./a.out
n_resp: 88

type: 0x0101
len: 68
cookie: 0x2112a442
tx id: 0x8aa14599e313cfbf341717f5

type: 0x0020 (XOR-MAPPED-ADDRESS)
len: 8
protocol family: 0x01
port: 54056
ip: aa.bbb.aa.bbb

type: 0x0001 (MAPPED-ADDRESS)
len: 8
protocol family: 0x01
port: 54056
ip: aa.bbb.aa.bbb

type: 0x802b (RESPONSE-ORIGIN)
len: 8
protocol family: 0x01
port: 3478
ip: xx.yyy.xx.yyy

type: 0x8022 (SOFTWARE)
len: 20
software: Coturn-4.6.2 'Gorst'

type: 0x8028 (FINGERPRINT)
len: 4
CRC-32: 0xfa1bfddd

crc.c.diff:

--- crc.orig.c
+++ crc.c
@@ -1,12 +1,19 @@
+#include "crc.h"
+
+/*
+ * https://datatracker.ietf.org/doc/html/rfc8489#section-14.7
+ * https://datatracker.ietf.org/doc/html/rfc1952#section-8
+ */
+
 unsigned long crc_table[256];
 int crc_table_computed = 0;
 
-void make_crc_table(void)
+static void make_crc_table(void)
 {
-  unsigned long c;
+  uint32_t c;
   int n, k;
   for (n = 0; n < 256; n++) {
-    c = (unsigned long) n;
+    c = (uint32_t) n;
     for (k = 0; k < 8; k++) {
       if (c & 1) {
         c = 0xedb88320L ^ (c >> 1);
@@ -19,21 +26,20 @@
   crc_table_computed = 1;
 }
 
-unsigned long update_crc(unsigned long crc,
-                unsigned char *buf, int len)
+static uint32_t update_crc(uint32_t crc, const uint8_t *buf, size_t len)
 {
-  unsigned long c = crc ^ 0xffffffffL;
-  int n;
+  uint32_t c = crc ^ 0xffffffff;
+  size_t n;
 
   if (!crc_table_computed)
     make_crc_table();
   for (n = 0; n < len; n++) {
     c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
   }
-  return c ^ 0xffffffffL;
+  return c ^ 0xffffffff;
 }
 
-unsigned long crc(unsigned char *buf, int len)
+uint32_t crc(const uint8_t *buf, size_t len)
 {
-  return update_crc(0L, buf, len);
+  return update_crc(0, buf, len);
 }

https://datatracker.ietf.org/doc/html/rfc8489

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