]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
bittorrent-dht: decode node data structures
authorJason Ish <jason.ish@oisf.net>
Wed, 7 Sep 2022 14:51:28 +0000 (08:51 -0600)
committerVictor Julien <vjulien@oisf.net>
Fri, 28 Oct 2022 09:48:13 +0000 (11:48 +0200)
Instead of decoding the nodes field into a blog of bytes, decode it into
an array of node info objects, each with a node id, IP address and port.

rust/src/bittorrent_dht/logger.rs
rust/src/bittorrent_dht/parser.rs

index 51c0418af0a0280b19414326aab831655d80d928..50a1a260877c2ffac5b9c91df9a4bf9353d3fc68 100644 (file)
 use super::bittorrent_dht::BitTorrentDHTTransaction;
 use crate::jsonbuilder::{JsonBuilder, JsonError};
 
+/// Format bytes as an IP address string.
+fn print_ip_addr(addr: &[u8]) -> std::string::String {
+    if addr.len() == 4 {
+        return format!("{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3]);
+    } else if addr.len() == 16 {
+        return format!("{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}",
+                       addr[0],
+                       addr[1],
+                       addr[2],
+                       addr[3],
+                       addr[4],
+                       addr[5],
+                       addr[6],
+                       addr[7],
+                       addr[8],
+                       addr[9],
+                       addr[10],
+                       addr[11],
+                       addr[12],
+                       addr[13],
+                       addr[14],
+                       addr[15]);
+    } else {
+        return "".to_string();
+    }
+}
+
 fn log_bittorrent_dht(
     tx: &BitTorrentDHTTransaction, js: &mut JsonBuilder,
 ) -> Result<(), JsonError> {
@@ -58,7 +85,17 @@ fn log_bittorrent_dht(
         js.open_object("response")?;
         js.set_hex("id", &response.id)?;
         if let Some(nodes) = &response.nodes {
-            js.set_hex("nodes", nodes)?;
+            if !nodes.is_empty() {
+                js.open_array("nodes")?;
+                for node in nodes {
+                    js.start_object()?;
+                    js.set_hex("id", &node.id)?;
+                    js.set_string("ip", &print_ip_addr(&node.ip))?;
+                    js.set_uint("port", node.port.into())?;
+                    js.close()?;
+                }
+                js.close()?;
+            }
         }
         if let Some(values) = &response.values {
             js.open_array("values")?;
index ec74b8a186759df9eccc10e35fa0b8fff605fa56..b72c5aee9a8322632374961fb205ecdacaf8f1c3 100644 (file)
 /*! Parses BitTorrent DHT specification BEP_0005
  *  <https://www.bittorrent.org/beps/bep_0005.html> !*/
 
+// TODO: Custom error type, as we have bencode and nom errors, and may have an our application
+// specific errors as we finish off this parser.
+
 use crate::bittorrent_dht::bittorrent_dht::BitTorrentDHTTransaction;
 use bendy::decoding::{Decoder, Error, FromBencode, Object, ResultExt};
+use nom7::IResult;
+use nom7::bytes::complete::take;
+use nom7::number::complete::be_u16;
 
 #[derive(Debug, Eq, PartialEq)]
 pub struct BitTorrentDHTRequest {
@@ -44,7 +50,7 @@ pub struct BitTorrentDHTResponse {
     pub id: Vec<u8>,
     /// q = find_node/get_peers - compact node info for target node or
     ///                           K(8) closest good nodes in routing table
-    pub nodes: Option<Vec<u8>>,
+    pub nodes: Option<Vec<Node>>,
     /// q = get_peers - list of compact peer infos
     pub values: Option<Vec<String>>,
     /// q = get_peers - token key required for sender's future
@@ -60,6 +66,28 @@ pub struct BitTorrentDHTError {
     pub msg: String,
 }
 
+#[derive(Debug, Eq, PartialEq)]
+pub struct Node {
+    pub id: Vec<u8>,
+    pub ip: Vec<u8>,
+    pub port: u16,
+}
+
+/// Parse IPv4 node structures.
+pub fn parse_node(i: &[u8]) -> IResult<&[u8], Node> {
+    let (i, id) = take(20usize)(i)?;
+    let (i, ip) = take(4usize)(i)?;
+    let (i, port) = be_u16(i)?;
+    Ok((
+        i,
+        Node {
+            id: id.to_vec(),
+            ip: ip.to_vec(),
+            port,
+        },
+    ))
+}
+
 impl FromBencode for BitTorrentDHTRequest {
     // Try to parse with a `max_depth` of one.
     //
@@ -172,10 +200,12 @@ impl FromBencode for BitTorrentDHTResponse {
                     id = value.try_into_bytes().context("id").map(Some)?;
                 }
                 (b"nodes", value) => {
-                    nodes = value
-                        .try_into_bytes()
-                        .context("nodes")
-                        .map(|v| Some(v.to_vec()))?;
+                    let (_, decoded_nodes) =
+                        nom7::multi::many0(parse_node)(value.try_into_bytes().context("nodes")?)
+                            .map_err(|_| Error::malformed_content("nodes.node"))?;
+                    if !decoded_nodes.is_empty() {
+                        nodes = Some(decoded_nodes);
+                    }
                 }
                 (b"values", value) => {
                     values = Vec::decode_bencode_object(value)
@@ -427,11 +457,11 @@ mod tests {
         "test response from bencode 4")]
     #[test_case(
         b"d2:id20:0123456789abcdefghij5:nodes9:def456...e",
-        BitTorrentDHTResponse { id: b"0123456789abcdefghij".to_vec(), token: None, values: None, nodes: Some(b"def456...".to_vec()) } ;
+        BitTorrentDHTResponse { id: b"0123456789abcdefghij".to_vec(), token: None, values: None, nodes: None } ;
         "test response from bencode 5")]
     #[test_case(
         b"d2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe",
-        BitTorrentDHTResponse { id: b"abcdefghij0123456789".to_vec(), token: Some(b"aoeusnth".to_vec()), values: None, nodes: Some(b"def456...".to_vec()) } ;
+        BitTorrentDHTResponse { id: b"abcdefghij0123456789".to_vec(), token: Some(b"aoeusnth".to_vec()), values: None, nodes: None } ;
         "test response from bencode 6")]
     fn test_response_from_bencode(encoded: &[u8], expected: BitTorrentDHTResponse) {
         let decoded = BitTorrentDHTResponse::from_bencode(encoded).unwrap();
@@ -599,4 +629,22 @@ mod tests {
         let err = parse_bittorrent_dht_packet(encoded, &mut tx).unwrap_err();
         assert_eq!(expected_error, err.to_string());
     }
+
+    #[test]
+    fn test_parse_node() {
+        let bytes = b"aaaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01";
+        let (_rem, node) = parse_node(bytes).unwrap();
+        assert_eq!(node.id, b"aaaaaaaaaaaaaaaaaaaa");
+        assert_eq!(node.ip, b"\x00\x00\x00\x00");
+        assert_eq!(node.port, 1);
+
+        // Short one byte.
+        let bytes = b"aaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01";
+        assert!(parse_node(bytes).is_err());
+
+        // Has remaining bytes.
+        let bytes = b"aaaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01bb";
+        let (rem, _node) = parse_node(bytes).unwrap();
+        assert_eq!(rem, b"bb");
+    }
 }