DNS Decoding

Problem this snippet solves:

Sample iRule that does DNS decoding, for DNS protocol debugging and studying :)

June 3 2014 : We have got few customers using this script in production and having system rebooted due to possible indefinite loop in this script. There are possible risks with the while loops without any protection. As the original contributor kindly mentions this is debugging and studying sample, those who want to use this script in their production should understand what it is doing and improve security by their selves. Kimihito.

Code :

when RULE_INIT {
    array set ::type2name {
        1    A
        2    NS
        3    MD
        4    MF
        5    CNAME
        6    SOA
        7    MB
        8    MG
        9    MR
        10   NULL
        11   WKS
        12   PTR
        13   HINFO
        14   MINFO
        15   MX
        16   TXT
        17   RP
        18   AFSDB
        19   X25
        20   ISDN
        21   RS
        22   NSAP
        23   NSAP_PTR
        24   SIG
        25   KEY
        26   PX
        27   GPOS
        28   AAAA
        29   LOC
        30   NXT
        31   EID
        32   NIMLOC
        33   SRV
        34   ATMA
        35   NAPTR
        36   KX
        37   CERT
        38   A6
        39   DNAME
        40   SINK
        41   OPT
        42   APL
        43   DS
        44   SSHFP
        45   IPSECKEY
        46   RRSIG
        47   NSEC
        48   DNSKEY
        49   DHCID
        55   HIP
        99   SPF
        100  UINFO
        101  UID
        102  GID
        103  UNSPEC
        249  TKEY
        250  TSIG
        251  IXFR
        252  AXFR
        253  MAILB
        254  MAILA
        32768    TA
        32769    DLV
        65281    WINS
        65282    WINS_R
    }
    array set ::class2name {
        1    IN
        2    CS
        3    CH
        4    HS
        254  NONE
        255  ANY
    }
    array set ::qr2txt {
        0   "query"
        1   "response"
    }
    array set ::opcode2txt {
        0   "Standard query"
        1   "Inverse query"
        2   "Server status request"
        4   "Zone change notification"
        5   "Dynamic update"
    }
    array set ::rcode2txt {
        0   "No error condition"
        1   "Format error"
        2   "Server failure"
        3   "Name Error"
        4   "Not Implemented"
        5   "Refused"
        6   "Name exists"
        7   "RRset exists"
        8   "RRset does not exist"
        9   "Not authoritative"
        10  "Name out of zone"    
        16   "TSIG Signature Failure"
        17   "Key not recognized"
        18   "Signature out of time window"
        19   "Bad TKEY Mode"
        20   "uplicate key name"
        21   "Algorithm not supported"
        22   "Bad Truncation"
    }
}
when CLIENT_DATA {
    binary scan [UDP::payload] SSSSSS id flags qdcount ancount nscount arcount
    set qr [expr ($flags >> 15)&0x1]
    set opcode [expr ($flags >> 11)& 0xf]
    set aa [expr ($flags >> 10)& 0x1]
    set tc [expr ($flags >> 9)& 0x1]
    set rd [expr ($flags >> 8)& 0x1]
    set ra [expr ($flags >> 7)& 0x1]
    set z [expr ($flags >> 4)& 0x7]
    set rcode [expr $flags & 0xf]
    #log local0. "DEBUG: ID=$id Query=$qr Opcode=$opcode Authoritative=$aa Truncate=$tc Recursion_Desired=$rd \
Recursion_Available=$ra Reserve(000)=$z Response_Code=$rcode Questions=$qdcount Answers=$ancount Name_Servers=$nscount \
Additionals=$arcount"

    # Total Header length = 12 bytes
    set index 12

    # Question Section: it is usually 1 (qdcount=1), we may not need to loop here actually.
    while { $qdcount > 0 } {
        binary scan [UDP::payload] @${index}c count
        incr index
        while { $count != 0 } {
            binary scan [UDP::payload] @${index}A${count}c name new_count
            incr index $count
            incr index
            set count $new_count
            if { [info exists dname] } {
                set dname $dname.$name
            } else {
                set dname $name
            }
        }
        binary scan [UDP::payload] @${index}SS qtype qclass
        log local0. "Question: $dname qtype=$::type2name($qtype) qclass=$::class2name($qclass)"
        unset dname
        incr index 4
        incr qdcount -1
    }

}

when SERVER_DATA {
    binary scan [UDP::payload] SSSSSS id flags qdcount ancount nscount arcount
    set qr [expr ($flags >> 15)&0x1]
    set opcode [expr ($flags >> 11)& 0xf]
    set aa [expr ($flags >> 10)& 0x1]
    set tc [expr ($flags >> 9)& 0x1]
    set rd [expr ($flags >> 8)& 0x1]
    set ra [expr ($flags >> 7)& 0x1]
    set z [expr ($flags >> 4)& 0x7]
    set rcode [expr $flags & 0xf]
    #log local0. "DEBUG: ID=$id Query=$qr Opcode=$opcode Authoritative=$aa Truncate=$tc Recursion_Desired=$rd \
Recursion_Available=$ra Reserve(000)=$z Response_Code=$rcode Questions=$qdcount Answers=$ancount Name_Servers=$nscount \
Additionals=$arcount"

    # Total Header length = 12 bytes
    set index 12

    # Question Section: it is usually 1 (qdcount=1), we may not need to loop here actually.
    while { $qdcount > 0 } {
        binary scan [UDP::payload] @${index}c count
        incr index
        while { $count != 0 } {
            binary scan [UDP::payload] @${index}A${count}c name new_count
            incr index $count
            incr index
            set count $new_count
            if { [info exists dname] } {
                set dname $dname.$name
            } else {
                set dname $name
            }
        }
        binary scan [UDP::payload] @${index}SS qtype qclass
        log local0. "Question: $dname qtype=$::type2name($qtype) qclass=$::class2name($qclass)"
        unset dname
        incr index 4
        incr qdcount -1
    }

    # The Answer, Authority and Additional Sections
    while { $ancount > 0 || $nscount > 0 || $arcount > 0} {
        binary scan [UDP::payload] @${index}S pointer
        if { [expr ($pointer >> 14) & 0x3] == 3 } {
            set saveindex $index
            set index [expr $pointer & 0x3fff]
        } 
        binary scan [UDP::payload] @${index}c count
        incr index
        while { $count != 0 } {
            binary scan [UDP::payload] @${index}A${count}S name pointer
            incr index $count
            if { [expr ($pointer >> 14) & 0x3] == 3 } {
                if { not [info exists saveindex] } {
                    set saveindex $index 
                }
                set index [expr $pointer & 0x3fff]
            }
            binary scan [UDP::payload] @${index}c count
            incr index
            if { [info exists dname] } {
                set dname $dname.$name
            } else {
                set dname $name
            }
        }
        if { [info exists saveindex] } {
            set index $saveindex 
            incr index 2
            unset saveindex
        }
        binary scan [UDP::payload] @${index}SSIS type class ttl rdlength
        incr index 10
        if { [info exists dname] } { set savename $dname; unset dname }
        switch $type {
            1 {
                # A
                binary scan [UDP::payload] @${index}cccc a b c d
                set addr [expr ($a+0x100)%0x100].[expr ($b+0x100)%0x100].[expr ($c+0x100)%0x100].[expr ($d+0x100)%0x100]
                log local0. "$::type2name($type): $savename $addr"
                incr index $rdlength
            }
            12 -
            9 -
            8 -
            7 -
            5 -
            4 -
            3 -
            2 {
                # NS, MD, MF, CNAME, MB, MG, MR, PTR, MINFO
                binary scan [UDP::payload] @${index}S pointer
                if { [expr ($pointer >> 14) & 0x3] == 3 } {
                    set saveindex $index
                    set index [expr $pointer & 0x3fff]
                } 
                binary scan [UDP::payload] @${index}c count
                incr index
                while { $count != 0 } {
                    binary scan [UDP::payload] @${index}A${count}S name pointer
                    incr index $count
                    if { [expr ($pointer >> 14) & 0x3] == 3 } {
                        if { not [info exists saveindex] } {
                            set saveindex $index 
                        }
                        set index [expr $pointer & 0x3fff]
                    }
                    binary scan [UDP::payload] @${index}c count
                    incr index
                    if { [info exists dname] } {
                        set dname $dname.$name
                    } else {
                        set dname $name
                    }
                }
                if { [info exists saveindex] } {
                    set index $saveindex 
                    incr index 2
                    unset saveindex
                }
                log local0. "$::type2name($type): $savename $dname"
                unset dname
            }
            6 {
                # SOA
                foreach i { mname rname } {
                    binary scan [UDP::payload] @${index}S pointer
                    if { [expr ($pointer >> 14) & 0x3] == 3 } {
                        set saveindex $index
                        set index [expr $pointer & 0x3fff]
                    } 
                    binary scan [UDP::payload] @${index}c count
                    incr index
                    while { $count != 0 } {
                        binary scan [UDP::payload] @${index}A${count}S name pointer
                        incr index $count
                        if { [expr ($pointer >> 14) & 0x3] == 3 } {
                            if { not [info exists saveindex] } {
                                set saveindex $index 
                            }
                            set index [expr $pointer & 0x3fff]
                        }
                        binary scan [UDP::payload] @${index}c count
                        incr index
                        if { [info exists dname] } {
                            set dname $dname.$name
                        } else {
                            set dname $name
                        }
                    }
                    if { [info exists saveindex] } {
                        set index $saveindex 
                        incr index 2
                        unset saveindex
                    }
                    set $i $dname
                    unset dname
                }
                # SERIAL, REFRESH, RETRY, EXPIRE
                binary scan [UDP::payload] @${index}IIIII serial refresh retry expire minimum
                incr index 20
                log local0. "SOA: $mname $rname serial=$serial, refresh=$refresh, retry=$retry, \
expire=$expire minimum=$minimum"
            }
            13 {
                foreach i { cpu os } {
                    binary scan [UDP::payload] @${index}c length
                    incr index
                    binary scan [UDP::payload] @${index}A${length} string
                    incr index $length
                    set $i $string
                }
                log local0. "$::type2name($type): $cpu $os"
            }
            14 {
                # MINFO
                # RMAILBX, EMAILBX
                foreach i { rmailbx emailbx } {
                    binary scan [UDP::payload] @${index}S pointer
                    if { [expr ($pointer >> 14) & 0x3] == 3 } {
                        set saveindex $index
                        set index [expr $pointer & 0x3fff]
                    } 
                    binary scan [UDP::payload] @${index}c count
                    incr index
                    while { $count != 0 } {
                        binary scan [UDP::payload] @${index}A${count}S name pointer
                        incr index $count
                        if { [expr ($pointer >> 14) & 0x3] == 3 } {
                            if { not [info exists saveindex] } {
                                set saveindex $index 
                            }
                            set index [expr $pointer & 0x3fff]
                        }
                        binary scan [UDP::payload] @${index}c count
                        incr index
                        if { [info exists dname] } {
                            set dname $dname.$name
                        } else {
                            set dname $name
                        }
                    }
                    if { [info exists saveindex] } {
                        set index $saveindex 
                        incr index 2
                        unset saveindex
                    }
                    set $i $dname
                    unset dname
                }
                unset dname
                log local0. "$::type2name($type): $savename $rmailbx $emailbx"
            }
            15 {
                # MX
                binary scan [UDP::payload] @${index}SS preference pointer
                incr index 2
                if { [expr ($pointer >> 14) & 0x3] == 3 } {
                    set saveindex $index
                    set index [expr $pointer & 0x3fff]
                } 
                binary scan [UDP::payload] @${index}c count
                incr index
                while { $count != 0 } {
                    binary scan [UDP::payload] @${index}A${count}S name pointer
                    incr index $count
                    if { [expr ($pointer >> 14) & 0x3] == 3 } {
                        if { not [info exists saveindex] } {
                            set saveindex $index 
                        }
                        set index [expr $pointer & 0x3fff]
                    }
                    binary scan [UDP::payload] @${index}c count
                    incr index
                    if { [info exists dname] } {
                        set dname $dname.$name
                    } else {
                        set dname $name
                    }
                }
                if { [info exists saveindex] } {
                    set index $saveindex 
                    incr index 2
                    unset saveindex
                }
                log local0. "$::type2name($type): $savename $dname $preference"
                unset dname
            }
            16 {
                set maxlen [expr $index + $rdlength ]
                while { $index < $maxlen } {
                    binary scan [UDP::payload] @${index}c length
                    incr index
                    binary scan [UDP::payload] @${index}A${length} string
                    incr index $length
                    log local0. "$::type2name($type): $string"
                }
            }
            28 {
                # AAAA
                binary scan [UDP::payload] @${index}H4H4H4H4H4H4H4H4 a b c d e f g h
                log local0. "$::type2name($type): $savename $a:$b:$c:$d:$e:$f:$g:$h"
                incr index $rdlength
            }
            33 {
                # SRV
                binary scan [UDP::payload] @${index}SSSS priority weight port pointer
                incr index 6
                if { [expr ($pointer >> 14) & 0x3] == 3 } {
                    set saveindex $index
                    set index [expr $pointer & 0x3fff]
                } 
                binary scan [UDP::payload] @${index}c count
                incr index
                while { $count != 0 } {
                    binary scan [UDP::payload] @${index}A${count}S name pointer
                    incr index $count
                    if { [expr ($pointer >> 14) & 0x3] == 3 } {
                        if { not [info exists saveindex] } {
                            set saveindex $index 
                        }
                        set index [expr $pointer & 0x3fff]
                    }
                    binary scan [UDP::payload] @${index}c count
                    incr index
                    if { [info exists dname] } {
                        set dname $dname.$name
                    } else {
                        set dname $name
                    }
                }
                if { [info exists saveindex] } {
                    set index $saveindex 
                    incr index 2
                    unset saveindex
                }
                log local0. "$::type2name($type): $savename $priority $weight $port $dname"
                unset dname
            }
            default {
                binary scan [UDP::payload] @${index}H[expr $rdlength * 2] rdata
                log local0. "$::type2name($type): data=0x$rdata"
                incr index $rdlength
            }
        }
        if { $ancount > 0 } {
            incr ancount -1
        } elseif  { $nscount > 0 } {
            incr nscount -1
        } else {
            incr arcount -1
        }
    }
}

# Introducing loop limit counters, $limitqdcount and $limitname to avoid tmm. It'd be better to apply something similar not only to CLIENT_DATA but also SERVER_DATA side.

# Line 123 ~ 

    # Total Header length = 12 bytes
    set index 12
    # Question Section: it is usually 1 (qdcount=1), we may not need to loop here actually.
    # This is a protection for many Questions in a single query packet
    set limitqdcount 0
    while { $qdcount > 0 } {
            # qdcount is generally 1.
            if { $limitqdcount > 0 } {
                log local0. "limitqdcount exceeded "
                return
            }
        binary scan [UDP::payload] @${index}c count
        incr index
        # This is a protection for too long domain name 
        set limitname 0
        while { $count != 0 } {
            # How long domain name of the query you allow. Setting it to 2, you allow "www.example.com" but "www.my.example.com".
            if { $limitname > 2 } {
                log local0. "limitname exceeded : too long domain name "
                return
            }
            binary scan [UDP::payload] @${index}A${count}c name new_count
            incr index $count
            incr index
            set count $new_count
            if { [info exists dname] } {
                set dname $dname.$name
            } else {
                set dname $name
            }
            incr limitname 1
        }
        binary scan [UDP::payload] @${index}SS qtype qclass opcode rcode
        log local0. "Question : $dname qtype=$::type2name($qtype) qclass=$::class2name($qclass)"
        unset dname
        incr index 4
        incr qdcount -1
        incr limitqdcount 1
    }
}
Published Mar 17, 2015
Version 1.0

Was this article helpful?

No CommentsBe the first to comment