Create DNS monitor without external dig or nslookup

Problem this snippet solves:

NOTE: This was written in 10.x and early 11.x days. Since then, a native DNS monitor has been added to the product.


This example will create a udp monitor with proper send and receive strings to query a DNS server. Much of the heavy lifting was pulled from an iApp template that ships with v11. I have tested this out on as far back as 10.2.1 base.

Installation

One method to get this into your config would be:

In the code block below, click the icon in the top right corner '<>' view source. Ctrl-A, Ctrl-C to select and copy all.

Then in tmsh, type 'edit /cli script dns.monitor' If your editor is configured to use vi (the default setting) Type :set paste, then c, then Shift + G. This will clear out the default skeleton.

You will now be in insert mode. Press Shift + insert. This will paste in the script.

Now save the script by pressing Esc, then typing :wq < enter > and follow the prompts.

How to use this snippet:

Example

root@bigip(Active)(/Common)(tmos)# run cli script dns.monitor xxx
Requires 5 arguments:     
         := Name you want to give the monitor
  := A, NS, PTR, SOA, CNAME
     := What you are asking the DNS server (send string)
       := Why you expect for a "healthy" response (recv string)
     := The frequency you want the monitor to fire

 e.g. dns.monitor my_dns A www.example.com 192.0.2.55 5

root@bigip(Active)(/Common)(tmos)# run cli script dns.monitor my_dns A www.example.com 192.0.2.55 5
root@bigip(Active)(/Common)(tmos)# list ltm monitor udp my_dns
ltm monitor udp my_dns {
    debug no
    defaults-from udp
    destination *:*
    interval 5
    recv \\x00\\x01.*\\x04\\xc0\\x00\\x02\\x37
    send \\x96\\x87\\x01\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x03www\\x07example\\x03com\\x00\\x00\\x01\\x00\\x01
    time-until-up 0
    timeout 16
}
root@bigip(Active)(/Common)(tmos)#

Code :

create script dns.monitor {
proc script::init {} {
  }

  proc script::run {} {
    if {$tmsh::argc == 6} {

    # create monitor
      set monitor_name [lindex $tmsh::argv 1]
      set monitor_type [string toupper [ lindex $tmsh::argv 2 ]]
      switch $monitor_type {
        A {
          set monitor_send [generate_monitor_send_string_a_record [lindex $tmsh::argv 3]]
          set monitor_recv [generate_monitor_recv_string_a_record [lindex $tmsh::argv 4]]
        }

        NS  {
          set monitor_send [generate_monitor_send_string_ns_record [lindex $tmsh::argv 3]]
          set monitor_recv [generate_monitor_recv_string_ns_record [lindex $tmsh::argv 4] [lindex $tmsh::argv 3]]
        }

        PTR  {
          set monitor_send [generate_monitor_send_string_ptr_record [lindex $tmsh::argv 3]]
          set monitor_recv [generate_monitor_recv_string_ptr_record [lindex $tmsh::argv 4]]
        }

        SOA  {
          set monitor_send [generate_monitor_send_string_soa_record [lindex $tmsh::argv 3]]
          set monitor_recv [generate_monitor_recv_string_soa_record [lindex $tmsh::argv 4] [lindex $tmsh::argv 3]]
        }

        CNAME  {
          set monitor_send [generate_monitor_send_string_cname_record [lindex $tmsh::argv 3]] 
          set monitor_recv [generate_monitor_recv_string_cname_record [lindex $tmsh::argv 4] [lindex $tmsh::argv 3]]
        }
        default {
          usage
        }
      }


      set monitor_interval [lindex $tmsh::argv 5]
      set monitor_timeout [ expr { [ expr {$monitor_interval * 3} ] + 1 } ]

      # doctor the send and receive strings so that they go through as intended
      set monitor_send [doctor_hex_string $monitor_send]
      set monitor_recv [doctor_hex_string $monitor_recv]

      tmsh_create "ltm monitor udp $monitor_name defaults-from udp interval $monitor_interval timeout $monitor_timeout send \"$monitor_send\" recv \"$monitor_recv\""

    } else {
      usage
    }
  }

  proc script::help {} {
    tmsh::add_help "Create a DNS monitor \n\
          "
  }

  proc script::tabc {} {
  }

  proc tmsh_create { arguments } {
    regsub -all {\[} $arguments "\\\[" arguments
    regsub -all {\]} $arguments "\\\]" arguments
    tmsh::create "$arguments"
  }

  proc number_to_hex_string { number } {
    if { $number < 0 } {
      set number [ expr {0 - $number} ]
    }
    set hex_string [format "\\x%02x" $number]
    return $hex_string
  }

  proc hostname_or_ip_address_to_dns_name { hostname } {
    set fields [split $hostname .]

    set dns_name ""
    foreach field $fields {
      set len [string length $field]
      set str_len [number_to_hex_string $len]
      append dns_name $str_len
      append dns_name $field
    }
    append dns_name "\\x00"
    return $dns_name
  }

  proc hostname_or_ip_address_to_reverse_dns_name { hostname } {
    set complete_hostname [format "arpa.in-addr.%s" $hostname]

    set fields [split $complete_hostname .]

    set dns_name ""
    foreach field $fields {
    #        if [string is integer $field] {
      set len [string length $field]
      set str_len [number_to_hex_string $len]
      set dns_name [format "%s%s%s" $str_len $field $dns_name]
      #        }
      }
      append dns_name "\\x00"
      return $dns_name
    }

    proc get_id { } {
      set value1 [expr { int(256 * rand()) }]
      set value2 [expr { int(256 * rand()) }]

      set id [number_to_hex_string $value1]
      append id [number_to_hex_string $value2]

      return $id
    }

    proc generate_dns_packet_payload { question } {
      set payload [get_id]
      append payload "\\x01\\x00"
      append payload "\\x00\\x01"
      append payload "\\x00\\x00"
      append payload "\\x00\\x00"
      append payload "\\x00\\x00"
      append payload $question

      return $payload
    }

    proc ip_addr_to_a_resp_addr { address } {
      set fields [split $address .]

      set num_fields 0
      set field_encoding ""
      foreach field $fields {
        if {[string is integer $field]} {
          set num_fields [expr { $num_fields + 1 }]
          set part [number_to_hex_string $field]
          append field_encoding $part
        }
      }

      set dns_name [number_to_hex_string $num_fields]
      append dns_name $field_encoding

      return $dns_name
    }

    proc generate_send_payload {
      input_string
      record_type_code
      reverse } {
      if { $reverse == 0 } {
        set dns_name [hostname_or_ip_address_to_dns_name $input_string]
      }
      else {
        set dns_name [hostname_or_ip_address_to_reverse_dns_name $input_string]
      }

      set internet_address_qclass "\\x00\\x01"
      set question $dns_name
      append question $record_type_code
      append question $internet_address_qclass

      set send [generate_dns_packet_payload $question]

      return $send
    }

    proc generate_monitor_send_string_a_record { input_string } {
      set send [generate_send_payload $input_string "\\x00\\x01" 0]
      return $send
    }

    proc generate_monitor_send_string_ns_record { input_string } {
      set send [generate_send_payload $input_string "\\x00\\x02" 0]
      return $send
    }

    proc generate_monitor_send_string_ptr_record { input_string } {
      set send [generate_send_payload $input_string "\\x00\\x0c" 1]
      return $send
    }

    proc generate_monitor_send_string_soa_record { input_string } {
      set send [generate_send_payload $input_string "\\x00\\x06" 0]
      return $send
    }

    proc generate_monitor_send_string_cname_record { input_string } {
      set send [generate_send_payload $input_string "\\x00\\x05" 0]
      return $send
    }

    proc generate_monitor_recv_string_a_record { input_string } {
      set a_resp_addr [ip_addr_to_a_resp_addr $input_string]
      set recv [format "\\x00\\x01.*%s" $a_resp_addr]

      return $recv
    }

    proc generate_monitor_recv_string_ns_record {
      input_string
      domain_name } {
      regsub -all {\.} $domain_name \. domain_name
      set re [format "^(.*)(\.%s)$" $domain_name]
      set found [regexp $re $input_string matched new_hostname rest]

      if { $found != 0 } {
        set recv_temp [hostname_or_ip_address_to_dns_name $new_hostname]

        set re {^(.*\\)(x00)$}
        set found [regexp -nocase $re $recv_temp matched recv rest]

        if { $found != 0 } {
          set recv [format "\\x00\\x02.*%sxc0" $recv]
        }
        else {
          set recv [format "\\x00\\x02.*%s" $recv_temp]
        }
      }
      else {
        set recv [hostname_or_ip_address_to_dns_name $input_string]
        set recv [format "\\x00\\x02.*%s" $recv]
      }

      return $recv
    }

    proc generate_monitor_recv_string_ptr_record { input_string } {
      set dns_name [hostname_or_ip_address_to_dns_name $input_string]
      set recv [format "\\x00\\x0c.*%s" $dns_name]

      return $recv
    }

    proc generate_monitor_recv_string_soa_record {
      input_string
      domain_name } {
      regsub -all {\.} $domain_name \. domain_name
      set re [format "^(.*)(\.%s)$" $domain_name]
      set found [regexp -nocase $re $input_string matched new_hostname rest]

      if { $found != 0 } {
        set recv_temp [hostname_or_ip_address_to_dns_name $new_hostname]

        set re {^(.*)(\\x00)$}
        set found [regexp -nocase $re $recv_temp matched recv rest]

        if { $found != 0 } {
          set recv [format "\\x00\\x06.*%s(\\xc0|\\x05)" $recv]
        }
        else {
          set recv [format "\\x00\\x06.*%s" $recv_temp]
        }
      }
      else {
        set recv [hostname_or_ip_address_to_dns_name $input_string]
        set recv [format "\\x00\\x06.*%s" $recv]
      }

      return $recv
    }

    proc generate_monitor_recv_string_cname_record {
      input_string
      hostname } {
      regsub -all {\.} $hostname \. hostname
      #puts "hostname = $hostname"
      set re {^([^\.]+)\.(.+)$}
      #puts "re = $re"
      set found [regexp -nocase $re $hostname matched first domain_name]
      #puts "found = $found"

      if { $found == 0 } {
        set domain_name $hostname
      }

      regsub -all {\.} $domain_name \. domain_name

      #puts "domain_name = $domain_name"
      #puts "input_string = $input_string"
      set re [format "^(.*)(\.%s)$" $domain_name]
      #puts "re = $re"
      set found [regexp -nocase $re $input_string matched new_hostname rest]

      if { $found != 0 } {
        set recv_temp [hostname_or_ip_address_to_dns_name $new_hostname]

        set re {^(.*\\)(x00)$}
        set found [regexp -nocase $re $recv_temp matched recv rest]

        if { $found != 0 } {
          set re {^(.*)(\\x00)$}
          set found [regexp -nocase $re $recv_temp matched recv rest]
          set recv [format "\\x00\\x05.*%s(\\xc0|\\x05)" $recv]
        }
        else {
          set recv [format "\\x00\\x05.*%s" $recv_temp]
        }
      }
      else {
        set recv [hostname_or_ip_address_to_dns_name $input_string]
        set recv [format "\\x00\\x05.*%s" $recv]
      }

      return $recv
    }

    proc doctor_hex_string { input } {
      set pieces [split $input \\ ]

      set output ""
      set first_time 1
      foreach piece $pieces {
        if { $first_time == 1 } {
          set first_time 0
        }
        else {
          append output {\\}
        }
        append output $piece
      }

      return $output
    }

    proc usage { } {
      puts "Requires 5 arguments:      \n\
                := Name you want to give the monitor \n\
         := A, NS, PTR, SOA, CNAME \n\
            := What you are asking the DNS server (send string) \n\
              := Why you expect for a \"healthy\" response (recv string) \n\
            := The frequency you want the monitor to fire \n\n\
        e.g. [lindex $tmsh::argv 0] A www.example.com 192.0.2.55 5"
      exit
    }
}
Updated Jul 16, 2024
Version 2.0
No CommentsBe the first to comment