packetTap.sh 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #!/bin/bash
  2. shopt -s extglob
  3. # settings
  4. readonly LOGDIR='/var/log/tcpdump'
  5. # Name or location of main log file. If not a path, will be saved to LOGDIR
  6. MAINLOGFILE='main.log'
  7. ## File is reduced when this size is reached.
  8. readonly MAINLOGFILEMAXSIZE=$(( 20 * 1024 * 1024 )) ## bytes
  9. ## This is the extra space given when file is reduced.
  10. readonly MAINLOGFILEALLOWANCE=$(( 1 * 1024 * 1024 )) ## bytes
  11. ## Recommended: >= 300
  12. readonly MAINLOGCHECKINTERVALS=300 ## seconds
  13. # Location of tcpdump
  14. readonly TCPDUMP='/usr/sbin/tcpdump'
  15. ## customize arguments here e.g. (-C 1 "another with spaces")
  16. readonly MY_IP=$(host `hostname` | cut -d' ' -f4)
  17. # Arguments for tcpdump
  18. readonly TCPDUMPARGS=("(src $MY_IP and dst port 22) or
  19. (dst $MY_IP and src port 22)")
  20. # Capture log prefix
  21. readonly TCPDUMPCAPTUREFILEPREFIX='thesis-capture-'
  22. # Capture log suffix
  23. readonly TCPDUMPCAPTUREFILESUFFIX=''
  24. # Frequency to check that tcpdump is running
  25. readonly TCPDUMPCHECKINTERVALS=60 ## seconds
  26. # Frequency to try and re-run tcpdump if it failed
  27. readonly TCPDUMPTRYTIMEOUT=20 ## seconds
  28. # How long to keep log files around
  29. readonly OLD=14 ## days
  30. # How large of a block to copy at a time
  31. readonly DDBLOCKSIZE=512 ## bytes
  32. # Location for temporary files
  33. readonly TEMPDIR='/var/tmp'
  34. # other variables
  35. CURRENTDATE=''
  36. QUIT=false
  37. TCPDUMPPID=0
  38. # functions
  39. # Print to log file
  40. function log {
  41. echo "[$(date '+%F %T')] $1" >> "$MAINLOGFILE"
  42. echo "$1"
  43. }
  44. # Check if tcpdump process is running
  45. # Returns 1 if not found
  46. function checktcpdump {
  47. [[ $TCPDUMPPID -ne 0 ]] && [[ -e /proc/$TCPDUMPPID ]] &&
  48. kill -s 0 "$TCPDUMPPID" 2>/dev/null
  49. }
  50. # Creates a new log file for tcpdump and forks a new process
  51. # Returns 1 on error
  52. function starttcpdump {
  53. log "Starting tcpdump..."
  54. CURRENTDATE=$(date +%F)
  55. local BASENAME="${TCPDUMPCAPTUREFILEPREFIX}${CURRENTDATE}${TCPDUMPCAPTUREFILESUFFIX}"
  56. local -a EXISTINGFILES
  57. # Generate a list of all files matching pattern and put into array
  58. {
  59. if [[ BASH_VERSINFO -ge 4 ]]; then
  60. readarray -t EXISTINGFILES
  61. else
  62. EXISTINGFILES=()
  63. local -i I=0
  64. while read LINE; do
  65. EXISTINGFILES[I++]=$LINE
  66. done
  67. fi
  68. } < <(compgen -G "$LOGDIR/${BASENAME}.+([[:digit:]]).pcap*([[:digit:]])")
  69. # Finds the lowest log file number higher than all existing files
  70. local NEXT_SESSION=0
  71. if [[ ${#EXISTINGFILES[@]} -gt 0 ]]; then
  72. local SESSION_NUMBER
  73. for FILE in "${EXISTINGFILES[@]}"; do
  74. SESSION_NUMBER=${FILE%.pcap*}
  75. SESSION_NUMBER=${SESSION_NUMBER##*.}
  76. # If session_number is a gigit and >= next_session, then
  77. # next_session = session_number + 1
  78. [[ $SESSION_NUMBER == +([[:digit:]]) &&
  79. SESSION_NUMBER -ge NEXT_SESSION ]] &&
  80. NEXT_SESSION=$(( SESSION_NUMBER + 1 ))
  81. done
  82. fi
  83. # Forks tcpdump and checks that it succeeded
  84. local OUTPUTFILE=$LOGDIR/${BASENAME}.$NEXT_SESSION.pcap
  85. "$TCPDUMP" "${TCPDUMPARGS[@]}" -w "$OUTPUTFILE" &
  86. if [[ $? -ne 0 ]]; then
  87. TCPDUMPPID=0
  88. return 1
  89. fi
  90. TCPDUMPPID=$!
  91. disown "$TCPDUMPPID"
  92. checktcpdump
  93. }
  94. # Try to start tcpdump. If $QUIT, then exit after a failed attempt
  95. # otherwise try again
  96. function starttcpdumploop {
  97. until starttcpdump; do
  98. log "Error: Failed to start tcpdump. Waiting for $TCPDUMPTRYTIMEOUT seconds before next attempt..."
  99. read -t $TCPDUMPTRYTIMEOUT
  100. [[ $QUIT = true ]] && {
  101. log "Ending tcpdump manager script."
  102. exit
  103. }
  104. done
  105. }
  106. # Kill the current tcpdump process and set QUIT to true
  107. function stoptcpdump {
  108. log "Stopping tcpdump..."
  109. kill "$TCPDUMPPID"
  110. # If tcpdump is still running, sigkill it
  111. checktcpdump && kill -s 9 "$TCPDUMPPID"
  112. TCPDUMPPID=0
  113. QUIT=true
  114. }
  115. # Stop tcpdump and start it again
  116. function restarttcpdump {
  117. local TMPQUIT=$QUIT
  118. log "Restarting tcpdump..."
  119. checktcpdump && stoptcpdump
  120. QUIT=$TMPQUIT
  121. starttcpdumploop
  122. }
  123. # Sets QUIT to true
  124. function catchsignals {
  125. log "Caught a signal."
  126. QUIT=true
  127. }
  128. function main {
  129. local CAPTUREFILEPATTERN FILE NEWDATE SIZE TEMPFILE
  130. local -i I
  131. # Initializes variables and starts tcpdump child
  132. CAPTUREFILEPATTERN="${TCPDUMPCAPTUREFILEPREFIX}[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]${TCPDUMPCAPTUREFILESUFFIX}.pcap*"
  133. [[ $MAINLOGFILE != */* ]] && MAINLOGFILE=$LOGDIR/$MAINLOGFILE
  134. log "Starting tcpdump manager script..."
  135. [[ $MAINLOGFILEMAXSIZE == +([[:digit:]]) && MAINLOGFILEMAXSIZE -gt DDBLOCKSIZE ]] || {
  136. echo "MAINLOGFILEMAXSIZE is not valid."
  137. return 1
  138. }
  139. [[ $MAINLOGFILEALLOWANCE == +([[:digit:]]) && MAINLOGFILEALLOWANCE -gt DDBLOCKSIZE && MAINLOGFILEALLOWANCE -lt MAINLOGFILEMAXSIZE ]] || {
  140. echo "MAINLOGFILEALLOWANCE is not valid."
  141. return 1
  142. }
  143. trap catchsignals SIGQUIT SIGINT SIGKILL SIGTERM
  144. mkdir -p "$LOGDIR"
  145. starttcpdumploop
  146. #
  147. for (( I = 1;; I = (I + 1) % 10000 )); do
  148. # Wait 1 second
  149. ## we have to separate this from the next statement
  150. ## to ensure proper handling of signals
  151. read -t 1
  152. # If QUIT, exit loop
  153. [[ $QUIT = true ]] && break
  154. # Handle daily file rotations
  155. if (( (I % TCPDUMPCHECKINTERVALS) == 0 )); then
  156. NEWDATE=$(exec date +%F)
  157. if [[ ! $NEWDATE = "$CURRENTDATE" ]]; then
  158. log "A new day has come."
  159. # Delete files older than $OLD
  160. if read FILE; then
  161. log "Deleting $OLD-days old files..."
  162. while
  163. log "Deleting $FILE..."
  164. rm -f "$FILE"
  165. read FILE
  166. do
  167. continue
  168. done
  169. fi < <(exec find "$LOGDIR" -name "$CAPTUREFILEPATTERN" -daystart -ctime "+$OLD") # or -mtime?
  170. # Delete empty files
  171. find "$LOGDIR" -size 0 -delete
  172. restarttcpdump
  173. fi
  174. fi
  175. # Truncates main log from front if it exceeds maximum
  176. if (( (I % MAINLOGCHECKINTERVALS) == 0 )); then
  177. SIZE=$(exec stat --printf=%s "$MAINLOGFILE")
  178. if [[ $SIZE == +([[:digit:]]) && SIZE -gt MAINLOGFILEMAXSIZE ]]; then
  179. log "Reducing log data in $MAINLOGFILE..."
  180. TEMPFILE=$TEMPDIR/tcpdump-$RANDOM.tmp
  181. SKIP=$(( (SIZE - (MAINLOGFILEMAXSIZE - MAINLOGFILEALLOWANCE)) /
  182. DDBLOCKSIZE ))
  183. dd "bs=$DDBLOCKSIZE" "skip=$SKIP" "if=$MAINLOGFILE" "of=$TEMPFILE"
  184. ## better than mv
  185. cat "$TEMPFILE" > "$MAINLOGFILE"; rm -f "$TEMPFILE"
  186. fi
  187. fi
  188. done
  189. # Stop tcpdump
  190. checktcpdump && stoptcpdump
  191. log "Ending tcpdump manager script."
  192. }
  193. # start
  194. main