eibgrad

ddwrt-dns-monitor.sh

Apr 2nd, 2022 (edited)
304
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/bin/sh
  2. # version: 1.1.0, 26-apr-2022, by eibgrad
  3. # href: https://tinyurl.com/335ee4hb
  4.  
  5. SCRIPTS_DIR='/tmp'
  6. SCRIPT="$SCRIPTS_DIR/ddwrt-dns-monitor.sh"
  7.  
  8. mkdir -p $SCRIPTS_DIR
  9.  
  10. # ------------------------------ BEGIN OPTIONS ------------------------------- #
  11.  
  12. # how often (in secs) to update display (min 3, max 30)
  13. INTERVAL=6
  14.  
  15. # uncomment/comment to enable/disable logging of Do53 connections over WAN
  16. #LOGGING=
  17.  
  18. # ------------------------------- END OPTIONS -------------------------------- #
  19.  
  20. # ---------------------- DO NOT CHANGE BELOW THIS LINE ----------------------- #
  21.  
  22. cat << 'EOF' > $SCRIPT
  23. #!/bin/sh
  24.  
  25. BNAME="$(basename $0 .sh)"
  26.  
  27. # display color attributes
  28. RED='\033[0;31m'
  29. YELLOW='\033[0;33m'
  30. GREEN='\033[0;32m'
  31.  
  32. # display monochrome attributes
  33. NORMAL='\033[0m'
  34. BOLD='\033[1m'
  35. ITALIC='\033[3m'
  36. UNDERLINE='\033[4m'
  37.  
  38. # display command menu attributes (color and monochrome)
  39. CMENU='\033[7;36m' # reverse + cyan
  40. MMENU='\033[7m'    # reverse only
  41.  
  42. # display reset attribute
  43. RS='\033[0m'
  44.  
  45. # display update interval boundaries
  46. MIN_INTERVAL=3
  47. MAX_INTERVAL=30
  48.  
  49. # work files
  50. HEAD="/tmp/tmp.$$.$BNAME.head"
  51. BODY="/tmp/tmp.$$.$BNAME.body"
  52. DATA="/tmp/tmp.$$.$BNAME.data"
  53. LOG="/tmp/tmp.$$.$BNAME.log"; > $LOG
  54.  
  55. # function min_str( str1 str2 )
  56. min_str() { [ "$1" ] && echo "$1" || echo "$2"; }
  57.  
  58. # function max_val( val1 val2 )
  59. max_val() { echo $(($1 < $2 ? $2 : $1)); }
  60.  
  61. # function min_val( val1 val2 )
  62. min_val() { echo $(($1 > $2 ? $2 : $1)); }
  63.  
  64. # function format_header()
  65. format_header() {
  66.     local ip dns spbr dpbr sw_vpn_printed
  67.  
  68.     # publish wan/lan ip information
  69.     printf "WAN/LAN IP: $wan_ip_4disp/$(nvram get lan_ipaddr)\n\n"
  70.  
  71.     # publish wan dns information
  72.     printf " WAN DNS: $(echo $(awk '/^nameserver /{print $2}' \
  73.        /tmp/resolv.conf) | sed -r 's/ /, /g')\n"
  74.  
  75.     # publish dhcp dns information
  76.     if grep -Eq '^no-resolv( |$)' /tmp/dnsmasq.conf; then
  77.         printf "DHCP DNS: $(echo $(awk -F'[= ]' '/^server=/{print $2}' \
  78.            /tmp/dnsmasq.conf) | sed -r 's/ /, /g')\n"
  79.     else
  80.         printf "DHCP DNS: $(echo $(awk '/^nameserver /{print $2}' \
  81.            /tmp/resolv.dnsmasq) | sed -r 's/ /, /g')\n"
  82.     fi
  83.  
  84.     # publish dnscrypt dns information
  85.     [ "$doc_ip" ] && printf " DoC DNS: $doc_ip:$doc_port\n"
  86.  
  87.     # publish openvpn information
  88.     if [ "$(nvram get openvpncl_enable)" == "1" ]; then
  89.         ip="$(ifconfig tun1 2>/dev/null | \
  90.            awk '/inet addr/{split ($2,A,":"); print A[2]}')"
  91.  
  92.         case $(nvram get openvpncl_spbr) in
  93.                 '0') spbr='All via VPN';;
  94.                 '1') spbr='Selected via VPN';;
  95.                 '2') spbr='Selected via WAN';;
  96.                   *) if [ "$(nvram get openvpncl_route)" ]; then
  97.                          spbr='Selected via VPN'
  98.                      else
  99.                          spbr='All via VPN'
  100.                      fi;;
  101.         esac
  102.  
  103.         printf "\nOpenVPN IP/SPBR: $(min_str $ip 0.0.0.0)/$spbr\n"
  104.     fi
  105.  
  106.     local i=0; local tunnels=$(nvram get oet_tunnels)
  107.  
  108.     # publish wireguard information
  109.     while [ $((++i)) -le $tunnels ]; do
  110.  
  111.         [ "$(nvram get oet${i}_en)" == "1" ] || continue
  112.  
  113.         ip="$(nvram get oet${i}_ipaddr)"
  114.         dns="$(nvram get oet${i}_dns)"
  115.  
  116.         case $(nvram get oet${i}_spbr) in
  117.             '0') spbr='All via VPN';;
  118.             '1') spbr='Selected via VPN';;
  119.             '2') spbr='Selected via WAN';;
  120.         esac
  121.  
  122.         case $(nvram get oet${i}_dpbr) in
  123.             '0') dpbr='All via VPN';;
  124.             '1') dpbr='Selected via VPN';;
  125.             '2') dpbr='Selected via WAN';;
  126.         esac
  127.  
  128.         printf "\nWireGuard #${i} IP/DNS/SPBR/DPBR: $ip/$dns/$spbr/$dpbr"
  129.  
  130.         sw_vpn_printed=
  131.     done
  132.  
  133.     [ ${sw_vpn_printed+x} ] && printf '\n'
  134.  
  135.     printf "\nActive DNS (Do53/DoC/DoT) UDP/TCP Connections\n"
  136.     printf "  ${sev_lvl_2}Do53 (plaintext) routed over the WAN${RS}\n"
  137.     printf "  ${sev_lvl_1}DoC/DoT (ciphertext) routed over the WAN${RS}\n"
  138.     printf "  ${sev_lvl_0}DoC/DoT/Do53 NOT routed over the WAN "
  139.     printf "(loopback, local, or VPN)${RS}\n"
  140.     echo ' '
  141. }
  142.  
  143. # function format_body()
  144. format_body() {
  145.     _print_with_dupe_count() {
  146.         local dupe_count=0
  147.         local prev_line="$(head -n1 $DATA)"
  148.  
  149.         # print line w/ duplicate line-count indicator
  150.         __print_line() {
  151.             if [ $dupe_count -gt 1 ]; then
  152.                 printf '%-95s %s\n' "$prev_line" "($dupe_count)"
  153.             else
  154.                 echo "$prev_line"
  155.             fi
  156.         }
  157.  
  158.         # find and print unique lines while counting duplicates
  159.         while read line; do
  160.             if [ "$line" == "$prev_line" ]; then
  161.                 let dupe_count++
  162.             else
  163.                 __print_line; prev_line="$line"; dupe_count=1
  164.             fi
  165.         done < $DATA
  166.  
  167.         [ "$prev_line" ] && __print_line
  168.     }
  169.  
  170.     # publish DoC/Do53 over udp (replied and sorted)
  171.     grep -E "^ipv4 .* (udp .* dport=$doc_port .* src=$doc_ip |udp .* dport=53 )" \
  172.             /proc/net/nf_conntrack | \
  173.         awk '$0 !~ /UNREPLIED/{printf "%s %-19s %-19s %-11s %-19s %s\n",
  174.                $3, $6, $7, $9, $12, $13}' | \
  175.             sort > $DATA
  176.  
  177.     # remove duplicates; optionally include dupe count
  178.     [ ${sw_dupes+x} ] && _print_with_dupe_count || uniq $DATA
  179.  
  180.     # publish DoC/DoT/Do53 over tcp (replied and sorted)
  181.     grep -E "^ipv4 .* (tcp .* dport=$doc_port .* src=$doc_ip |tcp .* dport=(53|853)) " \
  182.             /proc/net/nf_conntrack | \
  183.         awk '/ASSURED/{printf "%s %-19s %-19s %-11s %-19s %s\n",
  184.                $3, $7, $8, $10, $13, $14}' | \
  185.             sort > $DATA
  186.  
  187.     # remove duplicates; optionally include dupe count
  188.     [ ${sw_dupes+x} ] && _print_with_dupe_count || uniq $DATA
  189. }
  190.  
  191. # function pause_display()
  192. pause_display() {
  193.     read -sp "$(echo -e ${menu}"\nPress [Enter] key to continue..."${RS})" \
  194.         < "$(tty 0>&2)"
  195. }
  196.  
  197. # function exit_0()
  198. exit_0() {
  199.     # publish name of log file
  200.     [ -s $LOG ] && echo -e "\nlog file: $LOG" || rm -f $LOG
  201.  
  202.     # cleanup work files
  203.     rm -f $HEAD $BODY $DATA
  204.  
  205.     # publish information on restarting
  206.     echo -e "\nRun $0 to restart."
  207.  
  208.     exit 0
  209. }
  210.  
  211. # trap on unexpected exit (e.g., crtl-c)
  212. trap 'exit_0' SIGHUP SIGINT SIGTERM
  213.  
  214. # enable header
  215. sw_head=
  216.  
  217. # set initial update interval
  218. interval=$(max_val $MIN_INTERVAL $(min_val $INTERVAL $MAX_INTERVAL))
  219.  
  220. # set initial logging state
  221. [ "$LOGGING" ] && sw_log=
  222.  
  223. # begin display loop
  224. while :; do
  225.  
  226. # establish wan ip for analysis and display
  227. wan_ip="$(nvram get wan_ipaddr)"
  228. [ "$wan_ip" == '0.0.0.0' ] && wan_ip="$(nvram get lan_gateway)"
  229. wan_ip_4disp="$([ ${sw_wanip+x} ] && echo 'x.x.x.x' || echo $wan_ip)"
  230.  
  231. # establish dnscrypt ip and port
  232. if [ "$(nvram get dns_crypt)" == '1' ]; then
  233.     doc_ip_port="$(grep -m1 ^$(nvram get dns_crypt_resolver), \
  234.        /etc/dnscrypt/dnscrypt-resolvers.csv | \
  235.            sed 's/\".*\"//g' | cut -d, -f11 | \
  236.                grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}(:[0-9]{1,5}|)')"
  237.     doc_ip="$(echo $doc_ip_port | cut -d: -f1)"
  238.  
  239.     if [ "$doc_ip" ]; then
  240.         doc_port="$(min_str $(echo $doc_ip_port | cut -sd: -f2) 443)"
  241.     else
  242.         doc_port=''
  243.     fi
  244. else
  245.     doc_ip=''; doc_port=''
  246. fi
  247.  
  248. # set display attributes (color (default) or monochrome)
  249. if [ ! ${sw_mono+x} ]; then
  250.     sev_lvl_2="$RED"
  251.     sev_lvl_1="$YELLOW"
  252.     sev_lvl_0="$GREEN"
  253.     menu="$CMENU"
  254. else
  255.     sev_lvl_2="$BOLD"
  256.     sev_lvl_1="$UNDERLINE"
  257.     sev_lvl_0="$NORMAL"
  258.     menu="$MMENU"
  259. fi
  260.  
  261. # format command menu
  262. [ ${sw_head+x}   ] &&   _head='hide'    ||   _head='show'
  263. [ ${sw_scroll+x} ] && _scroll='disable' || _scroll='enable'
  264. [ ${sw_wanip+x}  ] &&  _wanip='show'    ||  _wanip='hide'
  265. [ ${sw_dupes+x}  ] &&  _dupes='hide'    ||  _dupes='show'
  266. [ ${sw_mono+x}   ] &&   _mono='disable' ||   _mono='enable'
  267. [ ${sw_log+x}    ] &&    _log='disable' ||    _log='enable'
  268.  
  269. # format header
  270. [ ${sw_head+x} ] && format_header > $HEAD
  271.  
  272. # format body
  273. format_body > $BODY
  274.  
  275. # clear display
  276. clear
  277.  
  278. # start of "more-able" output
  279. {
  280. # display command menu
  281. if [ ! ${sw_next+x} ]; then
  282.     printf "${menu}%s | %s | %s | %s | %s | %s%s${RS}\n" \
  283.         "[n]ext menu" \
  284.         "$_head [h]eader" \
  285.         "[+/-] interval ($interval)" \
  286.         "$_scroll [s]croll" \
  287.         "[p]ause" \
  288.         "[e]xit" \
  289.         ''
  290. else
  291.     printf "${menu}%s | %s | %s | %s | %s%s${RS}\n" \
  292.         "[n]ext menu" \
  293.         "$_wanip wan [i]p" \
  294.         "$_dupes [d]upes" \
  295.         "$_mono [m]ono" \
  296.         "$_log [l]og" \
  297.         ''
  298. fi
  299.  
  300. # display header
  301. [ ${sw_head+x} ] && cat $HEAD
  302.  
  303. # display column headings
  304. printf '%43s'   'v-------------- sender ---------------v'
  305. printf '%52s\n' 'v------------- recipient -------------v'
  306.  
  307. # display body/data (include severity level 0|1|2)
  308. while read line; do
  309.     # hide wan/public ip when requested
  310.     if [ ${sw_wanip+x} ]; then
  311.         line_4disp="$(echo "$line" | \
  312.            sed -r "s/(src|dst)=$wan_ip($)/\1=$wan_ip_4disp/g; \
  313.                     s/(src|dst)=$wan_ip( +)/\1=$wan_ip_4disp         /g")"
  314.     else
  315.         line_4disp="$line"
  316.     fi
  317.  
  318.     if echo $line | grep 'dport=53 ' | \
  319.             grep -Eq "(src|dst)=$wan_ip( |$)"; then
  320.         # Do53 connection routed over WAN
  321.         printf "${sev_lvl_2}$line_4disp${RS}\n"
  322.  
  323.         # log the connection (optional)
  324.         if [ ${sw_log+x} ]; then
  325.             # remove dupe count (if present)
  326.             line="$(echo "$line" | sed 's/\s*([0-9]*)$//')"
  327.             # ignore duplicates
  328.             grep -qxF "$line" $LOG || echo "$line" >> $LOG
  329.         fi
  330.     elif echo $line | grep -E "dport=($doc_port|853) " | \
  331.             grep -Eq "(src|dst)=$wan_ip( |$)"; then
  332.         # DoC/DoT connection routed over WAN
  333.         printf "${sev_lvl_1}$line_4disp${RS}\n"
  334.     else
  335.         # Do53/DoC/DoT connection NOT routed over WAN
  336.         printf "${sev_lvl_0}$line_4disp${RS}\n"
  337.     fi
  338. done < $BODY
  339.  
  340. [ -s $BODY ] || echo '<no-data>'
  341.  
  342. # end of "more-able" output
  343. } 2>&1 | $([ ${sw_scroll+x} ] && echo 'tee' || echo 'more')
  344.  
  345. # update display at regular interval (capture any user input)
  346. key_press=''; read -rsn1 -t $interval key_press < "$(tty 0>&2)"
  347.  
  348. # handle key-press (optional)
  349. if [ $key_press ]; then
  350.     case $key_press in
  351.         # common/shared menu option(s)
  352.         'n') [ ${sw_next+x}   ] && unset sw_next   || sw_next=;;
  353.         # primary menu options
  354.         'h') [ ${sw_head+x}   ] && unset sw_head   || sw_head=;;
  355.         '+') interval=$(min_val $((interval+3)) $MAX_INTERVAL);;
  356.         '-') interval=$(max_val $((interval-3)) $MIN_INTERVAL);;
  357.         'p') pause_display;;
  358.         'e') exit_0;;
  359.         # secondary menu options
  360.         'i') [ ${sw_wanip+x}  ] && unset sw_wanip  || sw_wanip=;;
  361.         'd') [ ${sw_dupes+x}  ] && unset sw_dupes  || sw_dupes=;;
  362.         'm') [ ${sw_mono+x}   ] && unset sw_mono   || sw_mono=;;
  363.         's') [ ${sw_scroll+x} ] && unset sw_scroll || sw_scroll=;;
  364.         'l') [ ${sw_log+x}    ] && unset sw_log    || sw_log=;;
  365.     esac
  366. fi
  367.  
  368. done # end of 'while :; do'
  369. EOF
  370. [ ${LOGGING+x} ] && sed -ri 's/\$LOGGING/LOGGING/g' $SCRIPT
  371. sed -i "s:\$INTERVAL:$INTERVAL:g" $SCRIPT
  372. chmod +x $SCRIPT
  373.  
  374. # begin execution
  375. $SCRIPT
RAW Paste Data Copied