@@ -283,3 +283,198 @@ fn exporter_as_addresses(peer: IpAddr) -> Vec<Address> {
283283 dst_addr: None ,
284284 } ]
285285}
286+
287+ #[ cfg( test) ]
288+ mod tests {
289+ use super :: * ;
290+ use crate :: networking:: types:: data_representation:: DataRepr ;
291+ use crate :: networking:: types:: traffic_direction:: TrafficDirection ;
292+ use std:: net:: { Ipv4Addr , Ipv6Addr } ;
293+
294+ /// The field specifiers `sniffnet-agent` puts in its templates, after the
295+ /// two address fields that differ between the IPv4 and IPv6 variants.
296+ const AGENT_COMMON_FIELDS : [ ( u16 , u16 ) ; 10 ] = [
297+ ( 7 , 2 ) ,
298+ ( 11 , 2 ) ,
299+ ( 4 , 1 ) ,
300+ ( 56 , 6 ) ,
301+ ( 80 , 6 ) ,
302+ ( 61 , 1 ) ,
303+ ( 352 , 8 ) ,
304+ ( 2 , 8 ) ,
305+ ( 152 , 8 ) ,
306+ ( 153 , 8 ) ,
307+ ] ;
308+
309+ fn agent_fields ( addr_fields : [ ( u16 , u16 ) ; 2 ] ) -> Vec < ( u16 , u16 ) > {
310+ addr_fields. into_iter ( ) . chain ( AGENT_COMMON_FIELDS ) . collect ( )
311+ }
312+
313+ fn set ( set_id : u16 , body : & [ u8 ] ) -> Vec < u8 > {
314+ let mut out = set_id. to_be_bytes ( ) . to_vec ( ) ;
315+ out. extend_from_slice ( & u16:: try_from ( body. len ( ) + 4 ) . unwrap ( ) . to_be_bytes ( ) ) ;
316+ out. extend_from_slice ( body) ;
317+ out
318+ }
319+
320+ fn template_set ( template_id : u16 , fields : & [ ( u16 , u16 ) ] ) -> Vec < u8 > {
321+ let mut body = template_id. to_be_bytes ( ) . to_vec ( ) ;
322+ body. extend_from_slice ( & u16:: try_from ( fields. len ( ) ) . unwrap ( ) . to_be_bytes ( ) ) ;
323+ for ( ie, len) in fields {
324+ body. extend_from_slice ( & ie. to_be_bytes ( ) ) ;
325+ body. extend_from_slice ( & len. to_be_bytes ( ) ) ;
326+ }
327+ set ( wire:: SET_ID_TEMPLATE , & body)
328+ }
329+
330+ /// Message header plus the given sets, with the length backfilled.
331+ fn datagram ( sets : & [ Vec < u8 > ] ) -> Vec < u8 > {
332+ let mut out = IPFIX_VERSION . to_be_bytes ( ) . to_vec ( ) ;
333+ out. extend_from_slice ( & [ 0 , 0 ] ) ; // length placeholder
334+ out. extend_from_slice ( & [ 0 ; 4 ] ) ; // export time
335+ out. extend_from_slice ( & [ 0 ; 4 ] ) ; // sequence number
336+ out. extend_from_slice ( & [ 0 ; 4 ] ) ; // observation domain
337+ for s in sets {
338+ out. extend_from_slice ( s) ;
339+ }
340+ let len = u16:: try_from ( out. len ( ) ) . unwrap ( ) . to_be_bytes ( ) ;
341+ out[ 2 ] = len[ 0 ] ;
342+ out[ 3 ] = len[ 1 ] ;
343+ out
344+ }
345+
346+ /// Record tail shared by both address families, in the agent's field order.
347+ fn record_tail ( bytes : u64 , packets : u64 ) -> Vec < u8 > {
348+ let mut r = Vec :: new ( ) ;
349+ r. extend_from_slice ( & 443u16 . to_be_bytes ( ) ) ; // source port
350+ r. extend_from_slice ( & 50_000u16 . to_be_bytes ( ) ) ; // destination port
351+ r. push ( 6 ) ; // TCP
352+ r. extend_from_slice ( & [ 0xAA ; 6 ] ) ; // source MAC
353+ r. extend_from_slice ( & [ 0 ; 6 ] ) ; // destination MAC: not observed
354+ r. push ( 0x00 ) ; // flowDirection: ingress
355+ r. extend_from_slice ( & bytes. to_be_bytes ( ) ) ;
356+ r. extend_from_slice ( & packets. to_be_bytes ( ) ) ;
357+ r. extend_from_slice ( & 20_000u64 . to_be_bytes ( ) ) ; // flow start: 20s
358+ r. extend_from_slice ( & 25_000u64 . to_be_bytes ( ) ) ; // flow end: 25s
359+ r
360+ }
361+
362+ fn run ( bytes : & [ u8 ] ) -> ( InfoTraffic , AddressesResolutionState ) {
363+ let mut templates = TemplateCache :: new ( ) ;
364+ let mut info = InfoTraffic :: default ( ) ;
365+ let mut resolutions = AddressesResolutionState :: new_detached ( ) ;
366+ process_datagram (
367+ bytes,
368+ "203.0.113.9:4739" . parse ( ) . unwrap ( ) ,
369+ & mut templates,
370+ & mut info,
371+ & IpBlacklist :: default ( ) ,
372+ & mut resolutions,
373+ ) ;
374+ ( info, resolutions)
375+ }
376+
377+ /// A template set plus a one-record data set, shaped exactly as the agent
378+ /// emits them.
379+ fn agent_datagram (
380+ template_id : u16 ,
381+ addr_fields : [ ( u16 , u16 ) ; 2 ] ,
382+ addrs : & [ u8 ] ,
383+ bytes : u64 ,
384+ packets : u64 ,
385+ ) -> Vec < u8 > {
386+ let mut record = addrs. to_vec ( ) ;
387+ record. extend_from_slice ( & record_tail ( bytes, packets) ) ;
388+ datagram ( & [
389+ template_set ( template_id, & agent_fields ( addr_fields) ) ,
390+ set ( template_id, & record) ,
391+ ] )
392+ }
393+
394+ #[ test]
395+ fn decodes_an_agent_shaped_ipv4_datagram ( ) {
396+ let mut addrs = Ipv4Addr :: new ( 10 , 0 , 0 , 1 ) . octets ( ) . to_vec ( ) ;
397+ addrs. extend_from_slice ( & Ipv4Addr :: new ( 8 , 8 , 8 , 8 ) . octets ( ) ) ;
398+ let ( info, resolutions) = run ( & agent_datagram ( 256 , [ ( 8 , 4 ) , ( 12 , 4 ) ] , & addrs, 1500 , 10 ) ) ;
399+
400+ let key = AddressPortPair {
401+ source : IpAddr :: V4 ( Ipv4Addr :: new ( 10 , 0 , 0 , 1 ) ) ,
402+ sport : Some ( 443 ) ,
403+ dest : IpAddr :: V4 ( Ipv4Addr :: new ( 8 , 8 , 8 , 8 ) ) ,
404+ dport : Some ( 50_000 ) ,
405+ protocol : Protocol :: TCP ,
406+ } ;
407+ let entry = info. map . get ( & key) . expect ( "flow present" ) ;
408+ assert_eq ! ( entry. transmitted_bytes, 1500 ) ;
409+ assert_eq ! ( entry. transmitted_packets, 10 ) ;
410+ // flowDirection 0x00 is ingress, and it overrides any address guess
411+ assert_eq ! ( entry. traffic_direction, TrafficDirection :: Incoming ) ;
412+ assert_eq ! ( entry. mac_address1, Some ( "aa:aa:aa:aa:aa:aa" . to_string( ) ) ) ;
413+ assert_eq ! ( entry. mac_address2, None , "all-zero MAC means not observed" ) ;
414+ assert_eq ! ( entry. initial_timestamp, Timestamp :: new( 20 , 0 ) ) ;
415+ assert_eq ! ( entry. final_timestamp, Timestamp :: new( 25 , 0 ) ) ;
416+
417+ assert_eq ! ( info. tot_data_info. tot_data( DataRepr :: Bytes ) , 1500 ) ;
418+ assert_eq ! ( info. tot_data_info. tot_data( DataRepr :: Packets ) , 10 ) ;
419+ assert_eq ! ( info. services. len( ) , 1 ) ;
420+ // no rDNS threads are running, so the address is left awaiting lookup
421+ assert_eq ! ( resolutions. addresses_waiting_resolution. len( ) , 1 ) ;
422+ assert ! ( info. hosts. is_empty( ) ) ;
423+ }
424+
425+ #[ test]
426+ fn decodes_an_agent_shaped_ipv6_datagram ( ) {
427+ let src = Ipv6Addr :: new ( 0x2001 , 0xdb8 , 0 , 0 , 0 , 0 , 0 , 1 ) ;
428+ let dst = Ipv6Addr :: new ( 0x2001 , 0xdb8 , 0 , 0 , 0 , 0 , 0 , 2 ) ;
429+ let mut addrs = src. octets ( ) . to_vec ( ) ;
430+ addrs. extend_from_slice ( & dst. octets ( ) ) ;
431+ let ( info, _) = run ( & agent_datagram ( 257 , [ ( 27 , 16 ) , ( 28 , 16 ) ] , & addrs, 800 , 4 ) ) ;
432+
433+ let key = AddressPortPair {
434+ source : IpAddr :: V6 ( src) ,
435+ sport : Some ( 443 ) ,
436+ dest : IpAddr :: V6 ( dst) ,
437+ dport : Some ( 50_000 ) ,
438+ protocol : Protocol :: TCP ,
439+ } ;
440+ let entry = info. map . get ( & key) . expect ( "flow present" ) ;
441+ assert_eq ! ( entry. transmitted_bytes, 800 ) ;
442+ assert_eq ! ( entry. transmitted_packets, 4 ) ;
443+ }
444+
445+ #[ test]
446+ fn record_without_counters_is_skipped ( ) {
447+ let mut addrs = Ipv4Addr :: new ( 10 , 0 , 0 , 1 ) . octets ( ) . to_vec ( ) ;
448+ addrs. extend_from_slice ( & Ipv4Addr :: new ( 8 , 8 , 8 , 8 ) . octets ( ) ) ;
449+ let ( info, _) = run ( & agent_datagram ( 256 , [ ( 8 , 4 ) , ( 12 , 4 ) ] , & addrs, 0 , 0 ) ) ;
450+
451+ assert ! ( info. map. is_empty( ) , "no traffic to account for" ) ;
452+ assert_eq ! ( info. tot_data_info. tot_data( DataRepr :: Packets ) , 0 ) ;
453+ }
454+
455+ #[ test]
456+ fn data_set_without_a_known_template_is_skipped ( ) {
457+ // Data referencing template 256 before any template set has arrived.
458+ let bytes = datagram ( & [ set ( 256 , & [ 0xAA ; 58 ] ) ] ) ;
459+ let ( info, _) = run ( & bytes) ;
460+ assert ! ( info. map. is_empty( ) ) ;
461+ }
462+
463+ #[ test]
464+ fn trailing_padding_does_not_produce_an_extra_record ( ) {
465+ let mut addrs = Ipv4Addr :: new ( 10 , 0 , 0 , 1 ) . octets ( ) . to_vec ( ) ;
466+ addrs. extend_from_slice ( & Ipv4Addr :: new ( 8 , 8 , 8 , 8 ) . octets ( ) ) ;
467+ let mut record = addrs;
468+ record. extend_from_slice ( & record_tail ( 1500 , 10 ) ) ;
469+ record. extend_from_slice ( & [ 0 ; 3 ] ) ; // pad to a 4-byte boundary
470+
471+ let bytes = datagram ( & [
472+ template_set ( 256 , & agent_fields ( [ ( 8 , 4 ) , ( 12 , 4 ) ] ) ) ,
473+ set ( 256 , & record) ,
474+ ] ) ;
475+ let ( info, _) = run ( & bytes) ;
476+
477+ assert_eq ! ( info. map. len( ) , 1 ) ;
478+ assert_eq ! ( info. tot_data_info. tot_data( DataRepr :: Packets ) , 10 ) ;
479+ }
480+ }
0 commit comments