diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index a8fba82aeb..67c8bf2339 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -56,6 +56,7 @@ regex = "~1.5.5" lazy_static = "~1.4.0" base64 = "~0.13.0" time = "=0.3.13" +bendy = { version = "~0.3.3", default-features = false } suricata-derive = { path = "./derive" } diff --git a/rust/src/bittorrent_dht/bittorrent_dht.rs b/rust/src/bittorrent_dht/bittorrent_dht.rs new file mode 100644 index 0000000000..615428d8d2 --- /dev/null +++ b/rust/src/bittorrent_dht/bittorrent_dht.rs @@ -0,0 +1,353 @@ +/* Copyright (C) 2021 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 crate::applayer::{self, *}; +use crate::bittorrent_dht::parser::{ + parse_bittorrent_dht_packet, BitTorrentDHTError, BitTorrentDHTRequest, BitTorrentDHTResponse, +}; +use crate::core::{self, AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_UDP}; +use std::ffi::CString; +use std::str::FromStr; + +const BITTORRENT_DHT_PAYLOAD_PREFIX: &[u8] = b"d1:ad2:id20:"; +const BITTORRENT_DHT_PAYLOAD_PREFIX_LEN: u32 = 12; + +static mut ALPROTO_BITTORRENT_DHT: AppProto = ALPROTO_UNKNOWN; + +#[repr(u32)] +#[derive(AppLayerEvent)] +pub enum BitTorrentDHTEvent { + MalformedPacket = 0, +} + +impl BitTorrentDHTEvent { + pub fn to_cstring(&self) -> &str { + match *self { + BitTorrentDHTEvent::MalformedPacket => "malformed_packet\0", + } + } + + pub fn from_id(id: u32) -> Option { + match id { + 0 => Some(BitTorrentDHTEvent::MalformedPacket), + _ => None, + } + } +} + +impl FromStr for BitTorrentDHTEvent { + type Err = (); + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_ref() { + "malformed_packet" => Ok(BitTorrentDHTEvent::MalformedPacket), + _ => Err(()), + } + } +} + +pub struct BitTorrentDHTTransaction { + tx_id: u64, + pub request_type: Option, + pub request: Option, + pub response: Option, + pub error: Option, + pub transaction_id: String, + pub client_version: Option, + + de_state: Option<*mut core::DetectEngineState>, + events: *mut core::AppLayerDecoderEvents, + tx_data: AppLayerTxData, +} + +impl BitTorrentDHTTransaction { + pub fn new() -> BitTorrentDHTTransaction { + BitTorrentDHTTransaction { + tx_id: 0, + request_type: None, + request: None, + response: None, + error: None, + transaction_id: String::new(), + client_version: None, + de_state: None, + events: std::ptr::null_mut(), + tx_data: AppLayerTxData::new(), + } + } + + pub fn free(&mut self) { + if self.events != std::ptr::null_mut() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + if let Some(state) = self.de_state { + core::sc_detect_engine_state_free(state); + } + } + + /// Set an event on the transaction + pub fn set_event(&mut self, event: BitTorrentDHTEvent) { + core::sc_app_layer_decoder_events_set_event_raw(&mut self.events, event as u8); + } +} + +impl Drop for BitTorrentDHTTransaction { + fn drop(&mut self) { + self.free(); + } +} + +#[derive(Default)] +pub struct BitTorrentDHTState { + tx_id: u64, + transactions: Vec, + state_data: AppLayerStateData, +} + +impl BitTorrentDHTState { + pub fn new() -> Self { + Self::default() + } + + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + self.transactions.retain(|tx| tx.tx_id != tx_id + 1); + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&BitTorrentDHTTransaction> { + self.transactions.iter().find(|&tx| tx.tx_id == tx_id + 1) + } + + fn new_tx(&mut self) -> BitTorrentDHTTransaction { + let mut tx = BitTorrentDHTTransaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + pub fn parse(&mut self, input: &[u8]) -> bool { + let mut tx = self.new_tx(); + let mut status = true; + + if let Err(_e) = parse_bittorrent_dht_packet(input, &mut tx) { + status = false; + tx.set_event(BitTorrentDHTEvent::MalformedPacket); + SCLogDebug!("BitTorrent DHT Parsing Error: {}", _e); + } + + self.transactions.push(tx); + + return status; + } + + fn tx_iterator( + &mut self, min_tx_id: u64, state: &mut u64, + ) -> Option<(&BitTorrentDHTTransaction, 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; + return Some((tx, tx.tx_id - 1, (len - index) > 1)); + } + + return None; + } +} + +/// Probe to see if this flow looks like BitTorrent DHT +fn probe(input: &[u8]) -> bool { + // Ensure the flow started with a request from the client which + // contained the BitTorrent DHT request payload prefix bytes + if input.starts_with(BITTORRENT_DHT_PAYLOAD_PREFIX) { + return true; + } + return false; +} + +// C exports. + +export_tx_data_get!(rs_bittorrent_dht_get_tx_data, BitTorrentDHTTransaction); +export_state_data_get!(rs_bittorrent_dht_get_state_data, BitTorrentDHTState); + +/// C entry point for BitTorrent DHT probing parser. +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_probing_parser( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + // Need more than BITTORRENT_DHT_PAYLOAD_PREFIX_LEN bytes. + if input_len > BITTORRENT_DHT_PAYLOAD_PREFIX_LEN && input != std::ptr::null_mut() { + let slice = build_slice!(input, input_len as usize); + if probe(slice) { + return ALPROTO_BITTORRENT_DHT; + } + } + return ALPROTO_UNKNOWN; +} + +#[no_mangle] +pub extern "C" fn rs_bittorrent_dht_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = BitTorrentDHTState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut std::os::raw::c_void; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(Box::from_raw(state as *mut BitTorrentDHTState)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_tx_free( + state: *mut std::os::raw::c_void, tx_id: u64, +) { + let state = cast_pointer!(state, BitTorrentDHTState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_parse( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, BitTorrentDHTState); + let buf = stream_slice.as_slice(); + state.parse(buf).into() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, BitTorrentDHTState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_get_tx_count( + state: *mut std::os::raw::c_void, +) -> u64 { + let state = cast_pointer!(state, BitTorrentDHTState); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, BitTorrentDHTTransaction); + + // Transaction is done if we have a request, response, or error since + // a new transaction is created for each received packet + if tx.request.is_some() || tx.response.is_some() || tx.error.is_some() { + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_get_events( + tx: *mut std::os::raw::c_void, +) -> *mut core::AppLayerDecoderEvents { + let tx = cast_pointer!(tx, BitTorrentDHTTransaction); + return tx.events; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_get_tx_iterator( + _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64, + _max_tx_id: u64, istate: &mut u64, +) -> applayer::AppLayerGetTxIterTuple { + let state = cast_pointer!(state, BitTorrentDHTState); + match state.tx_iterator(min_tx_id, istate) { + Some((tx, out_tx_id, has_next)) => { + let c_tx = tx as *const _ as *mut _; + let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } + } +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"bittorrent-dht\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_udp_register_parser() { + let default_port = CString::new("[1024:65535]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_UDP, + probe_ts: Some(rs_bittorrent_dht_probing_parser), + probe_tc: Some(rs_bittorrent_dht_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_bittorrent_dht_state_new, + state_free: rs_bittorrent_dht_state_free, + tx_free: rs_bittorrent_dht_state_tx_free, + parse_ts: rs_bittorrent_dht_parse, + parse_tc: rs_bittorrent_dht_parse, + get_tx_count: rs_bittorrent_dht_state_get_tx_count, + get_tx: rs_bittorrent_dht_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_bittorrent_dht_tx_get_alstate_progress, + get_eventinfo: Some(BitTorrentDHTEvent::get_event_info), + get_eventinfo_byid: Some(BitTorrentDHTEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(rs_bittorrent_dht_state_get_tx_iterator), + get_tx_data: rs_bittorrent_dht_get_tx_data, + get_state_data: rs_bittorrent_dht_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("udp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_BITTORRENT_DHT = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Parser registered for bittorrent-dht."); + } else { + SCLogDebug!("Protocol detector and parser disabled for bittorrent-dht."); + } +} diff --git a/rust/src/bittorrent_dht/logger.rs b/rust/src/bittorrent_dht/logger.rs new file mode 100644 index 0000000000..e7246a0584 --- /dev/null +++ b/rust/src/bittorrent_dht/logger.rs @@ -0,0 +1,84 @@ +/* Copyright (C) 2021 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 super::bittorrent_dht::BitTorrentDHTTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +fn log_bittorrent_dht( + tx: &BitTorrentDHTTransaction, js: &mut JsonBuilder, +) -> Result<(), JsonError> { + js.set_string("transaction_id", &tx.transaction_id)?; + if let Some(client_version) = &tx.client_version { + js.set_string("client_version", client_version)?; + } + if let Some(request_type) = &tx.request_type { + js.set_string("request_type", request_type)?; + } + if let Some(error) = &tx.error { + js.open_object("error")?; + js.set_uint("num", u64::from(error.num))?; + js.set_string("msg", &error.msg)?; + js.close()?; + }; + if let Some(request) = &tx.request { + js.open_object("request")?; + js.set_string("id", &request.id)?; + if let Some(target) = &request.target { + js.set_string("target", target)?; + } + if let Some(info_hash) = &request.info_hash { + js.set_string("info_hash", info_hash)?; + } + if let Some(token) = &request.token { + js.set_string("token", token)?; + } + if let Some(implied_port) = request.implied_port { + js.set_uint("implied_port", u64::from(implied_port))?; + } + if let Some(port) = request.port { + js.set_uint("port", u64::from(port))?; + } + js.close()?; + }; + if let Some(response) = &tx.response { + js.open_object("response")?; + js.set_string("id", &response.id)?; + if let Some(nodes) = &response.nodes { + js.set_string("nodes", nodes)?; + } + if let Some(values) = &response.values { + js.open_array("values")?; + for value in values { + js.append_string(value)?; + } + js.close()?; + } + if let Some(token) = &response.token { + js.set_string("token", token)?; + } + js.close()?; + }; + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_logger_log( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, BitTorrentDHTTransaction); + log_bittorrent_dht(tx, js).is_ok() +} diff --git a/rust/src/bittorrent_dht/mod.rs b/rust/src/bittorrent_dht/mod.rs new file mode 100644 index 0000000000..0c79ecc92f --- /dev/null +++ b/rust/src/bittorrent_dht/mod.rs @@ -0,0 +1,20 @@ +/* Copyright (C) 2021 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 bittorrent_dht; +pub mod logger; +pub mod parser; diff --git a/rust/src/bittorrent_dht/parser.rs b/rust/src/bittorrent_dht/parser.rs new file mode 100644 index 0000000000..aae30a8fec --- /dev/null +++ b/rust/src/bittorrent_dht/parser.rs @@ -0,0 +1,602 @@ +/* Copyright (C) 2021 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. + */ + +/*! Parses BitTorrent DHT specification BEP_0005 + * !*/ + +use crate::bittorrent_dht::bittorrent_dht::BitTorrentDHTTransaction; +use bendy::decoding::{Decoder, Error, FromBencode, Object, ResultExt}; + +#[derive(Debug, Eq, PartialEq)] +pub struct BitTorrentDHTRequest { + /// q = * - 20 byte string, sender's node ID in network byte order + pub id: String, + /// q = find_node - target node ID + pub target: Option, + /// q = get_peers/announce_peer - 20-byte info hash of target torrent + pub info_hash: Option, + /// q = announce_peer - token key received from previous get_peers query + pub token: Option, + /// q = announce_peer - 0 or 1, if 1 ignore provided port and + /// use source port of UDP packet + pub implied_port: Option, + /// q = announce_peer - port on which peer will download torrent + pub port: Option, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct BitTorrentDHTResponse { + /// q = * - 20 byte string, receiver's node ID in network byte order + pub id: String, + /// q = find_node/get_peers - compact node info for target node or + /// K(8) closest good nodes in routing table + pub nodes: Option, + /// q = get_peers - list of compact peer infos + pub values: Option>, + /// q = get_peers - token key required for sender's future + /// announce_peer query + pub token: Option, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct BitTorrentDHTError { + /// integer representing the error code + pub num: u16, + /// string containing the error message + pub msg: String, +} + +impl FromBencode for BitTorrentDHTRequest { + // Try to parse with a `max_depth` of one. + // + // The required max depth of a data structure is calculated as follows: + // - every potential nesting level encoded as bencode dictionary or + // list count as +1, + // - everything else is ignored. + // + // struct BitTorrentDHTRequest { // encoded as dictionary (+1) + // id: String, + // target: Option, + // info_hash: Option, + // token: Option, + // implied_port: Option, + // port: Option, + // } + const EXPECTED_RECURSION_DEPTH: usize = 1; + + fn decode_bencode_object(object: Object) -> Result + where + Self: Sized, + { + let mut id = None; + let mut target = None; + let mut info_hash = None; + let mut token = None; + let mut implied_port = None; + let mut port = None; + + let mut dict_dec = object.try_into_dictionary()?; + + while let Some(pair) = dict_dec.next_pair()? { + match pair { + (b"id", value) => { + id = String::decode_bencode_object(value) + .context("id") + .map(Some)?; + } + (b"target", value) => { + target = String::decode_bencode_object(value) + .context("target") + .map(Some)?; + } + (b"info_hash", value) => { + info_hash = String::decode_bencode_object(value) + .context("info_hash") + .map(Some)?; + } + (b"token", value) => { + token = String::decode_bencode_object(value) + .context("token") + .map(Some)?; + } + (b"implied_port", value) => { + implied_port = u8::decode_bencode_object(value) + .context("implied_port") + .map(Some)? + } + (b"port", value) => { + port = u16::decode_bencode_object(value) + .context("port") + .map(Some)? + } + (_unknown_field, _) => {} + } + } + + let id = id.ok_or_else(|| Error::missing_field("id"))?; + + Ok(BitTorrentDHTRequest { + id, + target, + info_hash, + token, + implied_port, + port, + }) + } +} + +impl FromBencode for BitTorrentDHTResponse { + // Try to parse with a `max_depth` of two. + // + // The required max depth of a data structure is calculated as follows: + // - every potential nesting level encoded as bencode dictionary or + // list count as +1, + // - everything else is ignored. + // + // struct BitTorrentDHTResponse { // encoded as dictionary (+1) + // id: String, + // nodes: Option, + // values: Option>, // if present, encoded as list (+1) + // token: Option, + // } + const EXPECTED_RECURSION_DEPTH: usize = 2; + + fn decode_bencode_object(object: Object) -> Result + where + Self: Sized, + { + let mut id = None; + let mut nodes = None; + let mut values = None; + let mut token = None; + + let mut dict_dec = object.try_into_dictionary()?; + + while let Some(pair) = dict_dec.next_pair()? { + match pair { + (b"id", value) => { + id = String::decode_bencode_object(value) + .context("id") + .map(Some)?; + } + (b"nodes", value) => { + nodes = String::decode_bencode_object(value) + .context("nodes") + .map(Some)?; + } + (b"values", value) => { + values = Vec::decode_bencode_object(value) + .context("values") + .map(Some)?; + } + (b"token", value) => { + token = String::decode_bencode_object(value) + .context("token") + .map(Some)?; + } + (_unknown_field, _) => {} + } + } + + let id = id.ok_or_else(|| Error::missing_field("id"))?; + + Ok(BitTorrentDHTResponse { + id, + nodes, + values, + token, + }) + } +} + +impl FromBencode for BitTorrentDHTError { + // Try to parse with a `max_depth` of one. + // + // The required max depth of a data structure is calculated as follows: + // - every potential nesting level encoded as bencode dictionary or + // list count as +1, + // - everything else is ignored. + // + // struct BitTorrentDHTError { // encoded as dictionary (+1) + // num: u16, + // msg: String, + // } + const EXPECTED_RECURSION_DEPTH: usize = 1; + + fn decode_bencode_object(object: Object) -> Result + where + Self: Sized, + { + let mut num = None; + let mut msg = None; + + let mut list_dec = object.try_into_list()?; + + while let Some(object) = list_dec.next_object()? { + match object { + Object::Integer(_) => { + num = u16::decode_bencode_object(object) + .context("num") + .map(Some)?; + } + Object::Bytes(_) => { + msg = String::decode_bencode_object(object) + .context("msg") + .map(Some)?; + } + _ => {} + } + } + + let num = num.ok_or_else(|| Error::missing_field("num"))?; + let msg = msg.ok_or_else(|| Error::missing_field("msg"))?; + + Ok(BitTorrentDHTError { num, msg }) + } +} + +pub fn parse_bittorrent_dht_packet( + bytes: &[u8], tx: &mut BitTorrentDHTTransaction, +) -> Result<(), Error> { + // Try to parse with a `max_depth` of three. + // + // The required max depth of a data structure is calculated as follows: + // - every potential nesting level encoded as bencode dictionary or + // list count as +1, + // - everything else is ignored. + // + // - Outer packet is a dictionary (+1) + // - Max depth of child within dictionary is a BitTorrentDHTResponse (+2) + let mut decoder = Decoder::new(bytes).with_max_depth(3); + let object = decoder.next_object()?; + + let mut packet_type = None; + let mut query_type = None; + let mut query_arguments = None; + let mut response = None; + let mut error = None; + let mut transaction_id = None; + let mut client_version = None; + + let mut dict_dec = object + .ok_or_else(|| Error::unexpected_token("Dict", "EOF"))? + .try_into_dictionary()?; + + while let Some(pair) = dict_dec.next_pair()? { + match pair { + (b"y", value) => { + // q (query) vs r (response) vs e (error) + packet_type = String::decode_bencode_object(value) + .context("packet_type") + .map(Some)?; + } + (b"q", value) => { + // query type found + query_type = String::decode_bencode_object(value) + .context("query_type") + .map(Some)?; + } + (b"a", value) => { + // query arguments found + query_arguments = BitTorrentDHTRequest::decode_bencode_object(value) + .context("query_arguments") + .map(Some)?; + } + (b"r", value) => { + // response found + response = BitTorrentDHTResponse::decode_bencode_object(value) + .context("response") + .map(Some)?; + } + (b"e", value) => { + // error found + error = BitTorrentDHTError::decode_bencode_object(value) + .context("error") + .map(Some)?; + } + (b"t", value) => { + // transaction id found + transaction_id = String::decode_bencode_object(value) + .context("transaction_id") + .map(Some)?; + } + (b"v", value) => { + // client version string found + client_version = String::decode_bencode_object(value) + .context("client_version") + .map(Some)?; + } + (_unknown_field, _) => {} + } + } + + if let Some(t) = packet_type { + match t.as_str() { + "q" => { + tx.request_type = + Some(query_type.ok_or_else(|| Error::missing_field("query_type"))?); + tx.request = + Some(query_arguments.ok_or_else(|| Error::missing_field("query_arguments"))?); + } + "r" => { + tx.response = Some(response.ok_or_else(|| Error::missing_field("response"))?); + } + "e" => { + tx.error = Some(error.ok_or_else(|| Error::missing_field("error"))?); + } + v => { + return Err(Error::unexpected_token("packet_type q, r, or e", v)); + } + } + } else { + return Err(Error::missing_field("packet_type")); + } + + tx.transaction_id = transaction_id.ok_or_else(|| Error::missing_field("transaction_id"))?; + // Client version string is an optional field + tx.client_version = client_version; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe", + BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: Some(1u8), info_hash: Some("mnopqrstuvwxyz123456".to_string()), port: Some(6881u16), token: Some("aoeusnth".to_string()), target: None } ; + "test request from bencode 1")] + #[test_case( + b"d2:id20:abcdefghij0123456789e", + BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: None, info_hash: None, port: None, token: None, target: None } ; + "test request from bencode 2")] + #[test_case( + b"d2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e", + BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: None, info_hash: None, port: None, token: None, target: Some("mnopqrstuvwxyz123456".to_string()) } ; + "test request from bencode 3")] + #[test_case( + b"d2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e", + BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: None, info_hash: Some("mnopqrstuvwxyz123456".to_string()), port: None, token: None, target: None } ; + "test request from bencode 4")] + fn test_request_from_bencode(encoded: &[u8], expected: BitTorrentDHTRequest) { + let decoded = BitTorrentDHTRequest::from_bencode(encoded).unwrap(); + assert_eq!(expected, decoded); + } + + #[test_case( + b"d12:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe", + "Error: missing field: id" ; + "test request from bencode err 1")] + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti9999e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe", + "Error: malformed content discovered in implied_port" ; + "test request from bencode err 2")] + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti-1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe", + "Error: malformed content discovered in implied_port" ; + "test request from bencode err 3")] + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti9999999e5:token8:aoeusnthe", + "Error: malformed content discovered in port" ; + "test request from bencode err 4")] + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti-1e5:token8:aoeusnthe", + "Error: malformed content discovered in port" ; + "test request from bencode err 5")] + #[test_case( + b"i123e", + "Error: discovered Dict but expected Num" ; + "test request from bencode err 6")] + fn test_request_from_bencode_err(encoded: &[u8], expected_error: &str) { + let err = BitTorrentDHTRequest::from_bencode(encoded).unwrap_err(); + assert_eq!(expected_error, err.to_string()); + } + + #[test_case( + b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valueslee", + BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: Some(vec![]), nodes: None } ; + "test response from bencode 1")] + #[test_case( + b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.uee", + BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: Some(vec!["axje.u".to_string()]), nodes: None } ; + "test response from bencode 2")] + #[test_case( + b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee", + BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: Some(vec!["axje.u".to_string(), "idhtnm".to_string()]), nodes: None } ; + "test response from bencode 3")] + #[test_case( + b"d2:id20:mnopqrstuvwxyz123456e", + BitTorrentDHTResponse { id: "mnopqrstuvwxyz123456".to_string(), token: None, values: None, nodes: None } ; + "test response from bencode 4")] + #[test_case( + b"d2:id20:0123456789abcdefghij5:nodes9:def456...e", + BitTorrentDHTResponse { id: "0123456789abcdefghij".to_string(), token: None, values: None, nodes: Some("def456...".to_string()) } ; + "test response from bencode 5")] + #[test_case( + b"d2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe", + BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: None, nodes: Some("def456...".to_string()) } ; + "test response from bencode 6")] + fn test_response_from_bencode(encoded: &[u8], expected: BitTorrentDHTResponse) { + let decoded = BitTorrentDHTResponse::from_bencode(encoded).unwrap(); + assert_eq!(expected, decoded); + } + + #[test_case( + b"d5:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee", + "Error: missing field: id" ; + "test response from bencode err 1")] + #[test_case( + b"i123e", + "Error: discovered Dict but expected Num" ; + "test response from bencode err 2")] + fn test_response_from_bencode_err(encoded: &[u8], expected_error: &str) { + let err = BitTorrentDHTResponse::from_bencode(encoded).unwrap_err(); + assert_eq!(expected_error, err.to_string()); + } + + #[test_case( + b"li201e23:A Generic Error Ocurrede", + BitTorrentDHTError { num: 201u16, msg: "A Generic Error Ocurred".to_string() } ; + "test error from bencode 1")] + #[test_case( + b"li202e12:Server Errore", + BitTorrentDHTError { num: 202u16, msg: "Server Error".to_string() } ; + "test error from bencode 2")] + #[test_case( + b"li203e14:Protocol Errore", + BitTorrentDHTError { num: 203u16, msg: "Protocol Error".to_string() } ; + "test error from bencode 3")] + #[test_case( + b"li204e14:Method Unknowne", + BitTorrentDHTError { num: 204u16, msg: "Method Unknown".to_string() } ; + "test error from bencode 4")] + fn test_error_from_bencode(encoded: &[u8], expected: BitTorrentDHTError) { + let decoded = BitTorrentDHTError::from_bencode(encoded).unwrap(); + assert_eq!(expected, decoded); + } + + #[test_case( + b"l23:A Generic Error Ocurrede", + "Error: missing field: num" ; + "test error from bencode err 1")] + #[test_case( + b"li201ee", + "Error: missing field: msg" ; + "test error from bencode err 2")] + #[test_case( + b"li999999ee", + "Error: malformed content discovered in num" ; + "test error from bencode err 3")] + #[test_case( + b"li-1ee", + "Error: malformed content discovered in num" ; + "test error from bencode err 4")] + #[test_case( + b"i123e", + "Error: discovered List but expected Num" ; + "test error from bencode err 5")] + fn test_error_from_bencode_err(encoded: &[u8], expected_error: &str) { + let err = BitTorrentDHTError::from_bencode(encoded).unwrap_err(); + assert_eq!(expected_error, err.to_string()); + } + + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:v4:UT011:y1:qe", + Some("ping".to_string()), + Some(BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: None, info_hash: None, port: None, token: None, target: None }), + None, + None, + "aa".to_string(), + Some("UT01".to_string()) ; + "test parse bittorrent dht packet 1" + )] + #[test_case( + b"d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re", + None, + None, + Some(BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: Some(vec!["axje.u".to_string(), "idhtnm".to_string()]), nodes: None}), + None, + "aa".to_string(), + None ; + "test parse bittorrent dht packet 2" + )] + #[test_case( + b"d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:v4:UT011:y1:ee", + None, + None, + None, + Some(BitTorrentDHTError { num: 201u16, msg: "A Generic Error Ocurred".to_string() }), + "aa".to_string(), + Some("UT01".to_string()) ; + "test parse bittorrent dht packet 3" + )] + fn test_parse_bittorrent_dht_packet( + encoded: &[u8], request_type: Option, + expected_request: Option, + expected_response: Option, + expected_error: Option, expected_transaction_id: String, + expected_client_version: Option, + ) { + let mut tx = BitTorrentDHTTransaction::new(); + parse_bittorrent_dht_packet(encoded, &mut tx).unwrap(); + assert_eq!(request_type, tx.request_type); + assert_eq!(expected_request, tx.request); + assert_eq!(expected_response, tx.response); + assert_eq!(expected_error, tx.error); + assert_eq!(expected_transaction_id, tx.transaction_id); + assert_eq!(expected_client_version, tx.client_version); + } + + #[test_case( + b"", + "Error: discovered Dict but expected EOF" ; + "test parse bittorrent dht packet err 1" + )] + #[test_case( + b"li2123ei321ee", + "Error: discovered Dict but expected List" ; + "test parse bittorrent dht packet err 2" + )] + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aae", + "Error: missing field: packet_type" ; + "test parse bittorrent dht packet err 3" + )] + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:Fe", + "Error: discovered packet_type q, r, or e but expected F" ; + "test parse bittorrent dht packet err 4" + )] + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:t2:aa1:y1:qe", + "Error: missing field: query_type" ; + "test parse bittorrent dht packet err 5" + )] + #[test_case( + b"d1:q4:ping1:t2:aa1:y1:qe", + "Error: missing field: query_arguments" ; + "test parse bittorrent dht packet err 6" + )] + #[test_case( + b"d1:t2:aa1:y1:re", + "Error: missing field: response" ; + "test parse bittorrent dht packet err 7" + )] + #[test_case( + b"d1:t2:aa1:y1:ee", + "Error: missing field: error" ; + "test parse bittorrent dht packet err 8" + )] + #[test_case( + b"d1:ade1:q4:ping1:t2:aa1:y1:qe", + "Error: missing field: id in query_arguments" ; + "test parse bittorrent dht packet err 9" + )] + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:y1:qe", + "Error: missing field: transaction_id" ; + "test parse bittorrent dht packet err 10" + )] + fn test_parse_bittorrent_dht_packet_err(encoded: &[u8], expected_error: &str) { + let mut tx = BitTorrentDHTTransaction::new(); + let err = parse_bittorrent_dht_packet(encoded, &mut tx).unwrap_err(); + assert_eq!(expected_error, err.to_string()); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e5c3cacd76..175053fc08 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -118,6 +118,7 @@ pub mod mime; pub mod ssh; pub mod http2; pub mod quic; +pub mod bittorrent_dht; pub mod plugin; pub mod util; pub mod ffi; diff --git a/src/Makefile.am b/src/Makefile.am index 3ba7e5e119..a019992345 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -396,6 +396,7 @@ noinst_HEADERS = \ output.h \ output-json-alert.h \ output-json-anomaly.h \ + output-json-bittorrent-dht.h \ output-json-dcerpc.h \ output-json-dhcp.h \ output-json-dnp3.h \ @@ -1003,6 +1004,7 @@ libsuricata_c_a_SOURCES = \ output-flow.c \ output-json-alert.c \ output-json-anomaly.c \ + output-json-bittorrent-dht.c \ output-json.c \ output-json-common.c \ output-json-dcerpc.c \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index ceb0474a81..d66f7e27cb 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2014 Open Information Security Foundation +/* Copyright (C) 2007-2021 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 @@ -920,6 +920,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) printf(" alproto: ALPROTO_DNP3\n"); + else if (pp_pe->alproto == ALPROTO_BITTORRENT_DHT) + printf(" alproto: ALPROTO_BITTORRENT_DHT\n"); else printf("impossible\n"); @@ -1003,6 +1005,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) printf(" alproto: ALPROTO_DNP3\n"); + else if (pp_pe->alproto == ALPROTO_BITTORRENT_DHT) + printf(" alproto: ALPROTO_BITTORRENT_DHT\n"); else printf("impossible\n"); diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 31c4c8ade5..c65a1c2af2 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2020 Open Information Security Foundation +/* Copyright (C) 2007-2021 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 @@ -1723,6 +1723,7 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterSMTPParsers(); rs_dns_udp_register_parser(); rs_dns_tcp_register_parser(); + rs_bittorrent_dht_udp_register_parser(); RegisterModbusParsers(); RegisterENIPUDPParsers(); RegisterENIPTCPParsers(); diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 823e758621..d18d2f6990 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2013 Open Information Security Foundation +/* Copyright (C) 2007-2021 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 @@ -132,6 +132,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_HTTP: proto_name = "http_any"; break; + case ALPROTO_BITTORRENT_DHT: + proto_name = "bittorrent-dht"; + break; case ALPROTO_FAILED: proto_name = "failed"; break; @@ -191,6 +194,8 @@ AppProto StringToAppProto(const char *proto_name) if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST; if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP; if (strcmp(proto_name,"http2")==0) return ALPROTO_HTTP2; + if (strcmp(proto_name, "bittorrent-dht") == 0) + return ALPROTO_BITTORRENT_DHT; if (strcmp(proto_name,"failed")==0) return ALPROTO_FAILED; return ALPROTO_UNKNOWN; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 3bc023c599..0c29135db6 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2013 Open Information Security Foundation +/* Copyright (C) 2007-2021 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 @@ -60,6 +60,7 @@ enum AppProtoEnum { ALPROTO_TEMPLATE_RUST, ALPROTO_RDP, ALPROTO_HTTP2, + ALPROTO_BITTORRENT_DHT, // signature-only (ie not seen in flow) // HTTP for any version (ALPROTO_HTTP1 (version 1) or ALPROTO_HTTP2) diff --git a/src/output-json-alert.c b/src/output-json-alert.c index 3ec980b83c..5b72dfba3c 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -274,6 +274,25 @@ static void AlertJsonRDP(const Flow *f, const uint64_t tx_id, JsonBuilder *js) } } +static void AlertJsonBitTorrentDHT(const Flow *f, const uint64_t tx_id, JsonBuilder *js) +{ + void *bittorrent_dht_state = (void *)FlowGetAppState(f); + if (bittorrent_dht_state != NULL) { + void *tx = + AppLayerParserGetTx(f->proto, ALPROTO_BITTORRENT_DHT, bittorrent_dht_state, tx_id); + if (tx != NULL) { + JsonBuilderMark mark = { 0, 0, 0 }; + jb_get_mark(js, &mark); + jb_open_object(js, "bittorrent-dht"); + if (rs_bittorrent_dht_logger_log(tx, js)) { + jb_close(js); + } else { + jb_restore_mark(js, &mark); + } + } + } +} + static void AlertJsonSourceTarget(const Packet *p, const PacketAlert *pa, JsonBuilder *js, JsonAddrInfo *addr) { @@ -568,6 +587,9 @@ static void AlertAddAppLayer(const Packet *p, JsonBuilder *jb, jb_restore_mark(jb, &mark); } break; + case ALPROTO_BITTORRENT_DHT: + AlertJsonBitTorrentDHT(p->flow, tx_id, jb); + break; default: break; } diff --git a/src/output-json-bittorrent-dht.c b/src/output-json-bittorrent-dht.c new file mode 100644 index 0000000000..64d7ff8804 --- /dev/null +++ b/src/output-json-bittorrent-dht.c @@ -0,0 +1,165 @@ +/* Copyright (C) 2021 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 + * + * Implement JSON/eve logging app-layer BitTorrent DHT. + */ + +#include "suricata-common.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 "output-json-bittorrent-dht.h" +#include "rust.h" + +typedef struct LogBitTorrentDHTFileCtx_ { + uint32_t flags; + OutputJsonCtx *eve_ctx; +} LogBitTorrentDHTFileCtx; + +typedef struct LogBitTorrentDHTLogThread_ { + LogBitTorrentDHTFileCtx *bittorrent_dht_log_ctx; + OutputJsonThreadCtx *ctx; +} LogBitTorrentDHTLogThread; + +static int JsonBitTorrentDHTLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, + void *state, void *tx, uint64_t tx_id) +{ + LogBitTorrentDHTLogThread *thread = thread_data; + + JsonBuilder *js = CreateEveHeader( + p, LOG_DIR_PACKET, "bittorrent-dht", NULL, thread->bittorrent_dht_log_ctx->eve_ctx); + if (unlikely(js == NULL)) { + return TM_ECODE_FAILED; + } + + jb_open_object(js, "bittorrent-dht"); + if (!rs_bittorrent_dht_logger_log(tx, js)) { + goto error; + } + jb_close(js); + + OutputJsonBuilderBuffer(js, thread->ctx); + jb_free(js); + + return TM_ECODE_OK; + +error: + jb_free(js); + return TM_ECODE_FAILED; +} + +static void OutputBitTorrentDHTLogDeInitCtxSub(OutputCtx *output_ctx) +{ + LogBitTorrentDHTFileCtx *bittorrent_dht_log_ctx = (LogBitTorrentDHTFileCtx *)output_ctx->data; + SCFree(bittorrent_dht_log_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputBitTorrentDHTLogInitSub(ConfNode *conf, OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ajt = parent_ctx->data; + + LogBitTorrentDHTFileCtx *bittorrent_dht_log_ctx = SCCalloc(1, sizeof(*bittorrent_dht_log_ctx)); + if (unlikely(bittorrent_dht_log_ctx == NULL)) { + return result; + } + bittorrent_dht_log_ctx->eve_ctx = ajt; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); + if (unlikely(output_ctx == NULL)) { + SCFree(bittorrent_dht_log_ctx); + return result; + } + output_ctx->data = bittorrent_dht_log_ctx; + output_ctx->DeInit = OutputBitTorrentDHTLogDeInitCtxSub; + + AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_BITTORRENT_DHT); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +static TmEcode JsonBitTorrentDHTLogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + LogBitTorrentDHTLogThread *thread = SCCalloc(1, sizeof(*thread)); + if (unlikely(thread == NULL)) { + return TM_ECODE_FAILED; + } + + if (initdata == NULL) { + SCLogDebug("Error getting context for EveLogBitTorrentDHT. \"initdata\" is NULL."); + goto error_exit; + } + + thread->bittorrent_dht_log_ctx = ((OutputCtx *)initdata)->data; + thread->ctx = CreateEveThreadCtx(t, thread->bittorrent_dht_log_ctx->eve_ctx); + if (!thread->ctx) { + goto error_exit; + } + *data = (void *)thread; + + return TM_ECODE_OK; + +error_exit: + SCFree(thread); + return TM_ECODE_FAILED; +} + +static TmEcode JsonBitTorrentDHTLogThreadDeinit(ThreadVars *t, void *data) +{ + LogBitTorrentDHTLogThread *thread = (LogBitTorrentDHTLogThread *)data; + if (thread == NULL) { + return TM_ECODE_OK; + } + FreeEveThreadCtx(thread->ctx); + SCFree(thread); + return TM_ECODE_OK; +} + +void JsonBitTorrentDHTLogRegister(void) +{ + if (ConfGetNode("app-layer.protocols.bittorrent-dht") == NULL) { + return; + } + + /* Register as an eve sub-module. */ + OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonBitTorrentDHTLog", + "eve-log.bittorrent-dht", OutputBitTorrentDHTLogInitSub, ALPROTO_BITTORRENT_DHT, + JsonBitTorrentDHTLogger, JsonBitTorrentDHTLogThreadInit, + JsonBitTorrentDHTLogThreadDeinit, NULL); +} diff --git a/src/output-json-bittorrent-dht.h b/src/output-json-bittorrent-dht.h new file mode 100644 index 0000000000..8927f4d159 --- /dev/null +++ b/src/output-json-bittorrent-dht.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2021 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 + */ + +#ifndef __OUTPUT_JSON_BITTORRENT_DHT_H__ +#define __OUTPUT_JSON_BITTORRENT_DHT_H__ + +void JsonBitTorrentDHTLogRegister(void); + +#endif /* __OUTPUT_JSON_BITTORRENT_DHT_H__ */ diff --git a/src/output.c b/src/output.c index bcf4e26255..a9929f048d 100644 --- a/src/output.c +++ b/src/output.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2016 Open Information Security Foundation +/* Copyright (C) 2007-2021 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 @@ -87,6 +87,7 @@ #include "output-json-metadata.h" #include "output-json-dcerpc.h" #include "output-json-frame.h" +#include "output-json-bittorrent-dht.h" #include "output-filestore.h" typedef struct RootLogger_ { @@ -1126,4 +1127,6 @@ void OutputRegisterLoggers(void) JsonDCERPCLogRegister(); /* app layer frames */ JsonFrameLogRegister(); + /* BitTorrent DHT JSON logger */ + JsonBitTorrentDHTLogRegister(); } diff --git a/src/util-profiling.c b/src/util-profiling.c index 55b5029fd2..aadbe66ac5 100644 --- a/src/util-profiling.c +++ b/src/util-profiling.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2012 Open Information Security Foundation +/* Copyright (C) 2007-2021 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 diff --git a/suricata.yaml.in b/suricata.yaml.in index e1a2ca932d..5988dfbf67 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -280,6 +280,7 @@ outputs: - ike - dcerpc - krb5 + - bittorrent-dht - snmp - rfb - sip @@ -808,6 +809,8 @@ app-layer: # max-tx: 4096 krb5: enabled: yes + bittorrent-dht: + enabled: yes snmp: enabled: yes ike: