Chapter 5 How many UDP packets were sent from 192.168.1.26 to 24.39.217.246?

5.1 Objects in memory/packages loaded from preceding chapters

Objects available:

  • read_zeek_log() — helper function to read Zeek log files (Chapter 3)
  • packets — data frame of PCAP packet data (Chapter 4)

Packages:

  • {tidyverse}

5.2 Question Setup

We’ve been asked to determine how many UDP packets were sent from 192.168.1.26 to 24.39.217.246. You use UDP every day (well, your browsers/devices do) when you lookup website addresses (i.e. make traditional DNS queries) and even visit some websites since super fancy ones use HTTP/3 or QUIC protocols to speedup web sessions. This makes knowing how to find UDP information in packet captures a “must have” skill.

5.3 Solving the quest with tshark

The truth is that we don’t really need R to answer this question since tshark has a rich query filter language which lets us subset what we’re looking for by wide array of fields.

In this case we’re looking for an IP source address (ip.src) of 192.168.1.26 talking to an IP destination address (ip.dst) of 24.39.217.246 speaking the UDP (udp) network protocol:

system("tshark -r maze/maze.pcapng '(ip.src == 192.168.1.26) and (ip.dst == 24.39.217.246) and udp'", intern = TRUE)
##  [1] "15806 128.281136434 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"
##  [2] "15808 128.283606894 192.168.1.26 → 24.39.217.246 UDP 94 51601 → 54150 Len=52"
##  [3] "15825 130.239091258 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"
##  [4] "15851 132.241685345 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"
##  [5] "15865 135.324998370 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"
##  [6] "15942 137.223543961 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"
##  [7] "16095 139.223629695 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"
##  [8] "16695 143.331929739 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"
##  [9] "16810 145.217958711 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"
## [10] "16955 147.222253195 192.168.1.26 → 24.39.217.246 UDP 94 53638 → 54150 Len=52"

If you’re on a system with wc (word/char/line count utility — Windows folks can use WSL 2) you can pipe that output to said utility and end up with the value 10.

5.4 Solving the quest with R

5.4.1 Using the tshark packets data

We already have packets in memory and can use a {dplyr} chain with essentially the same query we used in tshark:

packets %>% 
  filter(
    src == "192.168.1.26", 
    dst == "24.39.217.246",
    proto == "UDP"
  ) %>% 
  count()
## # A tibble: 1 x 1
##       n
##   <int>
## 1    10

We can also use “classic R” idioms (along with the new, built-in/native pipe symbol |>) if we’re in a retro-ish mood:

packets |>
  subset(
    (src == "192.168.1.26") &
      (dst == "24.39.217.246") &
      (proto == "UDP")    
  ) |>
  nrow()
## [1] 10

5.4.2 Using Zeek conn.log data

We can arrive at the same answer by examining the Zeek conn.log data using a similar technique. The Zeek src/dst equivalents are id.orig_h/id.resp_h (proto is the same but the contents are lowercase), and Zeek’s conn.log has an orig_pkts field for each record which is the number of packets that the originator sent, which means we just need to sum those up to get our answer.

# read in the Zeek conn.log — this will now be in memory for future reference
(conn <- read_zeek_log("maze/conn.log"))
## # A tibble: 12,443 x 23
##         ts uid    id.orig_h id.orig_p id.resp_h id.resp_p proto service duration
##      <dbl> <chr>  <chr>         <dbl> <chr>         <dbl> <chr> <chr>   <chr>   
##  1  1.62e9 C27ow… 192.168.…     51754 173.223.…        80 tcp   -       0.000607
##  2  1.62e9 Ct11V… 192.168.…     36116 192.168.…        53 udp   dns     0.191884
##  3  1.62e9 ClVxP… 192.168.…     33066 13.107.2…       443 tcp   ssl     10.3804…
##  4  1.62e9 CZF8x… 192.168.…     52064 192.168.…        53 udp   dns     0.068408
##  5  1.62e9 CGOtA… 192.168.…     58432 192.168.…        53 udp   dns     0.081218
##  6  1.62e9 CiHtO… 192.168.…     45191 192.168.…        53 udp   dns     0.133955
##  7  1.62e9 CMY0w… 192.168.…     59660 192.168.…        53 udp   dns     0.097726
##  8  1.62e9 CBQkN… 192.168.…     33078 13.107.2…       443 tcp   -       0.138868
##  9  1.62e9 CHMrA… 192.168.…     54444 192.168.…        53 udp   dns     -       
## 10  1.62e9 CO7KH… 192.168.…     54764 173.223.…       443 tcp   -       0.151084
## # … with 12,433 more rows, and 14 more variables: orig_bytes <chr>,
## #   resp_bytes <chr>, conn_state <chr>, local_orig <chr>, local_resp <chr>,
## #   missed_bytes <dbl>, history <chr>, orig_pkts <dbl>, orig_ip_bytes <dbl>,
## #   resp_pkts <dbl>, resp_ip_bytes <dbl>, tunnel_parents <chr>,
## #   orig_l2_addr <chr>, resp_l2_addr <chr>
conn %>% 
  filter(
    (id.orig_h == "192.168.1.26"),
    (id.resp_h == "24.39.217.246"),
    (proto == "udp")
  ) %>% 
  count(
    wt = orig_pkts
  )
## # A tibble: 1 x 1
##       n
##   <dbl>
## 1    10