mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00
272 lines
8.7 KiB
Text
272 lines
8.7 KiB
Text
##! Analyze DHCP traffic and provide a log that is organized around
|
|
##! the idea of a DHCP "conversation" defined by messages exchanged within
|
|
##! a relatively short period of time using the same transaction ID.
|
|
##! The log will have information from clients and servers to give a more
|
|
##! complete picture of what happened.
|
|
|
|
@load base/frameworks/cluster
|
|
@load ./consts
|
|
|
|
module DHCP;
|
|
|
|
export {
|
|
redef enum Log::ID += { LOG };
|
|
|
|
## The record type which contains the column fields of the DHCP log.
|
|
type Info: record {
|
|
## The earliest time at which a DHCP message over the
|
|
## associated connection is observed.
|
|
ts: time &log;
|
|
|
|
## A series of unique identifiers of the connections over which
|
|
## DHCP is occurring. This behavior with multiple connections is
|
|
## unique to DHCP because of the way it uses broadcast packets
|
|
## on local networks.
|
|
uids: set[string] &log;
|
|
|
|
## IP address of the client. If a transaction
|
|
## is only a client sending INFORM messages then
|
|
## there is no lease information exchanged so this
|
|
## is helpful to know who sent the messages.
|
|
## Getting an address in this field does require
|
|
## that the client sources at least one DHCP message
|
|
## using a non-broadcast address.
|
|
client_addr: addr &log &optional;
|
|
## IP address of the server involved in actually
|
|
## handing out the lease. There could be other
|
|
## servers replying with OFFER messages which won't
|
|
## be represented here. Getting an address in this
|
|
## field also requires that the server handing out
|
|
## the lease also sources packets from a non-broadcast
|
|
## IP address.
|
|
server_addr: addr &log &optional;
|
|
|
|
## Client port number seen at time of server handing out IP (expected
|
|
## as 68/udp).
|
|
client_port: port &optional;
|
|
## Server port number seen at time of server handing out IP (expected
|
|
## as 67/udp).
|
|
server_port: port &optional;
|
|
|
|
## Client's hardware address.
|
|
mac: string &log &optional;
|
|
|
|
## Name given by client in Hostname option 12.
|
|
host_name: string &log &optional;
|
|
## FQDN given by client in Client FQDN option 81.
|
|
client_fqdn: string &log &optional;
|
|
## Domain given by the server in option 15.
|
|
domain: string &log &optional;
|
|
|
|
## IP address requested by the client.
|
|
requested_addr: addr &log &optional;
|
|
## IP address assigned by the server.
|
|
assigned_addr: addr &log &optional;
|
|
## IP address lease interval.
|
|
lease_time: interval &log &optional;
|
|
|
|
## Message typically accompanied with a DHCP_DECLINE
|
|
## so the client can tell the server why it rejected
|
|
## an address.
|
|
client_message: string &log &optional;
|
|
## Message typically accompanied with a DHCP_NAK to let
|
|
## the client know why it rejected the request.
|
|
server_message: string &log &optional;
|
|
|
|
## The DHCP message types seen by this DHCP transaction
|
|
msg_types: vector of string &log &default=string_vec();
|
|
|
|
## Duration of the DHCP "session" representing the
|
|
## time from the first message to the last.
|
|
duration: interval &log &default=0secs;
|
|
};
|
|
|
|
## The maximum amount of time that a transation ID will be watched
|
|
## for to try and tie messages together into a single DHCP
|
|
## transaction narrative.
|
|
option DHCP::max_txid_watch_time = 30secs;
|
|
|
|
## This event is used internally to distribute data around clusters
|
|
## since DHCP doesn't follow the normal "connection" model used by
|
|
## most protocols. It can also be handled to extend the DHCP log.
|
|
## bro:see::`DHCP::log_info`.
|
|
global DHCP::aggregate_msgs: event(ts: time, id: conn_id, uid: string, is_orig: bool, msg: DHCP::Msg, options: DHCP::Options);
|
|
|
|
## This is a global variable that is only to be used in the
|
|
## :bro::see::`DHCP::aggregate_msgs` event. It can be used to avoid
|
|
## looking up the info record for a transaction ID in every event handler
|
|
## for :bro:see::`DHCP::aggregate_msgs`.
|
|
global DHCP::log_info: Info;
|
|
|
|
## Event that can be handled to access the DHCP
|
|
## record as it is sent on to the logging framework.
|
|
global log_dhcp: event(rec: Info);
|
|
}
|
|
|
|
# Add the dhcp info to the connection record.
|
|
redef record connection += {
|
|
dhcp: Info &optional;
|
|
};
|
|
|
|
redef record Info += {
|
|
last_message_ts: time &optional;
|
|
};
|
|
|
|
# 67/udp is the server's port, 68/udp the client.
|
|
# 4011/udp seems to be some proxyDHCP thing.
|
|
const ports = { 67/udp, 68/udp, 4011/udp };
|
|
redef likely_server_ports += { 67/udp };
|
|
|
|
event bro_init() &priority=5
|
|
{
|
|
Log::create_stream(DHCP::LOG, [$columns=Info, $ev=log_dhcp, $path="dhcp"]);
|
|
Analyzer::register_for_ports(Analyzer::ANALYZER_DHCP, ports);
|
|
}
|
|
|
|
@if ( Cluster::is_enabled() )
|
|
event bro_init()
|
|
{
|
|
Broker::auto_publish(Cluster::manager_topic, DHCP::aggregate_msgs);
|
|
}
|
|
@endif
|
|
|
|
function join_data_expiration(t: table[count] of Info, idx: count): interval
|
|
{
|
|
local info = t[idx];
|
|
|
|
local now = network_time();
|
|
# If a message hasn't been seen in the past 5 seconds or the
|
|
# total time watching has been more than the maximum time
|
|
# allowed by the configuration then log this data and expire it.
|
|
# Also, if Bro is shutting down.
|
|
if ( (now - info$last_message_ts) > 5sec ||
|
|
(now - info$ts) > max_txid_watch_time ||
|
|
bro_is_terminating() )
|
|
{
|
|
Log::write(LOG, info);
|
|
|
|
# Go ahead and expire the data now that the log
|
|
# entry has been written.
|
|
return 0secs;
|
|
}
|
|
else
|
|
{
|
|
return 5secs;
|
|
}
|
|
}
|
|
|
|
# This is where the data is stored as it's centralized. All data for a log must
|
|
# arrive within the expiration interval if it's to be logged fully. On a cluster,
|
|
# this data is only maintained on the manager.
|
|
global join_data: table[count] of Info = table()
|
|
&create_expire=10secs &expire_func=join_data_expiration;
|
|
|
|
|
|
|
|
@if ( ! Cluster::is_enabled() || Cluster::local_node_type() == Cluster::MANAGER )
|
|
# We are handling this event at priority 1000 because we really want
|
|
# the DHCP::log_info global to be set correctly before a user might try
|
|
# to access it.
|
|
event DHCP::aggregate_msgs(ts: time, id: conn_id, uid: string, is_orig: bool, msg: DHCP::Msg, options: DHCP::Options) &priority=1000
|
|
{
|
|
if ( msg$xid !in join_data )
|
|
{
|
|
join_data[msg$xid] = Info($ts=ts,
|
|
$uids=set(uid));
|
|
}
|
|
|
|
log_info = join_data[msg$xid];
|
|
}
|
|
|
|
event DHCP::aggregate_msgs(ts: time, id: conn_id, uid: string, is_orig: bool, msg: DHCP::Msg, options: DHCP::Options) &priority=5
|
|
{
|
|
log_info$duration = ts - log_info$ts;
|
|
|
|
if ( uid !in log_info$uids )
|
|
add log_info$uids[uid];
|
|
|
|
log_info$msg_types += DHCP::message_types[msg$m_type];
|
|
|
|
# Let's watch for messages in any DHCP message type
|
|
# and split them out based on client and server.
|
|
if ( options?$message )
|
|
{
|
|
if ( is_orig )
|
|
log_info$client_message = options$message;
|
|
else
|
|
log_info$server_message = options$message;
|
|
}
|
|
|
|
# Update the last message time so that we can do some data
|
|
# expiration handling.
|
|
log_info$last_message_ts = ts;
|
|
|
|
if ( is_orig ) # client requests
|
|
{
|
|
# Assign the client addr in case this is a session
|
|
# of only INFORM messages (no lease handed out).
|
|
# This also works if a normal lease handout uses
|
|
# unicast.
|
|
if ( id$orig_h != 0.0.0.0 && id$orig_h != 255.255.255.255 )
|
|
log_info$client_addr = id$orig_h;
|
|
|
|
if ( options?$host_name )
|
|
log_info$host_name = options$host_name;
|
|
|
|
if ( options?$client_fqdn )
|
|
log_info$client_fqdn = options$client_fqdn$domain_name;
|
|
|
|
if ( options?$client_id &&
|
|
options$client_id$hwtype == 1 ) # ETHERNET
|
|
log_info$mac = options$client_id$hwaddr;
|
|
|
|
if ( options?$addr_request )
|
|
log_info$requested_addr = options$addr_request;
|
|
}
|
|
else # server reply messages
|
|
{
|
|
# Only log the address of the server if it handed out
|
|
# an IP address.
|
|
if ( msg$yiaddr != 0.0.0.0 &&
|
|
id$resp_h != 255.255.255.255 )
|
|
{
|
|
log_info$server_addr = id$resp_h;
|
|
log_info$server_port = id$resp_p;
|
|
log_info$client_port = id$orig_p;
|
|
}
|
|
|
|
# Only use the client hardware address from the server
|
|
# if we didn't already pick one up from the client.
|
|
if ( msg$chaddr != "" && !log_info?$mac )
|
|
log_info$mac = msg$chaddr;
|
|
|
|
if ( msg$yiaddr != 0.0.0.0 )
|
|
log_info$assigned_addr = msg$yiaddr;
|
|
|
|
# If no client address has been seen yet, let's use the assigned addr.
|
|
if ( ! log_info?$client_addr && log_info?$assigned_addr )
|
|
log_info$client_addr = log_info$assigned_addr;
|
|
|
|
if ( options?$domain_name )
|
|
log_info$domain = options$domain_name;
|
|
|
|
if ( options?$lease )
|
|
log_info$lease_time = options$lease;
|
|
}
|
|
}
|
|
@endif
|
|
|
|
|
|
|
|
# Aggregate DHCP messages to the manager.
|
|
event dhcp_message(c: connection, is_orig: bool, msg: DHCP::Msg, options: DHCP::Options) &priority=-5
|
|
{
|
|
event DHCP::aggregate_msgs(network_time(), c$id, c$uid, is_orig, msg, options);
|
|
}
|
|
|
|
event bro_done() &priority=-5
|
|
{
|
|
# Log any remaining data that hasn't already been logged!
|
|
for ( i in DHCP::join_data )
|
|
join_data_expiration(DHCP::join_data, i);
|
|
}
|