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
}
}