zeek/scripts/base/protocols/quic/main.zeek
Christian Kreibich 2199cb1ddd Remove "experimental" from the QUIC history field's comment string [skip ci]
We're unlikely to fundamentally change (or remove) this field at this point, and
some users wondered whether we might do so, given the labeling.
2025-03-26 14:03:52 -07:00

239 lines
6.5 KiB
Text

##! Initial idea for a quic.log.
@load base/frameworks/notice/weird
@load base/protocols/conn/removal-hooks
@load ./consts
module QUIC;
export {
redef enum Log::ID += { LOG };
type Info: record {
## Timestamp of first QUIC packet for this entry.
ts: time &log;
## Unique ID for the connection.
uid: string &log;
## The connection's 4-tuple of endpoint addresses/ports.
id: conn_id &log;
## QUIC version as found in the first INITIAL packet from
## the client.
version: string &log;
## First Destination Connection ID used by client. This is
## random and unpredictable, but used for packet protection
## by client and server.
client_initial_dcid: string &log &optional;
## Client's Source Connection ID from the first INITIAL packet.
client_scid: string &log &optional;
## Server chosen Connection ID usually from server's first
## INITIAL packet. This is to be used by the client in
## subsequent packets.
server_scid: string &log &optional;
## Server name extracted from SNI extension in ClientHello
## packet if available.
server_name: string &log &optional;
## First protocol extracted from ALPN extension in ClientHello
## packet if available.
client_protocol: string &log &optional;
## QUIC history.
##
## Letters have the following meaning with client-sent
## letters being capitalized:
##
## ====== ====================================================
## Letter Meaning
## ====== ====================================================
## I INIT packet
## H HANDSHAKE packet
## Z 0RTT packet
## R RETRY packet
## C CONNECTION_CLOSE packet
## S SSL Client/Server Hello
## ====== ====================================================
history: string &log &default="";
# Internal state for the history field.
history_state: vector of string;
# Internal state if this record has already been logged.
logged: bool &default=F;
};
global log_quic: event(rec: Info);
global log_policy: Log::PolicyHook;
global finalize_quic: Conn::RemovalHook;
## The maximum length of the history field.
option max_history_length = 100;
}
redef record connection += {
# XXX: We may have multiple QUIC connections with different
# Connection ID over the same UDP connection.
quic: Info &optional;
};
# Faster to modify here than re-compiling .evt files.
const quic_ports = {
443/udp, # HTTP3-over-QUIC
853/udp, # DNS-over-QUIC
784/udp, # DNS-over-QUIC early
};
function add_to_history(c: connection, is_orig: bool, what: string)
{
if ( |c$quic$history_state| == max_history_length )
return;
c$quic$history_state += is_orig ? to_upper(what[0]) : to_lower(what[0]);
if ( |c$quic$history_state| == max_history_length )
Reporter::conn_weird("QUIC_max_history_length_reached", c);
}
function log_record(quic: Info)
{
quic$history = join_string_vec(quic$history_state, "");
Log::write(LOG, quic);
quic$logged = T;
}
function set_session(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
if ( ! c?$quic )
{
c$quic = Info(
$ts=network_time(),
$uid=c$uid,
$id=c$id,
$version=version_strings[version],
);
Conn::register_removal_hook(c, finalize_quic);
}
if ( is_orig && |dcid| > 0 && ! c$quic?$client_initial_dcid )
c$quic$client_initial_dcid = bytestring_to_hexstr(dcid);
if ( is_orig )
c$quic$client_scid = bytestring_to_hexstr(scid);
else
c$quic$server_scid = bytestring_to_hexstr(scid);
}
event QUIC::initial_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
set_session(c, is_orig, version, dcid, scid);
add_to_history(c, is_orig, "INIT");
}
event QUIC::handshake_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
set_session(c, is_orig, version, dcid, scid);
add_to_history(c, is_orig, "HANDSHAKE");
}
event QUIC::zero_rtt_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
set_session(c, is_orig, version, dcid, scid);
add_to_history(c, is_orig, "ZeroRTT");
}
# RETRY packets trigger a log entry and state reset.
event QUIC::retry_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string, retry_token: string, integrity_tag: string)
{
if ( ! c?$quic )
set_session(c, is_orig, version, dcid, scid);
add_to_history(c, is_orig, "RETRY");
log_record(c$quic);
delete c$quic;
}
# If we couldn't handle a version, log it as a single record.
event QUIC::unhandled_version(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
if ( ! c?$quic )
set_session(c, is_orig, version, dcid, scid);
add_to_history(c, is_orig, "UNHANDLED_VERSION");
log_record(c$quic);
delete c$quic;
}
# Upon a connection_close_frame(), if any c$quic state is pending to be logged, do so
# now and prepare for a new entry.
event QUIC::connection_close_frame(c: connection, is_orig: bool, version: count, dcid: string, scid: string, error_code: count, reason_phrase: string)
{
if ( ! c?$quic )
return;
add_to_history(c, is_orig, "CONNECTION_CLOSE");
log_record(c$quic);
delete c$quic;
}
event ssl_extension_server_name(c: connection, is_client: bool, names: string_vec) &priority=5
{
if ( is_client && c?$quic && |names| > 0 )
c$quic$server_name = names[0];
}
event ssl_extension_application_layer_protocol_negotiation(c: connection, is_client: bool, protocols: string_vec)
{
if ( c?$quic && is_client )
{
c$quic$client_protocol = protocols[0];
if ( |protocols| > 1 )
# Probably not overly weird, but the quic.log only
# works with the first one in the hope to avoid
# vector or concatenation.
Reporter::conn_weird("QUIC_many_protocols", c, cat(protocols));
}
}
event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec)
{
if ( ! c?$quic )
return;
add_to_history(c, T, "SSL");
}
event ssl_server_hello(c: connection, version: count, record_version: count, possible_ts: time, server_random: string, session_id: string, cipher: count, comp_method: count) &priority=-5
{
if ( ! c?$quic )
return;
add_to_history(c, F, "SSL");
}
hook finalize_quic(c: connection)
{
if ( ! c?$quic || c$quic$logged )
return;
log_record(c$quic);
}
event zeek_init()
{
Log::create_stream(LOG, [$columns=Info, $ev=log_quic, $path="quic", $policy=log_policy]);
Analyzer::register_for_ports(Analyzer::ANALYZER_QUIC, quic_ports);
}