Parses and logs the bittorrent-dht protocol.
Note: Includes some compilation fixups after rebase by Jason Ish.
Feature: #3086
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" }
--- /dev/null
+/* 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<BitTorrentDHTEvent> {
+ match id {
+ 0 => Some(BitTorrentDHTEvent::MalformedPacket),
+ _ => None,
+ }
+ }
+}
+
+impl FromStr for BitTorrentDHTEvent {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<BitTorrentDHTEvent, Self::Err> {
+ match s.to_lowercase().as_ref() {
+ "malformed_packet" => Ok(BitTorrentDHTEvent::MalformedPacket),
+ _ => Err(()),
+ }
+ }
+}
+
+pub struct BitTorrentDHTTransaction {
+ tx_id: u64,
+ pub request_type: Option<String>,
+ pub request: Option<BitTorrentDHTRequest>,
+ pub response: Option<BitTorrentDHTResponse>,
+ pub error: Option<BitTorrentDHTError>,
+ pub transaction_id: String,
+ pub client_version: Option<String>,
+
+ 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<BitTorrentDHTTransaction>,
+ 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.");
+ }
+}
--- /dev/null
+/* 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()
+}
--- /dev/null
+/* 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;
--- /dev/null
+/* 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
+ * <https://www.bittorrent.org/beps/bep_0005.html> !*/
+
+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<String>,
+ /// q = get_peers/announce_peer - 20-byte info hash of target torrent
+ pub info_hash: Option<String>,
+ /// q = announce_peer - token key received from previous get_peers query
+ pub token: Option<String>,
+ /// q = announce_peer - 0 or 1, if 1 ignore provided port and
+ /// use source port of UDP packet
+ pub implied_port: Option<u8>,
+ /// q = announce_peer - port on which peer will download torrent
+ pub port: Option<u16>,
+}
+
+#[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<String>,
+ /// q = get_peers - list of compact peer infos
+ pub values: Option<Vec<String>>,
+ /// q = get_peers - token key required for sender's future
+ /// announce_peer query
+ pub token: Option<String>,
+}
+
+#[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<String>,
+ // info_hash: Option<String>,
+ // token: Option<String>,
+ // implied_port: Option<u8>,
+ // port: Option<u16>,
+ // }
+ const EXPECTED_RECURSION_DEPTH: usize = 1;
+
+ fn decode_bencode_object(object: Object) -> Result<Self, Error>
+ 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<String>,
+ // values: Option<Vec<String>>, // if present, encoded as list (+1)
+ // token: Option<String>,
+ // }
+ const EXPECTED_RECURSION_DEPTH: usize = 2;
+
+ fn decode_bencode_object(object: Object) -> Result<Self, Error>
+ 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<Self, Error>
+ 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<String>,
+ expected_request: Option<BitTorrentDHTRequest>,
+ expected_response: Option<BitTorrentDHTResponse>,
+ expected_error: Option<BitTorrentDHTError>, expected_transaction_id: String,
+ expected_client_version: Option<String>,
+ ) {
+ 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());
+ }
+}
pub mod ssh;
pub mod http2;
pub mod quic;
+pub mod bittorrent_dht;
pub mod plugin;
pub mod util;
pub mod ffi;
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 \
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 \
-/* 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
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");
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");
-/* 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
RegisterSMTPParsers();
rs_dns_udp_register_parser();
rs_dns_tcp_register_parser();
+ rs_bittorrent_dht_udp_register_parser();
RegisterModbusParsers();
RegisterENIPUDPParsers();
RegisterENIPTCPParsers();
-/* 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
case ALPROTO_HTTP:
proto_name = "http_any";
break;
+ case ALPROTO_BITTORRENT_DHT:
+ proto_name = "bittorrent-dht";
+ break;
case ALPROTO_FAILED:
proto_name = "failed";
break;
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;
-/* 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
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)
}
}
+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)
{
jb_restore_mark(jb, &mark);
}
break;
+ case ALPROTO_BITTORRENT_DHT:
+ AlertJsonBitTorrentDHT(p->flow, tx_id, jb);
+ break;
default:
break;
}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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__ */
-/* 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
#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_ {
JsonDCERPCLogRegister();
/* app layer frames */
JsonFrameLogRegister();
+ /* BitTorrent DHT JSON logger */
+ JsonBitTorrentDHTLogRegister();
}
-/* 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
- ike
- dcerpc
- krb5
+ - bittorrent-dht
- snmp
- rfb
- sip
# max-tx: 4096
krb5:
enabled: yes
+ bittorrent-dht:
+ enabled: yes
snmp:
enabled: yes
ike: