]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
rust/dhcp: Rust based DHCP decoder and logger.
authorJason Ish <ish@unx.ca>
Tue, 8 May 2018 23:49:52 +0000 (17:49 -0600)
committerJason Ish <ish@unx.ca>
Sat, 16 Jun 2018 12:42:28 +0000 (06:42 -0600)
This is a DHCP decoder and logger written in Rust. Unlike most
parsers, this one is stateless so responses are not matched
up to requests by Suricata. However, the output does contain
enough fields to match them up in post-processing.

Rules are included to alert of malformed or truncated options.

23 files changed:
rust/gen-c-headers.py
rust/src/dhcp/README.txt [new file with mode: 0644]
rust/src/dhcp/ack.pcap [new file with mode: 0644]
rust/src/dhcp/dhcp.rs [new file with mode: 0644]
rust/src/dhcp/discover.pcap [new file with mode: 0644]
rust/src/dhcp/logger.rs [new file with mode: 0644]
rust/src/dhcp/mod.rs [new file with mode: 0644]
rust/src/dhcp/offer.pcap [new file with mode: 0644]
rust/src/dhcp/parser.rs [new file with mode: 0644]
rust/src/dhcp/request.pcap [new file with mode: 0644]
rust/src/lib.rs
src/Makefile.am
src/app-layer-detect-proto.c
src/app-layer-dhcp.c [new file with mode: 0644]
src/app-layer-dhcp.h [new file with mode: 0644]
src/app-layer-parser.c
src/app-layer-protos.c
src/app-layer-protos.h
src/output-json-dhcp.c [new file with mode: 0644]
src/output-json-dhcp.h [new file with mode: 0644]
src/output.c
src/suricata-common.h
suricata.yaml.in

index 1c103ae975b79f03d271be9a56eca15aca420570..3a965b038697f565d80aa173d2419f1ea99b54f8 100755 (executable)
@@ -58,6 +58,7 @@ type_map = {
     "u64" :"uint64_t",
 
     "libc::c_void": "void",
+    "c_void": "void",
 
     "libc::c_char": "char",
     "libc::c_int": "int",
diff --git a/rust/src/dhcp/README.txt b/rust/src/dhcp/README.txt
new file mode 100644 (file)
index 0000000..fb7fcc4
--- /dev/null
@@ -0,0 +1,4 @@
+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
diff --git a/rust/src/dhcp/ack.pcap b/rust/src/dhcp/ack.pcap
new file mode 100644 (file)
index 0000000..3c144dc
Binary files /dev/null and b/rust/src/dhcp/ack.pcap differ
diff --git a/rust/src/dhcp/dhcp.rs b/rust/src/dhcp/dhcp.rs
new file mode 100644 (file)
index 0000000..e65001a
--- /dev/null
@@ -0,0 +1,420 @@
+/* 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.");
+    }
+}
diff --git a/rust/src/dhcp/discover.pcap b/rust/src/dhcp/discover.pcap
new file mode 100644 (file)
index 0000000..f692f3f
Binary files /dev/null and b/rust/src/dhcp/discover.pcap differ
diff --git a/rust/src/dhcp/logger.rs b/rust/src/dhcp/logger.rs
new file mode 100644 (file)
index 0000000..8e3a5f7
--- /dev/null
@@ -0,0 +1,280 @@
+/* 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();
+        }
+    }
+}
diff --git a/rust/src/dhcp/mod.rs b/rust/src/dhcp/mod.rs
new file mode 100644 (file)
index 0000000..b34dd98
--- /dev/null
@@ -0,0 +1,20 @@
+/* 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;
diff --git a/rust/src/dhcp/offer.pcap b/rust/src/dhcp/offer.pcap
new file mode 100644 (file)
index 0000000..9d23e25
Binary files /dev/null and b/rust/src/dhcp/offer.pcap differ
diff --git a/rust/src/dhcp/parser.rs b/rust/src/dhcp/parser.rs
new file mode 100644 (file)
index 0000000..411a3b4
--- /dev/null
@@ -0,0 +1,288 @@
+/* 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);
+            }
+        }
+    }
+
+}
diff --git a/rust/src/dhcp/request.pcap b/rust/src/dhcp/request.pcap
new file mode 100644 (file)
index 0000000..5deb8b5
Binary files /dev/null and b/rust/src/dhcp/request.pcap differ
index 00b0a5c9ec68a573a4057395e67c91211d92cb94..02db7eb3d9452258db933a13d097e5a84a2f74b4 100644 (file)
@@ -56,3 +56,4 @@ pub mod ikev2;
 
 pub mod ntp;
 pub mod tftp;
+pub mod dhcp;
index f16246502d98b107c715c30ec47121eb972b55c6..e323e26749295270378efaad05d2f2788f449292 100644 (file)
@@ -48,6 +48,7 @@ app-layer-register.c app-layer-register.h \
 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 \
@@ -328,6 +329,7 @@ output-json-tftp.c output-json-tftp.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 \
index cd4ad19f6c5dd33f93a7b42c253ecb657ce449d1..08d340ed63c4af934bd061a4613559eac09c9e78 100644 (file)
@@ -729,6 +729,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar
                         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)
@@ -798,6 +800,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar
                     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)
diff --git a/src/app-layer-dhcp.c b/src/app-layer-dhcp.c
new file mode 100644 (file)
index 0000000..23b8a8a
--- /dev/null
@@ -0,0 +1,51 @@
+/* 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
+}
diff --git a/src/app-layer-dhcp.h b/src/app-layer-dhcp.h
new file mode 100644 (file)
index 0000000..2981ec3
--- /dev/null
@@ -0,0 +1,30 @@
+/* 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__ */
index 0587a12a930a8b1259f3ec56e1d034015cf723fd..5464a0d38edd6f1d508bd209f7dfa0b1fcdab1b3 100644 (file)
@@ -66,6 +66,7 @@
 #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"
@@ -1454,6 +1455,7 @@ void AppLayerParserRegisterProtocolParsers(void)
     RegisterTFTPParsers();
     RegisterIKEV2Parsers();
     RegisterKRB5Parsers();
+    RegisterDHCPParsers();
     RegisterTemplateParsers();
 
     /** IMAP */
index e8c199a8463cc4bb42f874d7a78d53da535261d6..7d083bcda71079e8faff8f2a9c7f2c378674313e 100644 (file)
@@ -99,6 +99,9 @@ const char *AppProtoToString(AppProto alproto)
         case ALPROTO_KRB5:
             proto_name = "krb5";
             break;
+        case ALPROTO_DHCP:
+            proto_name = "dhcp";
+            break;
         case ALPROTO_TEMPLATE:
             proto_name = "template";
             break;
@@ -140,6 +143,7 @@ AppProto StringToAppProto(const char *proto_name)
     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;
 
index 466f6ed96e3a662dfaf48d25a805fa8d07388f2e..7602167c65c13ef6385a03e8fefdb08a5abc27b8 100644 (file)
@@ -50,6 +50,7 @@ enum AppProtoEnum {
     ALPROTO_TFTP,
     ALPROTO_IKEV2,
     ALPROTO_KRB5,
+    ALPROTO_DHCP,
     ALPROTO_TEMPLATE,
 
     /* used by the probing parser when alproto detection fails
diff --git a/src/output-json-dhcp.c b/src/output-json-dhcp.c
new file mode 100644 (file)
index 0000000..5edebfa
--- /dev/null
@@ -0,0 +1,190 @@
+/* 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 */
diff --git a/src/output-json-dhcp.h b/src/output-json-dhcp.h
new file mode 100644 (file)
index 0000000..4b8dfce
--- /dev/null
@@ -0,0 +1,29 @@
+/* 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__ */
index dd4fa54ac9615b12fa51baeb91ded420565e720c..e27c24fa3f5eeb2095ced5f099cf09edbfd1f8de 100644 (file)
@@ -73,6 +73,7 @@
 #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"
@@ -1099,9 +1100,10 @@ void OutputRegisterLoggers(void)
     JsonSMBLogRegister();
     /* IKEv2 JSON logger. */
     JsonIKEv2LogRegister();
-
     /* KRB5 JSON logger. */
     JsonKRB5LogRegister();
+    /* DHCP JSON logger. */
+    JsonDHCPLogRegister();
     /* Template JSON logger. */
     JsonTemplateLogRegister();
 }
index d57b3ba52233540ef6c1b5cd65ee5e256179327f..29dd172e0205679bb1b74688d0a48129648e5647 100644 (file)
@@ -413,6 +413,7 @@ typedef enum {
     LOGGER_JSON_SMB,
     LOGGER_JSON_IKEV2,
     LOGGER_JSON_KRB5,
+    LOGGER_JSON_DHCP,
     LOGGER_JSON_TEMPLATE,
 
     LOGGER_ALERT_DEBUG,
index e82d8b0a416fe6bdefc1192fa0df7b2f2ab5b675..60ce166e3108b7de3c31ddf21a9a543f139e9683 100644 (file)
@@ -275,6 +275,14 @@ outputs:
         @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
@@ -1052,6 +1060,9 @@ app-layer:
     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