"u64" :"uint64_t",
"libc::c_void": "void",
+ "c_void": "void",
"libc::c_char": "char",
"libc::c_int": "int",
--- /dev/null
+These test pcap files are individual packets broken out of a pcap
+containing 4 DHCP messages. The original source of the PCAP file is
+
+https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=dhcp.pcap
--- /dev/null
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use applayer;
+use core;
+use core::{ALPROTO_UNKNOWN, AppProto, Flow};
+use dhcp::parser::*;
+use libc;
+use log::*;
+use nom;
+use parser::*;
+use std;
+use std::ffi::{CStr,CString};
+use std::mem::transmute;
+
+static mut ALPROTO_DHCP: AppProto = ALPROTO_UNKNOWN;
+
+static DHCP_MIN_FRAME_LEN: u32 = 232;
+
+pub const BOOTP_REQUEST: u8 = 1;
+pub const BOOTP_REPLY: u8 = 2;
+
+// DHCP option types. Names based on IANA naming:
+// https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml
+pub const DHCP_OPT_SUBNET_MASK: u8 = 1;
+pub const DHCP_OPT_ROUTERS: u8 = 3;
+pub const DHCP_OPT_DNS_SERVER: u8 = 6;
+pub const DHCP_OPT_HOSTNAME: u8 = 12;
+pub const DHCP_OPT_REQUESTED_IP: u8 = 50;
+pub const DHCP_OPT_ADDRESS_TIME: u8 = 51;
+pub const DHCP_OPT_TYPE: u8 = 53;
+pub const DHCP_OPT_SERVER_ID: u8 = 54;
+pub const DHCP_OPT_PARAMETER_LIST: u8 = 55;
+pub const DHCP_OPT_RENEWAL_TIME: u8 = 58;
+pub const DHCP_OPT_REBINDING_TIME: u8 = 59;
+pub const DHCP_OPT_CLIENT_ID: u8 = 61;
+pub const DHCP_OPT_END: u8 = 255;
+
+/// DHCP message types.
+pub const DHCP_TYPE_DISCOVER: u8 = 1;
+pub const DHCP_TYPE_OFFER: u8 = 2;
+pub const DHCP_TYPE_REQUEST: u8 = 3;
+pub const DHCP_TYPE_DECLINE: u8 = 4;
+pub const DHCP_TYPE_ACK: u8 = 5;
+pub const DHCP_TYPE_NAK: u8 = 6;
+pub const DHCP_TYPE_RELEASE: u8 = 7;
+pub const DHCP_TYPE_INFORM: u8 = 8;
+
+/// DHCP parameter types.
+/// https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.txt
+pub const DHCP_PARAM_SUBNET_MASK: u8 = 1;
+pub const DHCP_PARAM_ROUTER: u8 = 3;
+pub const DHCP_PARAM_DNS_SERVER: u8 = 6;
+pub const DHCP_PARAM_DOMAIN: u8 = 15;
+pub const DHCP_PARAM_ARP_TIMEOUT: u8 = 35;
+pub const DHCP_PARAM_NTP_SERVER: u8 = 42;
+pub const DHCP_PARAM_TFTP_SERVER_NAME: u8 = 66;
+pub const DHCP_PARAM_TFTP_SERVER_IP: u8 = 150;
+
+#[repr(u32)]
+pub enum DHCPEvent {
+ TruncatedOptions = 0,
+ MalformedOptions,
+}
+
+/// The concept of a transaction is more to satisfy the Suricata
+/// app-layer. This DHCP parser is actually stateless where each
+/// message is its own transaction.
+pub struct DHCPTransaction {
+ tx_id: u64,
+ pub message: DHCPMessage,
+ logged: applayer::LoggerFlags,
+ de_state: Option<*mut core::DetectEngineState>,
+ events: *mut core::AppLayerDecoderEvents,
+}
+
+impl DHCPTransaction {
+ pub fn new(id: u64, message: DHCPMessage) -> DHCPTransaction {
+ DHCPTransaction {
+ tx_id: id,
+ message: message,
+ logged: applayer::LoggerFlags::new(),
+ de_state: None,
+ events: std::ptr::null_mut(),
+ }
+ }
+}
+
+export_tx_get_detect_state!(rs_dhcp_tx_get_detect_state, DHCPTransaction);
+export_tx_set_detect_state!(rs_dhcp_tx_set_detect_state, DHCPTransaction);
+
+pub struct DHCPState {
+ // Internal transaction ID.
+ tx_id: u64,
+
+ // List of transactions.
+ transactions: Vec<DHCPTransaction>,
+
+ events: u16,
+}
+
+impl DHCPState {
+ pub fn new() -> DHCPState {
+ return DHCPState {
+ tx_id: 0,
+ transactions: Vec::new(),
+ events: 0,
+ };
+ }
+
+ pub fn parse(&mut self, input: &[u8]) -> bool {
+ match dhcp_parse(input) {
+ nom::IResult::Done(_, message) => {
+ let malformed_options = message.malformed_options;
+ let truncated_options = message.truncated_options;
+ self.tx_id += 1;
+ let transaction = DHCPTransaction::new(self.tx_id, message);
+ self.transactions.push(transaction);
+ if malformed_options {
+ self.set_event(DHCPEvent::MalformedOptions);
+ }
+ if truncated_options {
+ self.set_event(DHCPEvent::TruncatedOptions);
+ }
+ return true;
+ }
+ _ => {
+ return false;
+ }
+ }
+ }
+
+ pub fn get_tx(&mut self, tx_id: u64) -> Option<&DHCPTransaction> {
+ for tx in &mut self.transactions {
+ if tx.tx_id == tx_id + 1 {
+ return Some(tx);
+ }
+ }
+ return None;
+ }
+
+ fn free_tx(&mut self, tx_id: u64) {
+ let len = self.transactions.len();
+ let mut found = false;
+ let mut index = 0;
+ for i in 0..len {
+ let tx = &self.transactions[i];
+ if tx.tx_id == tx_id + 1 {
+ found = true;
+ index = i;
+ break;
+ }
+ }
+ if found {
+ self.transactions.remove(index);
+ }
+ }
+
+ fn set_event(&mut self, event: DHCPEvent) {
+ if let Some(tx) = self.transactions.last_mut() {
+ core::sc_app_layer_decoder_events_set_event_raw(
+ &mut tx.events, event as u8);
+ self.events += 1;
+ }
+ }
+
+ fn get_tx_iterator(&mut self, min_tx_id: u64, state: &mut u64) ->
+ Option<(&DHCPTransaction, u64, bool)>
+ {
+ let mut index = *state as usize;
+ let len = self.transactions.len();
+
+ while index < len {
+ let tx = &self.transactions[index];
+ if tx.tx_id < min_tx_id + 1 {
+ index += 1;
+ continue;
+ }
+ *state = index as u64 + 1;
+ return Some((tx, tx.tx_id - 1, (len - index) > 1));
+ }
+
+ return None;
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_probing_parser(_flow: *const Flow,
+ input: *const libc::uint8_t,
+ input_len: u32,
+ _offset: *const u32) -> AppProto {
+ if input_len < DHCP_MIN_FRAME_LEN {
+ return ALPROTO_UNKNOWN;
+ }
+
+ let slice = build_slice!(input, input_len as usize);
+ match parse_header(slice) {
+ nom::IResult::Done(_, _) => {
+ return unsafe { ALPROTO_DHCP };
+ }
+ _ => {
+ return ALPROTO_UNKNOWN;
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_tx_get_alstate_progress(_tx: *mut libc::c_void,
+ _direction: libc::uint8_t) -> libc::c_int {
+ // As this is a stateless parser, simply use 1.
+ return 1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_progress_completion_status(
+ _direction: libc::uint8_t) -> libc::c_int {
+ // The presence of a transaction means we are complete.
+ return 1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_get_tx(state: *mut libc::c_void,
+ tx_id: libc::uint64_t) -> *mut libc::c_void {
+ let state = cast_pointer!(state, DHCPState);
+ match state.get_tx(tx_id) {
+ Some(tx) => {
+ return unsafe { transmute(tx) };
+ }
+ None => {
+ return std::ptr::null_mut();
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_get_tx_count(state: *mut libc::c_void) -> libc::uint64_t {
+ let state = cast_pointer!(state, DHCPState);
+ return state.tx_id;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_parse(_flow: *const core::Flow,
+ state: *mut libc::c_void,
+ _pstate: *mut libc::c_void,
+ input: *const libc::uint8_t,
+ input_len: u32,
+ _data: *const libc::c_void) -> i8 {
+ let state = cast_pointer!(state, DHCPState);
+ let buf = build_slice!(input, input_len as usize);
+ if state.parse(buf) {
+ return 1;
+ }
+ return -1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_tx_free(
+ state: *mut libc::c_void,
+ tx_id: libc::uint64_t)
+{
+ let state = cast_pointer!(state, DHCPState);
+ state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_new() -> *mut libc::c_void {
+ let state = DHCPState::new();
+ let boxed = Box::new(state);
+ return unsafe {
+ transmute(boxed)
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_free(state: *mut libc::c_void) {
+ // Just unbox...
+ let _drop: Box<DHCPState> = unsafe { transmute(state) };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_tx_get_logged(_state: *mut libc::c_void, tx: *mut libc::c_void) -> u32 {
+ let tx = cast_pointer!(tx, DHCPTransaction);
+ return tx.logged.get();
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_tx_set_logged(_state: *mut libc::c_void,
+ tx: *mut libc::c_void,
+ logged: libc::uint32_t) {
+ let tx = cast_pointer!(tx, DHCPTransaction);
+ tx.logged.set(logged);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_get_events(state: *mut libc::c_void,
+ tx_id: libc::uint64_t)
+ -> *mut core::AppLayerDecoderEvents
+{
+ let state = cast_pointer!(state, DHCPState);
+ match state.get_tx(tx_id) {
+ Some(tx) => tx.events,
+ _ => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_get_event_info(
+ event_name: *const libc::c_char,
+ event_id: *mut libc::c_int,
+ event_type: *mut core::AppLayerEventType)
+ -> libc::c_int
+{
+ if event_name == std::ptr::null() {
+ return -1;
+ }
+ let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) };
+ let event = match c_event_name.to_str() {
+ Ok(s) => {
+ match s {
+ "malformed_options" => DHCPEvent::MalformedOptions as i32,
+ "truncated_options" => DHCPEvent::TruncatedOptions as i32,
+ _ => -1, // unknown event
+ }
+ },
+ Err(_) => -1, // UTF-8 conversion failed
+ };
+ unsafe{
+ *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+ *event_id = event as libc::c_int;
+ };
+ 0
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_state_get_tx_iterator(
+ _ipproto: libc::uint8_t,
+ _alproto: AppProto,
+ state: *mut libc::c_void,
+ min_tx_id: libc::uint64_t,
+ _max_tx_id: libc::uint64_t,
+ istate: &mut libc::uint64_t)
+ -> applayer::AppLayerGetTxIterTuple
+{
+ let state = cast_pointer!(state, DHCPState);
+ match state.get_tx_iterator(min_tx_id, istate) {
+ Some((tx, out_tx_id, has_next)) => {
+ let c_tx = unsafe { transmute(tx) };
+ let ires = applayer::AppLayerGetTxIterTuple::with_values(
+ c_tx, out_tx_id, has_next);
+ return ires;
+ }
+ None => {
+ return applayer::AppLayerGetTxIterTuple::not_found();
+ }
+ }
+}
+
+const PARSER_NAME: &'static [u8] = b"dhcp\0";
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_dhcp_register_parser() {
+ SCLogNotice!("Registering DHCP parser.");
+ let ports = CString::new("[67,68]").unwrap();
+ let parser = RustParser {
+ name: PARSER_NAME.as_ptr() as *const libc::c_char,
+ default_port: ports.as_ptr(),
+ ipproto: libc::IPPROTO_UDP,
+ probe_ts: rs_dhcp_probing_parser,
+ probe_tc: rs_dhcp_probing_parser,
+ min_depth: 0,
+ max_depth: 16,
+ state_new: rs_dhcp_state_new,
+ state_free: rs_dhcp_state_free,
+ tx_free: rs_dhcp_state_tx_free,
+ parse_ts: rs_dhcp_parse,
+ parse_tc: rs_dhcp_parse,
+ get_tx_count: rs_dhcp_state_get_tx_count,
+ get_tx: rs_dhcp_state_get_tx,
+ tx_get_comp_st: rs_dhcp_state_progress_completion_status,
+ tx_get_progress: rs_dhcp_tx_get_alstate_progress,
+ get_tx_logged: Some(rs_dhcp_tx_get_logged),
+ set_tx_logged: Some(rs_dhcp_tx_set_logged),
+ get_de_state: rs_dhcp_tx_get_detect_state,
+ set_de_state: rs_dhcp_tx_set_detect_state,
+ get_events: Some(rs_dhcp_state_get_events),
+ get_eventinfo: Some(rs_dhcp_state_get_event_info),
+ localstorage_new: None,
+ localstorage_free: None,
+ get_tx_mpm_id: None,
+ set_tx_mpm_id: None,
+ get_files: None,
+ get_tx_iterator: Some(rs_dhcp_state_get_tx_iterator),
+ };
+
+ let ip_proto_str = CString::new("udp").unwrap();
+
+ if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+ let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+ ALPROTO_DHCP = alproto;
+ if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+ let _ = AppLayerRegisterParser(&parser, alproto);
+ }
+ } else {
+ SCLogDebug!("Protocol detector and parser disabled for DHCP.");
+ }
+}
--- /dev/null
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+extern crate libc;
+
+use std;
+use std::os::raw::c_void;
+
+use dhcp::dhcp::*;
+use dhcp::parser::{DHCPOptionWrapper,DHCPOptGeneric};
+use dns::log::dns_print_addr;
+use json::*;
+use conf::ConfNode;
+
+pub struct DHCPLogger {
+ extended: bool,
+}
+
+impl DHCPLogger {
+
+ pub fn new(conf: ConfNode) -> DHCPLogger {
+ return DHCPLogger{
+ extended: conf.get_child_bool("extended"),
+ };
+ }
+
+ fn get_type(&self, tx: &DHCPTransaction) -> Option<u8> {
+ let options = &tx.message.options;
+ for option in options {
+ let code = option.code;
+ match &option.option {
+ &DHCPOptionWrapper::Generic(ref option) => {
+ match code {
+ DHCP_OPT_TYPE => {
+ if option.data.len() > 0 {
+ return Some(option.data[0]);
+ }
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ }
+ return None;
+ }
+
+ fn do_log(&self, tx: &DHCPTransaction) -> bool {
+ if !self.extended {
+ match self.get_type(tx) {
+ Some(t) => {
+ match t {
+ DHCP_TYPE_ACK => {
+ return true;
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ return false;
+ }
+ return true;
+ }
+
+ pub fn log(&self, tx: &DHCPTransaction) -> Option<Json> {
+ if !self.do_log(tx) {
+ return None;
+ }
+
+ let header = &tx.message.header;
+ let options = &tx.message.options;
+ let js = Json::object();
+
+ match header.opcode {
+ BOOTP_REQUEST => {
+ js.set_string("type", "request");
+ }
+ BOOTP_REPLY => {
+ js.set_string("type", "reply");
+ }
+ _ => {
+ js.set_string("type", "<unknown>");
+ }
+ }
+
+ js.set_integer("id", header.txid as u64);
+ js.set_string("client_mac",
+ &format_addr_hex(&header.clienthw.to_vec()));
+ js.set_string("assigned_ip", &dns_print_addr(&header.yourip));
+
+ if self.extended {
+ js.set_string("client_ip", &dns_print_addr(&header.clientip));
+ if header.opcode == BOOTP_REPLY {
+ js.set_string("relay_ip",
+ &dns_print_addr(&header.giaddr));
+ js.set_string("next_server_ip",
+ &dns_print_addr(&header.serverip));
+ }
+ }
+
+ for option in options {
+ let code = option.code;
+ match &option.option {
+ &DHCPOptionWrapper::ClientId(ref clientid) => {
+ js.set_string("client_id",
+ &format_addr_hex(&clientid.data));
+ }
+ &DHCPOptionWrapper::TimeValue(ref time_value) => {
+ match code {
+ DHCP_OPT_ADDRESS_TIME => {
+ if self.extended {
+ js.set_integer("lease_time",
+ time_value.seconds as u64);
+ }
+ }
+ DHCP_OPT_REBINDING_TIME => {
+ if self.extended {
+ js.set_integer("rebinding_time",
+ time_value.seconds as u64);
+ }
+ }
+ DHCP_OPT_RENEWAL_TIME => {
+ js.set_integer("renewal_time",
+ time_value.seconds as u64);
+ }
+ _ => {}
+ }
+ }
+ &DHCPOptionWrapper::Generic(ref option) => {
+ match code {
+ DHCP_OPT_SUBNET_MASK => {
+ if self.extended {
+ js.set_string("subnet_mask",
+ &dns_print_addr(&option.data));
+ }
+ }
+ DHCP_OPT_HOSTNAME => {
+ if option.data.len() > 0 {
+ js.set_string_from_bytes("hostname",
+ &option.data);
+ }
+ }
+ DHCP_OPT_TYPE => {
+ self.log_opt_type(&js, option);
+ }
+ DHCP_OPT_REQUESTED_IP => {
+ if self.extended {
+ js.set_string("requested_ip",
+ &dns_print_addr(&option.data));
+ }
+ }
+ DHCP_OPT_PARAMETER_LIST => {
+ if self.extended {
+ self.log_opt_parameters(&js, option);
+ }
+ }
+ DHCP_OPT_DNS_SERVER => {
+ if self.extended {
+ self.log_opt_dns_server(&js, option);
+ }
+ }
+ DHCP_OPT_ROUTERS => {
+ if self.extended {
+ self.log_opt_routers(&js, option);
+ }
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ }
+
+ return Some(js);
+ }
+
+ fn log_opt_type(&self, js: &Json, option: &DHCPOptGeneric) {
+ let dhcp_type = match option.data[0] {
+ DHCP_TYPE_DISCOVER => "discover",
+ DHCP_TYPE_OFFER => "offer",
+ DHCP_TYPE_REQUEST => "request",
+ DHCP_TYPE_DECLINE => "decline",
+ DHCP_TYPE_ACK => "ack",
+ DHCP_TYPE_NAK => "nak",
+ DHCP_TYPE_RELEASE => "release",
+ DHCP_TYPE_INFORM => "inform",
+ _ => "unknown"
+ };
+ js.set_string("dhcp_type", dhcp_type);
+ }
+
+ fn log_opt_parameters(&self, js: &Json, option: &DHCPOptGeneric) {
+ let params = Json::array();
+ for i in &option.data {
+ let param = match *i {
+ DHCP_PARAM_SUBNET_MASK => "subnet_mask",
+ DHCP_PARAM_ROUTER => "router",
+ DHCP_PARAM_DNS_SERVER => "dns_server",
+ DHCP_PARAM_DOMAIN => "domain",
+ DHCP_PARAM_ARP_TIMEOUT => "arp_timeout",
+ DHCP_PARAM_NTP_SERVER => "ntp_server",
+ DHCP_PARAM_TFTP_SERVER_NAME => "tftp_server_name",
+ DHCP_PARAM_TFTP_SERVER_IP => "tftp_server_ip",
+ _ => ""
+ };
+ if param.len() > 0 {
+ params.array_append_string(param);
+ }
+ }
+ js.set("params", params);
+ }
+
+ fn log_opt_dns_server(&self, js: &Json, option: &DHCPOptGeneric) {
+ let servers = Json::array();
+ for i in 0..(option.data.len() / 4) {
+ servers.array_append_string(&dns_print_addr(
+ &option.data[(i * 4)..(i * 4) + 4].to_vec()))
+ }
+ js.set("dns_servers", servers);
+ }
+
+ fn log_opt_routers(&self, js: &Json, option: &DHCPOptGeneric) {
+ let routers = Json::array();
+ for i in 0..(option.data.len() / 4) {
+ routers.array_append_string(&dns_print_addr(
+ &option.data[(i * 4)..(i * 4) + 4].to_vec()))
+ }
+ js.set("routers", routers);
+ }
+
+}
+
+fn format_addr_hex(input: &Vec<u8>) -> String {
+ let parts: Vec<String> = input.iter()
+ .map(|b| format!("{:02x}", b))
+ .collect();
+ return parts.join(":");
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_logger_new(conf: *const c_void) -> *mut libc::c_void {
+ let conf = ConfNode::wrap(conf);
+ let boxed = Box::new(DHCPLogger::new(conf));
+ return unsafe{std::mem::transmute(boxed)};
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_logger_free(logger: *mut libc::c_void) {
+ let _: Box<DHCPLogger> = unsafe{std::mem::transmute(logger)};
+}
+
+#[no_mangle]
+pub extern "C" fn rs_dhcp_logger_log(logger: *mut libc::c_void,
+ tx: *mut libc::c_void) -> *mut JsonT {
+ let logger = cast_pointer!(logger, DHCPLogger);
+ let tx = cast_pointer!(tx, DHCPTransaction);
+ match logger.log(tx) {
+ Some(js) => {
+ return js.unwrap();
+ }
+ _ => {
+ return std::ptr::null_mut();
+ }
+ }
+}
--- /dev/null
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+pub mod dhcp;
+pub mod parser;
+pub mod logger;
--- /dev/null
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use std::cmp::min;
+
+use dhcp::dhcp::*;
+use nom::*;
+
+pub struct DHCPMessage {
+ pub header: DHCPHeader,
+
+ pub options: Vec<DHCPOption>,
+
+ // Set to true if the options were found to be malformed. That is
+ // failing to parse with enough data.
+ pub malformed_options: bool,
+
+ // Set to true if the options failed to parse due to not enough
+ // data.
+ pub truncated_options: bool,
+}
+
+pub struct DHCPHeader {
+ pub opcode: u8,
+ pub htype: u8,
+ pub hlen: u8,
+ pub hops: u8,
+ pub txid: u32,
+ pub seconds: u16,
+ pub flags: u16,
+ pub clientip: Vec<u8>,
+ pub yourip: Vec<u8>,
+ pub serverip: Vec<u8>,
+ pub giaddr: Vec<u8>,
+ pub clienthw: Vec<u8>,
+ pub servername: Vec<u8>,
+ pub bootfilename: Vec<u8>,
+ pub magic: Vec<u8>,
+}
+
+pub struct DHCPOptClientId {
+ pub htype: u8,
+ pub data: Vec<u8>,
+}
+
+/// Option type for time values.
+pub struct DHCPOptTimeValue {
+ pub seconds: u32,
+}
+
+pub struct DHCPOptGeneric {
+ pub data: Vec<u8>,
+}
+
+pub enum DHCPOptionWrapper {
+ ClientId(DHCPOptClientId),
+ TimeValue(DHCPOptTimeValue),
+ Generic(DHCPOptGeneric),
+ End,
+}
+
+pub struct DHCPOption {
+ pub code: u8,
+ pub data: Option<Vec<u8>>,
+ pub option: DHCPOptionWrapper,
+}
+
+named!(pub parse_header<DHCPHeader>,
+ do_parse!(
+ opcode: be_u8
+ >> htype: be_u8
+ >> hlen: be_u8
+ >> hops: be_u8
+ >> txid: be_u32
+ >> seconds: be_u16
+ >> flags: be_u16
+ >> clientip: take!(4)
+ >> yourip: take!(4)
+ >> serverip: take!(4)
+ >> giaddr: take!(4)
+ >> clienthw: take!(16)
+ >> servername: take!(64)
+ >> bootfilename: take!(128)
+ >> magic: take!(4)
+ >> (
+ DHCPHeader{
+ opcode: opcode,
+ htype: htype,
+ hlen: hlen,
+ hops: hops,
+ txid: txid,
+ seconds: seconds,
+ flags: flags,
+ clientip: clientip.to_vec(),
+ yourip: yourip.to_vec(),
+ serverip: serverip.to_vec(),
+ giaddr: giaddr.to_vec(),
+ clienthw: clienthw[0..min(hlen as usize, 16)].to_vec(),
+ servername: servername.to_vec(),
+ bootfilename: bootfilename.to_vec(),
+ magic: magic.to_vec(),
+ }
+ )
+ )
+);
+
+named!(pub parse_clientid_option<DHCPOption>,
+ do_parse!(
+ code: be_u8 >>
+ len: be_u8 >>
+ htype: be_u8 >>
+ data: take!(len - 1) >>
+ (
+ DHCPOption{
+ code: code,
+ data: None,
+ option: DHCPOptionWrapper::ClientId(DHCPOptClientId{
+ htype: 1,
+ data: data.to_vec(),
+ }),
+ }
+ )
+ )
+);
+
+named!(pub parse_address_time_option<DHCPOption>,
+ do_parse!(
+ code: be_u8 >>
+ len: be_u8 >>
+ seconds: be_u32 >>
+ (
+ DHCPOption{
+ code: code,
+ data: None,
+ option: DHCPOptionWrapper::TimeValue(DHCPOptTimeValue{
+ seconds: seconds,
+ }),
+ }
+ )
+ )
+);
+
+named!(pub parse_generic_option<DHCPOption>,
+ do_parse!(
+ code: be_u8 >>
+ len: be_u8 >>
+ data: take!(len) >> (
+ DHCPOption{
+ code: code,
+ data: None,
+ option: DHCPOptionWrapper::Generic(DHCPOptGeneric{
+ data: data.to_vec(),
+ }),
+ }
+ ))
+);
+
+/// Parse a single DHCP option. When option 255 (END) is parsed, the remaining
+/// data will be consumed.
+named!(pub parse_option<DHCPOption>,
+ switch!(peek!(be_u8),
+ // End of options case. We consume the rest of the data
+ // so the parse is not called again. But is there a
+ // better way to "break"?
+ DHCP_OPT_END => do_parse!(
+ code: be_u8 >>
+ data: rest >> (DHCPOption{
+ code: code,
+ data: Some(data.to_vec()),
+ option: DHCPOptionWrapper::End,
+ })) |
+ DHCP_OPT_CLIENT_ID => call!(parse_clientid_option) |
+ DHCP_OPT_ADDRESS_TIME => call!(parse_address_time_option) |
+ DHCP_OPT_RENEWAL_TIME => call!(parse_address_time_option) |
+ DHCP_OPT_REBINDING_TIME => call!(parse_address_time_option) |
+ _ => call!(parse_generic_option)
+ ));
+
+/// Parse and return all the options. Upon the end of option indicator
+/// all the data will be consumed.
+named!(pub parse_all_options<Vec<DHCPOption>>, many0!(call!(parse_option)));
+
+pub fn dhcp_parse(input: &[u8]) -> IResult<&[u8], DHCPMessage> {
+ match parse_header(input) {
+ IResult::Done(rem, header) => {
+ let mut options = Vec::new();
+ let mut next = rem;
+ let mut malformed_options = false;
+ let mut truncated_options = false;
+ loop {
+ match parse_option(next) {
+ IResult::Done(rem, option) => {
+ let done = option.code == DHCP_OPT_END;
+ options.push(option);
+ next = rem;
+ if done {
+ break;
+ }
+ }
+ IResult::Incomplete(_) => {
+ println!("incomplete parsing options");
+ truncated_options = true;
+ break;
+ }
+ IResult::Error(_) => {
+ malformed_options = true;
+ break;
+ }
+ }
+ }
+ let message = DHCPMessage {
+ header: header,
+ options: options,
+ malformed_options: malformed_options,
+ truncated_options: truncated_options,
+ };
+ return IResult::Done(next, message);
+ }
+ IResult::Error(err) => {
+ return IResult::Error(err);
+ }
+ IResult::Incomplete(incomplete) => {
+ return IResult::Incomplete(incomplete);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use dhcp::dhcp::*;
+ use dhcp::parser::*;
+
+ #[test]
+ fn test_parse_discover() {
+ let pcap = include_bytes!("discover.pcap");
+ let payload = &pcap[24 + 16 + 42..];
+
+ match dhcp_parse(payload) {
+ IResult::Done(_rem, message) => {
+ let header = message.header;
+ assert_eq!(header.opcode, BOOTP_REQUEST);
+ assert_eq!(header.htype, 1);
+ assert_eq!(header.hlen, 6);
+ assert_eq!(header.hops, 0);
+ assert_eq!(header.txid, 0x00003d1d);
+ assert_eq!(header.seconds, 0);
+ assert_eq!(header.flags, 0);
+ assert_eq!(header.clientip, &[0, 0, 0, 0]);
+ assert_eq!(header.yourip, &[0, 0, 0, 0]);
+ assert_eq!(header.serverip, &[0, 0, 0, 0]);
+ assert_eq!(header.giaddr, &[0, 0, 0, 0]);
+ assert_eq!(&header.clienthw[..(header.hlen as usize)],
+ &[0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42]);
+ assert!(header.servername.iter().all(|&x| x == 0));
+ assert!(header.bootfilename.iter().all(|&x| x == 0));
+ assert_eq!(header.magic, &[0x63, 0x82, 0x53, 0x63]);
+
+ assert!(!message.malformed_options);
+ assert!(!message.truncated_options);
+
+ assert_eq!(message.options.len(), 5);
+ assert_eq!(message.options[0].code, DHCP_OPT_TYPE);
+ assert_eq!(message.options[1].code, DHCP_OPT_CLIENT_ID);
+ assert_eq!(message.options[2].code, DHCP_OPT_REQUESTED_IP);
+ assert_eq!(message.options[3].code, DHCP_OPT_PARAMETER_LIST);
+ assert_eq!(message.options[4].code, DHCP_OPT_END);
+ }
+ _ => {
+ assert!(false);
+ }
+ }
+ }
+
+}
pub mod ntp;
pub mod tftp;
+pub mod dhcp;
app-layer-tftp.c app-layer-tftp.h \
app-layer-ikev2.c app-layer-ikev2.h \
app-layer-krb5.c app-layer-krb5.h \
+app-layer-dhcp.c app-layer-dhcp.h \
app-layer-template.c app-layer-template.h \
app-layer-ssh.c app-layer-ssh.h \
app-layer-ssl.c app-layer-ssl.h \
output-json-smb.c output-json-smb.h \
output-json-ikev2.c output-json-ikev2.h \
output-json-krb5.c output-json-krb5.h \
+output-json-dhcp.c output-json-dhcp.h \
output-json-template.c output-json-template.h \
output-json-metadata.c output-json-metadata.h \
output-lua.c output-lua.h \
printf(" alproto: ALPROTO_IKEV2\n");
else if (pp_pe->alproto == ALPROTO_KRB5)
printf(" alproto: ALPROTO_KRB5\n");
+ else if (pp_pe->alproto == ALPROTO_DHCP)
+ printf(" alproto: ALPROTO_DHCP\n");
else if (pp_pe->alproto == ALPROTO_TEMPLATE)
printf(" alproto: ALPROTO_TEMPLATE\n");
else if (pp_pe->alproto == ALPROTO_DNP3)
printf(" alproto: ALPROTO_IKEV2\n");
else if (pp_pe->alproto == ALPROTO_KRB5)
printf(" alproto: ALPROTO_KRB5\n");
+ else if (pp_pe->alproto == ALPROTO_DHCP)
+ printf(" alproto: ALPROTO_DHCP\n");
else if (pp_pe->alproto == ALPROTO_TEMPLATE)
printf(" alproto: ALPROTO_TEMPLATE\n");
else if (pp_pe->alproto == ALPROTO_DNP3)
--- /dev/null
+/* Copyright (C) 2015 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Jason Ish <jason.ish@oisf.net>
+ */
+
+#include "suricata-common.h"
+#include "util-unittest.h"
+#include "app-layer-parser.h"
+#include "app-layer-dhcp.h"
+
+#ifdef HAVE_RUST
+#include "rust-dhcp-dhcp-gen.h"
+#endif /* HAVE_RUST */
+
+void RegisterDHCPParsers(void)
+{
+#ifdef HAVE_RUST
+ rs_dhcp_register_parser();
+#endif /* HAVE_RUST */
+#ifdef UNITTESTS
+ AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_DHCP,
+ DHCPParserRegisterTests);
+#endif
+}
+
+#ifdef UNITTESTS
+#endif
+
+void DHCPParserRegisterTests(void)
+{
+#ifdef UNITTESTS
+#endif
+}
--- /dev/null
+/* Copyright (C) 2015 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Jason Ish <jason.ish@oisf.net>
+ */
+
+#ifndef __APP_LAYER_DHCP_H__
+#define __APP_LAYER_DHCP_H__
+
+void RegisterDHCPParsers(void);
+void DHCPParserRegisterTests(void);
+
+#endif /* __APP_LAYER_DHCP_H__ */
#include "app-layer-tftp.h"
#include "app-layer-ikev2.h"
#include "app-layer-krb5.h"
+#include "app-layer-dhcp.h"
#include "app-layer-template.h"
#include "conf.h"
RegisterTFTPParsers();
RegisterIKEV2Parsers();
RegisterKRB5Parsers();
+ RegisterDHCPParsers();
RegisterTemplateParsers();
/** IMAP */
case ALPROTO_KRB5:
proto_name = "krb5";
break;
+ case ALPROTO_DHCP:
+ proto_name = "dhcp";
+ break;
case ALPROTO_TEMPLATE:
proto_name = "template";
break;
if (strcmp(proto_name,"ntp")==0) return ALPROTO_NTP;
if (strcmp(proto_name,"ikev2")==0) return ALPROTO_IKEV2;
if (strcmp(proto_name,"krb5")==0) return ALPROTO_KRB5;
+ if (strcmp(proto_name,"dhcp")==0) return ALPROTO_DHCP;
if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE;
if (strcmp(proto_name,"failed")==0) return ALPROTO_FAILED;
ALPROTO_TFTP,
ALPROTO_IKEV2,
ALPROTO_KRB5,
+ ALPROTO_DHCP,
ALPROTO_TEMPLATE,
/* used by the probing parser when alproto detection fails
--- /dev/null
+/* Copyright (C) 2015 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/*
+ * TODO: Update \author in this file and in output-json-dhcp.h.
+ * TODO: Remove SCLogNotice statements, or convert to debug.
+ * TODO: Implement your app-layers logging.
+ */
+
+/**
+ * \file
+ *
+ * \author Jason Ish <jason.ish@oisf.net>
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "detect.h"
+#include "pkt-var.h"
+#include "conf.h"
+
+#include "threads.h"
+#include "threadvars.h"
+#include "tm-threads.h"
+
+#include "util-unittest.h"
+#include "util-buffer.h"
+#include "util-debug.h"
+#include "util-byte.h"
+
+#include "output.h"
+#include "output-json.h"
+
+#include "app-layer.h"
+#include "app-layer-parser.h"
+
+#include "app-layer-dhcp.h"
+#include "output-json-dhcp.h"
+
+#if defined(HAVE_LIBJANSSON) && defined(HAVE_RUST)
+
+#include "rust-dhcp-logger-gen.h"
+
+typedef struct LogDHCPFileCtx_ {
+ LogFileCtx *file_ctx;
+ uint32_t flags;
+ void *rs_logger;
+} LogDHCPFileCtx;
+
+typedef struct LogDHCPLogThread_ {
+ LogDHCPFileCtx *dhcplog_ctx;
+ uint32_t count;
+ MemBuffer *buffer;
+} LogDHCPLogThread;
+
+static int JsonDHCPLogger(ThreadVars *tv, void *thread_data,
+ const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id)
+{
+ LogDHCPLogThread *thread = thread_data;
+ LogDHCPFileCtx *ctx = thread->dhcplog_ctx;
+
+ json_t *js = CreateJSONHeader((Packet *)p, 0, "dhcp");
+ if (unlikely(js == NULL)) {
+ return TM_ECODE_FAILED;
+ }
+
+ json_t *dhcp_js = rs_dhcp_logger_log(ctx->rs_logger, tx);
+ if (unlikely(dhcp_js == NULL)) {
+ goto skip;
+ }
+ json_object_set_new(js, "dhcp", dhcp_js);
+
+ MemBufferReset(thread->buffer);
+ OutputJSONBuffer(js, thread->dhcplog_ctx->file_ctx, &thread->buffer);
+ json_decref(js);
+
+ return TM_ECODE_OK;
+
+skip:
+ json_decref(js);
+ return TM_ECODE_OK;
+}
+
+static void OutputDHCPLogDeInitCtxSub(OutputCtx *output_ctx)
+{
+ LogDHCPFileCtx *dhcplog_ctx = (LogDHCPFileCtx *)output_ctx->data;
+ rs_dhcp_logger_free(dhcplog_ctx->rs_logger);
+ SCFree(dhcplog_ctx);
+ SCFree(output_ctx);
+}
+
+static OutputInitResult OutputDHCPLogInitSub(ConfNode *conf,
+ OutputCtx *parent_ctx)
+{
+ OutputInitResult result = { NULL, false };
+ OutputJsonCtx *ajt = parent_ctx->data;
+
+ LogDHCPFileCtx *dhcplog_ctx = SCCalloc(1, sizeof(*dhcplog_ctx));
+ if (unlikely(dhcplog_ctx == NULL)) {
+ return result;
+ }
+ dhcplog_ctx->file_ctx = ajt->file_ctx;
+
+ OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx));
+ if (unlikely(output_ctx == NULL)) {
+ SCFree(dhcplog_ctx);
+ return result;
+ }
+ output_ctx->data = dhcplog_ctx;
+ output_ctx->DeInit = OutputDHCPLogDeInitCtxSub;
+
+ dhcplog_ctx->rs_logger = rs_dhcp_logger_new(conf);
+
+ AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_DHCP);
+
+ result.ctx = output_ctx;
+ result.ok = true;
+ return result;
+}
+
+#define OUTPUT_BUFFER_SIZE 65535
+
+static TmEcode JsonDHCPLogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+ LogDHCPLogThread *thread = SCCalloc(1, sizeof(*thread));
+ if (unlikely(thread == NULL)) {
+ return TM_ECODE_FAILED;
+ }
+
+ if (initdata == NULL) {
+ SCLogDebug("Error getting context for EveLogDHCP. \"initdata\" is NULL.");
+ SCFree(thread);
+ return TM_ECODE_FAILED;
+ }
+
+ thread->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE);
+ if (unlikely(thread->buffer == NULL)) {
+ SCFree(thread);
+ return TM_ECODE_FAILED;
+ }
+
+ thread->dhcplog_ctx = ((OutputCtx *)initdata)->data;
+ *data = (void *)thread;
+
+ return TM_ECODE_OK;
+}
+
+static TmEcode JsonDHCPLogThreadDeinit(ThreadVars *t, void *data)
+{
+ LogDHCPLogThread *thread = (LogDHCPLogThread *)data;
+ if (thread == NULL) {
+ return TM_ECODE_OK;
+ }
+ if (thread->buffer != NULL) {
+ MemBufferFree(thread->buffer);
+ }
+ SCFree(thread);
+ return TM_ECODE_OK;
+}
+
+void JsonDHCPLogRegister(void)
+{
+ /* Register as an eve sub-module. */
+ OutputRegisterTxSubModule(LOGGER_JSON_DHCP, "eve-log", "JsonDHCPLog",
+ "eve-log.dhcp", OutputDHCPLogInitSub, ALPROTO_DHCP,
+ JsonDHCPLogger, JsonDHCPLogThreadInit,
+ JsonDHCPLogThreadDeinit, NULL);
+}
+
+#else /* No JSON support. */
+
+void JsonDHCPLogRegister(void)
+{
+}
+
+#endif /* HAVE_LIBJANSSON */
--- /dev/null
+/* Copyright (C) 2015 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author FirstName LastName <name@domain>
+ */
+
+#ifndef __OUTPUT_JSON_DHCP_H__
+#define __OUTPUT_JSON_DHCP_H__
+
+void JsonDHCPLogRegister(void);
+
+#endif /* __OUTPUT_JSON_DHCP_H__ */
#include "output-json-smb.h"
#include "output-json-ikev2.h"
#include "output-json-krb5.h"
+#include "output-json-dhcp.h"
#include "output-json-template.h"
#include "output-lua.h"
#include "output-json-dnp3.h"
JsonSMBLogRegister();
/* IKEv2 JSON logger. */
JsonIKEv2LogRegister();
-
/* KRB5 JSON logger. */
JsonKRB5LogRegister();
+ /* DHCP JSON logger. */
+ JsonDHCPLogRegister();
/* Template JSON logger. */
JsonTemplateLogRegister();
}
LOGGER_JSON_SMB,
LOGGER_JSON_IKEV2,
LOGGER_JSON_KRB5,
+ LOGGER_JSON_DHCP,
LOGGER_JSON_TEMPLATE,
LOGGER_ALERT_DEBUG,
@rust_config_comment@- tftp
@rust_config_comment@- ikev2
@rust_config_comment@- krb5
+ - dhcp:
+ # DHCP logging requires Rust.
+ enabled: @rust_config_enabled@
+ # When extended mode is on, all DHCP messages are logged
+ # with full detail. When extended mode is off (the
+ # default), just enough information to map a MAC address
+ # to an IP address is logged.
+ extended: no
- ssh
- stats:
totals: yes # stats for all threads merged together
ntp:
enabled: @rust_config_enabled@
+ dhcp:
+ enabled: @rust_config_enabled@
+
# Limit for the maximum number of asn1 frames to decode (default 256)
asn1-max-frames: 256