tcpdump/print-quic.c
2021-12-23 11:11:54 +00:00

292 lines
7.0 KiB
C

/*
* Copyright (c) 2021 Apple, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. The names of the authors may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
/* \summary: QUIC Protocol printer */
/* specification: https://www.rfc-editor.org/rfc/rfc9000.txt */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "netdissect-stdinc.h"
#include "netdissect-alloc.h"
#include "netdissect.h"
#include "extract.h"
#define QUIC_MAX_CID_LENGTH 20
typedef uint8_t quic_cid[QUIC_MAX_CID_LENGTH];
struct quic_cid_array {
uint8_t cid[QUIC_MAX_CID_LENGTH];
uint8_t length;
};
enum quic_lh_packet_type {
QUIC_LH_TYPE_INITIAL = 0,
QUIC_LH_TYPE_0RTT = 1,
QUIC_LH_TYPE_HANDSHAKE = 2,
QUIC_LH_TYPE_RETRY = 3
};
static void
hexprint(netdissect_options *ndo, const uint8_t *cp, size_t len)
{
size_t i;
for (i = 0; i < len; i++)
ND_PRINT("%02x", cp[i]);
}
#define QUIC_CID_LIST_MAX 512
static struct quic_cid_array quic_cid_array[QUIC_CID_LIST_MAX];
static struct quic_cid_array *
lookup_quic_cid(const u_char *cid, size_t length)
{
for (unsigned int i = 0; i < QUIC_CID_LIST_MAX; i++) {
if (quic_cid_array[i].length > length) {
continue;
}
if (quic_cid_array[i].length == 0) {
break;
}
if (memcmp(quic_cid_array[i].cid, cid,
quic_cid_array[i].length) == 0) {
/*
* Swap the entries so that it behaves like an
* LRU cache.
*/
if (i != 0) {
struct quic_cid_array tmp = quic_cid_array[i];
quic_cid_array[i] = quic_cid_array[0];
quic_cid_array[0] = tmp;
}
return &quic_cid_array[0];
}
}
return NULL;
}
static void
register_quic_cid(const quic_cid cid, uint8_t length)
{
static uint16_t next_cid = 0;
if (length == 0 ||
lookup_quic_cid(cid, length) != NULL) {
return;
}
memcpy(&quic_cid_array[next_cid].cid, cid, QUIC_MAX_CID_LENGTH);
quic_cid_array[next_cid].length = length;
next_cid = (next_cid + 1) % QUIC_CID_LIST_MAX;
}
/* Returns 1 if the first octet looks like a QUIC packet. */
int
quic_detect(netdissect_options *ndo, const u_char *p, const u_int len)
{
uint8_t first_octet;
if (len < 1)
return 0;
first_octet = GET_U_1(p);
/* All QUIC packets must have the Fixed Bit set to 1. */
if ((first_octet & 0x40) == 0x40)
return 1;
else
return 0;
}
/* Extracts the variable length integer (see RFC 9000 section 16). */
static inline uint64_t
get_be_vli(netdissect_options *ndo, const u_char *p, uint8_t *out_length)
{
uint64_t v;
uint8_t prefix;
uint8_t length;
v = GET_U_1(p);
p++;
prefix = (uint8_t)v >> 6;
length = 1 << prefix;
if (out_length != NULL)
*out_length = length;
v = v & 0x3f;
while (length > 1) {
v = (v << 8) + GET_U_1(p);
p++;
length--;
}
return v;
}
#define GET_BE_VLI(p, l) get_be_vli(ndo, (const u_char *)(p), l)
static const u_char *
quic_print_packet(netdissect_options *ndo, const u_char *bp, const u_char *end)
{
uint8_t first_octet = 0;
uint8_t packet_type = 0;
uint32_t version = 0;
quic_cid dcid = {0};
quic_cid scid = {0};
uint8_t dcil = 0; /* DCID length */
uint8_t scil = 0; /* SCID length */
uint8_t vli_length = 0;
uint8_t *token = NULL;
uint64_t token_length = 0;
first_octet = GET_U_1(bp);
bp += 1;
if (first_octet & 0x80) {
/* Long Header */
packet_type = (first_octet >> 4) & 0x03;
version = GET_BE_U_4(bp);
bp += 4;
if (version == 0)
ND_PRINT(", version negotiation");
else if (packet_type == QUIC_LH_TYPE_INITIAL)
ND_PRINT(", initial");
else if (packet_type == QUIC_LH_TYPE_0RTT)
ND_PRINT(", 0-rtt");
else if (packet_type == QUIC_LH_TYPE_HANDSHAKE)
ND_PRINT(", handshake");
else if (packet_type == QUIC_LH_TYPE_RETRY)
ND_PRINT(", retry");
if (version != 0 && version != 1)
ND_PRINT(", v%x", version);
dcil = GET_U_1(bp);
bp += 1;
if (dcil > 0 && dcil <= QUIC_MAX_CID_LENGTH) {
memset(dcid, 0, sizeof(dcid));
GET_CPY_BYTES(&dcid, bp, dcil);
bp += dcil;
ND_PRINT(", dcid ");
hexprint(ndo, dcid, dcil);
register_quic_cid(dcid, dcil);
}
scil = GET_U_1(bp);
bp += 1;
if (scil > 0 && scil <= QUIC_MAX_CID_LENGTH) {
memset(scid, 0, sizeof(dcid));
GET_CPY_BYTES(&scid, bp, scil);
bp += scil;
ND_PRINT(", scid ");
hexprint(ndo, scid, scil);
register_quic_cid(scid, scil);
}
if (version == 0) {
/* Version Negotiation packet */
while (bp < end) {
if (!ND_TTEST_4(bp)) {
nd_print_trunc(ndo);
bp = end;
} else {
uint32_t vn_version = GET_BE_U_4(bp);
bp += 4;
ND_PRINT(", version 0x%x", vn_version);
}
}
} else {
if (packet_type == QUIC_LH_TYPE_INITIAL) {
token_length = GET_BE_VLI(bp, &vli_length);
bp += vli_length;
if (token_length > 0 && token_length < 1000) {
token = nd_malloc(ndo, (size_t)token_length);
GET_CPY_BYTES(token, bp, (size_t)token_length);
bp += token_length;
ND_PRINT(", token ");
hexprint(ndo, token, (size_t)token_length);
}
}
if (packet_type == QUIC_LH_TYPE_RETRY) {
ND_PRINT(", token ");
if (end > bp && end - bp > 16 &&
ND_TTEST_LEN(bp, end - bp - 16)) {
token_length = end - bp - 16;
token = nd_malloc(ndo, (size_t)token_length);
GET_CPY_BYTES(token, bp, (size_t)token_length);
bp += token_length;
hexprint(ndo, token, (size_t)token_length);
} else {
nd_print_trunc(ndo);
}
bp = end;
} else {
/* Initial/Handshake/0-RTT */
uint64_t payload_length =
GET_BE_VLI(bp, &vli_length);
bp += vli_length;
ND_PRINT(", length %" PRIu64, payload_length);
if (!ND_TTEST_LEN(bp, payload_length)) {
nd_print_trunc(ndo);
bp = end;
} else
bp += payload_length;
}
}
} else {
/* Short Header */
ND_PRINT(", protected");
if (end > bp && end - bp > 16 &&
ND_TTEST_LEN(bp, end - bp)) {
struct quic_cid_array *cid_array =
lookup_quic_cid(bp, end - bp);
if (cid_array != NULL) {
ND_PRINT(", dcid ");
hexprint(ndo, cid_array->cid,
cid_array->length);
}
} else {
nd_print_trunc(ndo);
}
bp = end;
}
return bp;
}
void
quic_print(netdissect_options *ndo, const u_char *bp, const u_int len)
{
const uint8_t *end = bp + len;
ndo->ndo_protocol = "quic";
nd_print_protocol(ndo);
while (bp < end) {
bp = quic_print_packet(ndo, bp, end);
/*
* Skip all zero bytes which are
* considered padding.
*/
while (ND_TTEST_1(bp) && GET_U_1(bp) == 0)
bp++;
}
}