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);
}