7

I'm using this script that actually checks for the rate of incoming packets and it gets triggered if the rate hits 5mbps or more. The packets are then logged to a tcpdump file.

interface=eth0
dumpdir=/tmp/

while /bin/true; do
  pkt_old=`grep $interface: /proc/net/dev | cut -d :  -f2 | awk '{ print $2 }'`
  sleep 1
  pkt_new=`grep $interface: /proc/net/dev | cut -d :  -f2 | awk '{ print $2 }'`

  pkt=$(( $pkt_new - $pkt_old ))
  echo -ne "\r$pkt packets/s\033[0K"

  if [ $pkt -gt 5000 ]; then
    echo -e "\n`date` Under attack, dumping packets."
    tcpdump -n -s0 -c 2000 -w $dumpdir/dump.`date +"%Y%m%d-%H%M%S"`.cap
    echo "`date` Packets dumped, sleeping now."
    sleep 300
  fi
done

The output is something like 2000 Packets captured. XXX packets recieved by Filter and XXX-(minus)2000 dropped by Kernel.

Now what I want to know here is that the output file wouldn't actually tell me the rate of the attack like if it was 300mbps or what? So is the XXX packets recieved by filter is per second? If not, how do I check that because my port sometimes gets saturated.

UPDATE:

I used a program to capture statistics from the captured file through the above script. Here is what I got:

root@$:/tmp/dumps# capinfos dump.20130621-174506.cap
File name:           dump.20130621-174506.cap
File type:           Wireshark/tcpdump/... - libpcap
File encapsulation:  Linux cooked-mode capture
Number of packets:   2000
File size:           2065933 bytes
Data size:           2033909 bytes
Capture duration:    43 seconds
Start time:          Fri Jun 21 17:45:06 2013
End time:            Fri Jun 21 17:45:49 2013
Data byte rate:      46968.49 bytes/sec
Data bit rate:       375747.94 bits/sec
Average packet size: 1016.95 bytes
Average packet rate: 46.19 packets/sec

I believe the attack might only have lasted a good 15-20 seconds while the captured information was 43 seconds so the data-bit rate here might have been averaged over from this total time. What might help here is if someone could edit the original script above instead of capturing 2000 packets and dropping the rest, to capture all packets for a duration of lets say 5 seconds when the threshold hits.

UPDATE:

After changing the script as mentioned, it looked like the file was damaged as I read it in Wireshark which said "The capture file appears to have been cut short in the middle of a packet." Here is output from capinfos:

capinfos: An error occurred after reading 3085 packets from `"dump.20130710-215413.cap": Less data was read than was expected.

On a 2nd try, I was able to read that file only when I pressed Ctrl+C in console of the script:

capinfos dump.20130710-215413.cap
File name:           dump.20130710-215413.cap
File type:           Wireshark/tcpdump/... - libpcap
File encapsulation:  Linux cooked-mode capture
Number of packets:   18136
File size:           2600821 bytes
Data size:           2310621 bytes
Capture duration:    591 seconds
Start time:          Wed Jul 10 21:54:13 2013
End time:            Wed Jul 10 22:04:04 2013
Data byte rate:      3909.73 bytes/sec
Data bit rate:       31277.83 bits/sec
Average packet size: 127.41 bytes
Average packet rate: 30.69 packets/sec

Notice capture duration 591 seconds. I believe the 'sleep 300' has something to do over here because as I see the console output. This output is with '-c 2000' option:

./Log.sh
10275 packets/s
Wed Jul 10 12:41:31 MSD 2013 Under attack, dumping packets.
tcpdump: listening on venet0, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
2000 packets captured
100012 packets received by filter
98003 packets dropped by kernel
Wed Jul 10 12:42:34 MSD 2013 Packets dumped, sleeping now.

Now this is the output after you modified the script with 'sleep 5':

./Log.sh
24103 packets/s
Wed Jul 10 21:54:13 MSD 2013 Under attack, dumping packets.
tcpdump: listening on venet0, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
Wed Jul 10 21:54:18 MSD 2013 Packets dumped, sleeping now.
1620 packets/sroot@nl:~# 18136 packets captured
1850288 packets received by filter
1832106 packets dropped by kernel
^C

Notice I pressed Ctrl+C to break the sleep function I guess which made reading of the file possible.

Asad Moeen
  • 437
  • 3
  • 11
  • 22
  • 2
    Why are you using `tcpdump` - just look at the interface statistics itself. Or use [`iptables`](http://stackoverflow.com/questions/349576/linux-retrieve-per-interface-sent-received-packet-counters-ethernet-ipv4-ipv), or [`tcpstat`](http://linux.die.net/man/1/tcpstat) or [`netstat` or `nicstat`](http://www.cyberciti.biz/hardware/linux-install-nicstat-command-to-print-network-statistics-for-nics/) – choco-loo Jul 05 '13 at 11:24
  • agreed - tcpdump is a great tool for its purpose, but using a screwdriver as a hammer will rarely yield good results. – Jenny D Jul 05 '13 at 12:18
  • I use tcpdump to make firewall rules when I'm attacked. But I just thought I could use it to measure the size of the attack as well. I only want to know the size when it's above 10mbps. – Asad Moeen Jul 05 '13 at 15:21
  • updated my answer. – quanta Jul 10 '13 at 10:12
  • I ran into an error with that script. Updated the question! – Asad Moeen Jul 10 '13 at 18:00

3 Answers3

7

capinfos is what you are looking for:

$ capinfos ddos.cap
File name:           ddos.cap
File type:           Wireshark/tcpdump/... - libpcap
File encapsulation:  Ethernet
Packet size limit:   file hdr: 65535 bytes
Number of packets:   1000000
File size:           189073212 bytes
Data size:           173073188 bytes
Capture duration:    2 seconds
Start time:          Fri Jul  5 16:35:04 2013
End time:            Fri Jul  5 16:35:07 2013
Data byte rate:      69839025.27 bytes/sec
Data bit rate:       558712202.18 bits/sec
Average packet size: 173.07 bytes
Average packet rate: 403523.08 packets/sec
SHA1:                34d758e6445061855ca4397729098f469f411fe3
RIPEMD160:           14f430231fc2962cd86ddb8edb8daf75a5d07af8
MD5:                 5893809fb02d1a20997629a9a501842b
Strict time order:   False

Pay attention to the Data bit rate.


What might help here is if someone could edit the original script above instead of capturing 2000 packets and dropping the rest, to capture all packets for a duration of lets say 5 seconds when the threshold hits.

How about this:

tcpdump -n -s0 -w $dumpdir/dump.`date +"%Y%m%d-%H%M%S"`.cap &
sleep 5 && pkill -HUP -f /usr/sbin/tcpdump
quanta
  • 51,413
  • 19
  • 159
  • 217
  • Thank you and that data bit rate would be the overall rate of all the packets? – Asad Moeen Jul 10 '13 at 07:43
  • Yes, it is the overall. – quanta Jul 10 '13 at 08:48
  • Okay I've tried this and noticed a bit of an issue. The script I used above captures packets when threshold is greater than 5mb/s so now when I run capinfo on most of my cap files, I see the duration part of about "40-60 seconds" on each of the files while I believe the attack only existed for a mere 10-15 seconds. So the data-bit rate for almost all the files is around 0.2 to 0.5mb/s which is even lower than the above script's threshold. It may have to do with the capture duration so it averaged the number. – Asad Moeen Jul 10 '13 at 08:52
  • Edit your original question and show us an example result. – quanta Jul 10 '13 at 08:54
  • Updated in the original question. – Asad Moeen Jul 10 '13 at 09:54
0

How to measure the packet rate (frequency) using tcpdump and nano-second-resolution timestamps

Quick Summary:

For both options shown below, update the num_packets values and the tcpdump commands is all, in these "one-liner" command blobs:

Option 1:

This always works and is pretty good:

num_packets="25"; \
time_start_sec="$(date +"%s.%N")"; \
time sudo tcpdump -i any -c "$num_packets" \
    "(src 127.0.0.1 and port 40000) and (dst 127.0.0.1 and port 40001)"; \
time_end_sec="$(date +"%s.%N")"; \
dt_sec="$(bc <<< "scale=20; $time_end_sec - $time_start_sec")"; \
packet_rate_hz="$(bc <<< "scale=20; $num_packets / $dt_sec")"; \
printf "\n%s%.3f\n\n" "packet_rate_hz = " "$packet_rate_hz"

Option 2 [best]:

This works with most, if not all, tcpdump commands which do not include the -t flag, and is much better!:

num_packets="10"; \
time sudo tcpdump -l -i any -c "$num_packets" \
    "(src 127.0.0.1 and port 40000) and (dst 127.0.0.1 and port 40001)" \
    | tee tcpdump.txt; \
time_start_sec="$(grep -o \
    '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9]*' \
    "tcpdump.txt" \
    | head -n 1 | grep -o '[0-9][0-9]\.[0-9]*')"; \
time_end_sec="$(grep -o \
    '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9]*' \
    "tcpdump.txt" \
    | tail -n 1 | grep -o '[0-9][0-9]\.[0-9]*')"; \
dt_sec="$(bc <<< "scale=20; $time_end_sec - $time_start_sec")"; \
packet_rate_hz="$(bc <<< "scale=20; ($num_packets-1) / $dt_sec")"; \
printf "\n%s%.4f\n\n" "packet_rate_hz = " "$packet_rate_hz"

Option 1: manually time the whole process using nanosecond-resolution timestamps from date

This works ok. I simply tell tcpdump to grab a certain number of packets, time the whole thing, and calculate the packet rate:

num_packets="25"; \
time_start_sec="$(date +"%s.%N")"; \
time sudo tcpdump -i any -c "$num_packets" \
    "(src 127.0.0.1 and port 40000) and (dst 127.0.0.1 and port 40001)"; \
time_end_sec="$(date +"%s.%N")"; \
dt_sec="$(bc <<< "scale=20; $time_end_sec - $time_start_sec")"; \
packet_rate_hz="$(bc <<< "scale=20; $num_packets / $dt_sec")"; \
printf "\n%s%.3f\n\n" "packet_rate_hz = " "$packet_rate_hz"

It's a bash "one-liner". Copy and paste the whole chunk at once. Be sure to update the num_packets variable first to how many packets you want to capture, and update your tcpdump line to filter on just your packets of interest. In other words, modify these 2 lines for your needs:

num_packets="25"; \

time sudo tcpdump -i any -c "$num_packets" \
    "(src 127.0.0.1 and port 40000) and (dst 127.0.0.1 and port 40001)"; \

It will print out the packet rate at the end, like this (where the true packet rate is 10.00 Hz):

packet_rate_hz = 9.418

Note that due to some overhead of tcpdump as is starts up, the calculated frequency shown (9.698 Hz in this case) is skewed a bit low. I know its actual frequency is 10.00 Hz (yes, >2 zeros of precision), since I wrote the code which sends the packets and used precision timing to do so--see my answer here for the timing part of that code: How to run a high-resolution, high-precision periodic loop in Linux easily, at any frequency (ex: up to 10 KHz~100 KHz) using a soft real-time scheduler and nanosecond delays

So, for Option 1, using larger packet counts (setting num_packets to a larger value, such as 25 to 50 or more) is better because it reduces the error introduced by the overhead of starting up tcpdump, which we are including in our timing, but don't want to.

Example full command and output on real data:

$ num_packets="25"; \
> time_start_sec="$(date +"%s.%N")"; \
> time sudo tcpdump -i any -c "$num_packets" \
>     "(src 127.0.0.1 and port 40000) and (dst 127.0.0.1 and port 40001)"; \
> time_end_sec="$(date +"%s.%N")"; \
> dt_sec="$(bc <<< "scale=20; $time_end_sec - $time_start_sec")"; \
> packet_rate_hz="$(bc <<< "scale=20; $num_packets / $dt_sec")"; \
> printf "\n%s%.3f\n" "packet_rate_hz = " "$packet_rate_hz"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
11:58:36.181547 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:36.281798 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:36.381547 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:36.481842 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:36.581581 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:36.681724 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:36.781734 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:36.881735 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:36.981637 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.081633 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.181585 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.281661 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.381456 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.481732 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.581709 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.681696 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.781710 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.881603 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:37.981751 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:38.081860 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:38.181669 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:38.281727 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:38.381457 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:38.481574 IP localhost.40000 > localhost.40001: UDP, length 44
11:58:38.581724 IP localhost.40000 > localhost.40001: UDP, length 44
25 packets captured
50 packets received by filter
0 packets dropped by kernel

real    0m2.651s
user    0m0.016s
sys 0m0.017s

packet_rate_hz = 9.418

Option 2 [BEST--USE THIS WHEN POSSIBLE INSTEAD!]: extract the first and last packet timestamps from the tcpdump output and use those to determine the packet rate

For this technique, we need to do a few things:

  1. First off, always use the -l (dash lower-case L, NOT dash one) flag with tcpdump here to make the stdout output line buffered so that you will see it periodically update while the script is running. If you don't use this flag, you'll only see the output once at the very end of the tcpdump command if the output is short, or in huge periodic chunks each time the stdout buffer totally fills up if the output is long.
  2. Pipe the output to tee so that it A) saves it into a file you can extract the timestamps from afterwards and B) prints it to stdout so you can see it live.

Here is the "one-liner". Note that the time part is optional. It just prints out a total time at the end, which I think is nice. Again, update the num_packets value and the tcpdump command for your needs. The minimum num_packets value you can use is 2. This technique has such good accuracy that you do not need to use large num_packets values to make it work well. Collecting as few as 2 packets produces good results, and 10 packets produces excellent results.

num_packets="10"; \
time sudo tcpdump -l -i any -c "$num_packets" \
    "(src 127.0.0.1 and port 40000) and (dst 127.0.0.1 and port 40001)" \
    | tee tcpdump.txt; \
time_start_sec="$(grep -o \
    '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9]*' \
    "tcpdump.txt" \
    | head -n 1 | grep -o '[0-9][0-9]\.[0-9]*')"; \
time_end_sec="$(grep -o \
    '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9]*' \
    "tcpdump.txt" \
    | tail -n 1 | grep -o '[0-9][0-9]\.[0-9]*')"; \
dt_sec="$(bc <<< "scale=20; $time_end_sec - $time_start_sec")"; \
packet_rate_hz="$(bc <<< "scale=20; ($num_packets-1) / $dt_sec")"; \
printf "\n%s%.4f\n\n" "packet_rate_hz = " "$packet_rate_hz"

Again, modify these 2 lines (shown below) for your needs, keeping these things in mind:

  1. For the tcpdump line, be sure to keep -l and | tee tcpdump.txt, as described above.
  2. Also, using -t with tcpdump means "Don't print a timestamp on each dump line.", and if used, will cause this technique to not work since it relies on those timestamps. Option 1 above will still work just fine, however.
  3. If you are working on an embedded Linux board, consider the following:
    1. It may have a mostly read-only file-system, in which case you will need to force the tee output into a file in a writable part of your file system, such as perhaps /var/log/tcpdump.txt or /tmp/tcpdump.txt instead of just tcpdump.txt in your current directory.
      1. If no part of your filesystem is writable by default, then you may need to unmount it and remount it as read-writable (rw) first, as shown below. Then, run the tcpdump command above. When done, you can mount it back as read-only (ro) as shown here:
        # Remount your root dir (/) as read-writable (rw)
        mount -o remount,rw /
        # Remount your root dir (/) as read-only (ro)
        mount -o remount,ro /
        
    2. Your embedded Linux board may not have the sudo command, in which case you'll need to ssh or log into the board as root directly, and also remove the sudo word from the tcpdump command above.
num_packets="10"; \

time sudo tcpdump -l -i any -c "$num_packets" \
    "(src 127.0.0.1 and port 40000) and (dst 127.0.0.1 and port 40001)" \
    | tee tcpdump.txt; \

Example packet rate printed at the end (where the true packet rate is 10.00 Hz):

packet_rate_hz = 10.0003

Example full command and output on real data. Note that when line-buffering the output with -l, the stderr output, such as the lines which indicate how many packets were captured, received, and dropped, get accidentally intermingled with the rest of the stdout output. That's ok. It's non-ideal, but there's nothing we can do about it. So, it's normal.

$ num_packets="10"; \
> time sudo tcpdump -l -i any -c "$num_packets" \
>     "(src 127.0.0.1 and port 40000) and (dst 127.0.0.1 and port 40001)" \
>     | tee tcpdump.txt; \
> time_start_sec="$(grep -o \
>     '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9]*' \
>     "tcpdump.txt" \
>     | head -n 1 | grep -o '[0-9][0-9]\.[0-9]*')"; \
> time_end_sec="$(grep -o \
>     '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9]*' \
>     "tcpdump.txt" \
>     | tail -n 1 | grep -o '[0-9][0-9]\.[0-9]*')"; \
> dt_sec="$(bc <<< "scale=20; $time_end_sec - $time_start_sec")"; \
> packet_rate_hz="$(bc <<< "scale=20; ($num_packets-1) / $dt_sec")"; \
> printf "\n%s%.4f\n\n" "packet_rate_hz = " "$packet_rate_hz"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
11:59:35.481669 IP localhost.40000 > localhost.40001: UDP, length 44
11:59:35.581440 IP localhost.40000 > localhost.40001: UDP, length 44
11:59:35.681468 IP localhost.40000 > localhost.40001: UDP, length 44
11:59:35.781661 IP localhost.40000 > localhost.40001: UDP, length 44
11:59:35.881649 IP localhost.40000 > localhost.40001: UDP, length 44
10 packets captured
28 packets received by filter
0 packets dropped by kernel
11:59:35.981540 IP localhost.40000 > localhost.40001: UDP, length 44
11:59:36.081669 IP localhost.40000 > localhost.40001: UDP, length 44
11:59:36.181423 IP localhost.40000 > localhost.40001: UDP, length 44
11:59:36.281595 IP localhost.40000 > localhost.40001: UDP, length 44
11:59:36.381638 IP localhost.40000 > localhost.40001: UDP, length 44

real    0m1.098s
user    0m0.021s
sys 0m0.009s

packet_rate_hz = 10.0003

Option 1 vs Option 2

If Option 2 above is so much better and produces so much better results, why would we ever use Option 1?

Well, if you ever use the -t flag with tcpdump, man tcpdump indicates that it means:

-t     Don't print a timestamp on each dump line.

So, Option 2 clearly doesn't work if you use that flag, since it relies on those timestamps. But, Option 1 would still work just fine there!

If any other scenarios or possible tcpdump commands come up which don't work well with Option 2, you can use Option 1, which always works. But, whenever possible, use Option 2, as it produces much more accurate and precise output!

References

  1. To help me come up with my regular expressions in grep: https://regex101.com/
Gabriel Staples
  • 225
  • 1
  • 6
0

Count number of packets per second:

tcpdump -tttt -r dump.pcap | awk '{ print $2 }' | cut --delimiter='.' --fields=1 | uniq -c

This filters by the time column, and removes the microseconds component, grouping by the receive time.

Output:

      1 21:12:35
      3 21:12:39
    567 21:12:40
Bob
  • 113
  • 2