eibgrad

merlin-dns-monitor.sh

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