For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

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