bittorrent-dht: add bittorrent-dht app layer

Parses and logs the bittorrent-dht protocol.

Note: Includes some compilation fixups after rebase by Jason Ish.

Feature: #3086
pull/8113/head
Aaron Bungay 5 years ago committed by Victor Julien
parent 27672c950c
commit 86037885a9

@ -56,6 +56,7 @@ regex = "~1.5.5"
lazy_static = "~1.4.0" lazy_static = "~1.4.0"
base64 = "~0.13.0" base64 = "~0.13.0"
time = "=0.3.13" time = "=0.3.13"
bendy = { version = "~0.3.3", default-features = false }
suricata-derive = { path = "./derive" } suricata-derive = { path = "./derive" }

@ -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<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.");
}
}

@ -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()
}

@ -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;

@ -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
* <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());
}
}

@ -118,6 +118,7 @@ pub mod mime;
pub mod ssh; pub mod ssh;
pub mod http2; pub mod http2;
pub mod quic; pub mod quic;
pub mod bittorrent_dht;
pub mod plugin; pub mod plugin;
pub mod util; pub mod util;
pub mod ffi; pub mod ffi;

@ -396,6 +396,7 @@ noinst_HEADERS = \
output.h \ output.h \
output-json-alert.h \ output-json-alert.h \
output-json-anomaly.h \ output-json-anomaly.h \
output-json-bittorrent-dht.h \
output-json-dcerpc.h \ output-json-dcerpc.h \
output-json-dhcp.h \ output-json-dhcp.h \
output-json-dnp3.h \ output-json-dnp3.h \
@ -1003,6 +1004,7 @@ libsuricata_c_a_SOURCES = \
output-flow.c \ output-flow.c \
output-json-alert.c \ output-json-alert.c \
output-json-anomaly.c \ output-json-anomaly.c \
output-json-bittorrent-dht.c \
output-json.c \ output-json.c \
output-json-common.c \ output-json-common.c \
output-json-dcerpc.c \ output-json-dcerpc.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 * You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free * 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"); printf(" alproto: ALPROTO_TEMPLATE\n");
else if (pp_pe->alproto == ALPROTO_DNP3) else if (pp_pe->alproto == ALPROTO_DNP3)
printf(" alproto: ALPROTO_DNP3\n"); printf(" alproto: ALPROTO_DNP3\n");
else if (pp_pe->alproto == ALPROTO_BITTORRENT_DHT)
printf(" alproto: ALPROTO_BITTORRENT_DHT\n");
else else
printf("impossible\n"); printf("impossible\n");
@ -1003,6 +1005,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar
printf(" alproto: ALPROTO_TEMPLATE\n"); printf(" alproto: ALPROTO_TEMPLATE\n");
else if (pp_pe->alproto == ALPROTO_DNP3) else if (pp_pe->alproto == ALPROTO_DNP3)
printf(" alproto: ALPROTO_DNP3\n"); printf(" alproto: ALPROTO_DNP3\n");
else if (pp_pe->alproto == ALPROTO_BITTORRENT_DHT)
printf(" alproto: ALPROTO_BITTORRENT_DHT\n");
else else
printf("impossible\n"); printf("impossible\n");

@ -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 * You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free * the GNU General Public License version 2 as published by the Free
@ -1723,6 +1723,7 @@ void AppLayerParserRegisterProtocolParsers(void)
RegisterSMTPParsers(); RegisterSMTPParsers();
rs_dns_udp_register_parser(); rs_dns_udp_register_parser();
rs_dns_tcp_register_parser(); rs_dns_tcp_register_parser();
rs_bittorrent_dht_udp_register_parser();
RegisterModbusParsers(); RegisterModbusParsers();
RegisterENIPUDPParsers(); RegisterENIPUDPParsers();
RegisterENIPTCPParsers(); RegisterENIPTCPParsers();

@ -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 * You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free * the GNU General Public License version 2 as published by the Free
@ -132,6 +132,9 @@ const char *AppProtoToString(AppProto alproto)
case ALPROTO_HTTP: case ALPROTO_HTTP:
proto_name = "http_any"; proto_name = "http_any";
break; break;
case ALPROTO_BITTORRENT_DHT:
proto_name = "bittorrent-dht";
break;
case ALPROTO_FAILED: case ALPROTO_FAILED:
proto_name = "failed"; proto_name = "failed";
break; 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,"template-rust")==0) return ALPROTO_TEMPLATE_RUST;
if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP; if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP;
if (strcmp(proto_name,"http2")==0) return ALPROTO_HTTP2; 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; if (strcmp(proto_name,"failed")==0) return ALPROTO_FAILED;
return ALPROTO_UNKNOWN; return ALPROTO_UNKNOWN;

@ -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 * You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free * the GNU General Public License version 2 as published by the Free
@ -60,6 +60,7 @@ enum AppProtoEnum {
ALPROTO_TEMPLATE_RUST, ALPROTO_TEMPLATE_RUST,
ALPROTO_RDP, ALPROTO_RDP,
ALPROTO_HTTP2, ALPROTO_HTTP2,
ALPROTO_BITTORRENT_DHT,
// signature-only (ie not seen in flow) // signature-only (ie not seen in flow)
// HTTP for any version (ALPROTO_HTTP1 (version 1) or ALPROTO_HTTP2) // HTTP for any version (ALPROTO_HTTP1 (version 1) or ALPROTO_HTTP2)

@ -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, static void AlertJsonSourceTarget(const Packet *p, const PacketAlert *pa,
JsonBuilder *js, JsonAddrInfo *addr) JsonBuilder *js, JsonAddrInfo *addr)
{ {
@ -568,6 +587,9 @@ static void AlertAddAppLayer(const Packet *p, JsonBuilder *jb,
jb_restore_mark(jb, &mark); jb_restore_mark(jb, &mark);
} }
break; break;
case ALPROTO_BITTORRENT_DHT:
AlertJsonBitTorrentDHT(p->flow, tx_id, jb);
break;
default: default:
break; break;
} }

@ -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);
}

@ -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__ */

@ -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 * You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free * the GNU General Public License version 2 as published by the Free
@ -87,6 +87,7 @@
#include "output-json-metadata.h" #include "output-json-metadata.h"
#include "output-json-dcerpc.h" #include "output-json-dcerpc.h"
#include "output-json-frame.h" #include "output-json-frame.h"
#include "output-json-bittorrent-dht.h"
#include "output-filestore.h" #include "output-filestore.h"
typedef struct RootLogger_ { typedef struct RootLogger_ {
@ -1126,4 +1127,6 @@ void OutputRegisterLoggers(void)
JsonDCERPCLogRegister(); JsonDCERPCLogRegister();
/* app layer frames */ /* app layer frames */
JsonFrameLogRegister(); JsonFrameLogRegister();
/* BitTorrent DHT JSON logger */
JsonBitTorrentDHTLogRegister();
} }

@ -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 * You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free * the GNU General Public License version 2 as published by the Free

@ -280,6 +280,7 @@ outputs:
- ike - ike
- dcerpc - dcerpc
- krb5 - krb5
- bittorrent-dht
- snmp - snmp
- rfb - rfb
- sip - sip
@ -808,6 +809,8 @@ app-layer:
# max-tx: 4096 # max-tx: 4096
krb5: krb5:
enabled: yes enabled: yes
bittorrent-dht:
enabled: yes
snmp: snmp:
enabled: yes enabled: yes
ike: ike:

Loading…
Cancel
Save