NAT_iRule

Problem this snippet solves:

This is a solution that allows client from IPv6 network to communicate to IPv4 network thru BIG-IP. It actually contains 2 iRules:- NAT64 and DNS64 iRule (see iRule source section below).

Here is how it works:-

  • Suppose IPv6 client is going to connect to www.f5.com which resides in IPv4 network
  • First, IPv6 client performs DNS resolution. IPv6 client sends DNS AAAA query to BIG-IP.
  • BIG-IP transforms AAAA to A query by DNS64 iRule
  • BIG-IP forwards A query from previous step to DNS pool in IPv4 domain
  • DNS server in IPv4 domain replies to BIG-IP
  • BIG-IP performs appropriate transformation such as changing from A to AAAA query and changing IPv4 address to IPv6 address before forwarding DNS answer back to IPv6 client. BIG-IP uses fix 96-bit prefix address concatenate with 32-bit IPv4 address to form new dynamic IPv6 address. This steps is once again done by DNS64 iRule.
  • Once IPv6 client gets the answer to AAAA query (as IPv6 destination address). It opens connection to the IPv6 destination address. Traffic to the IPv6 destination must be routed through BIG-IP
  • BIG-IP receives IPv6 traffic from client, perform src and dst address translation, and forwards to IPv4 network. This step is done by NAT64 iRule.

Change in version 2:-

  • It forwards AAAA first, then check the AAAA response. If AAAA response has RCODE=3 (error), forward to client. Otherwise, check if response contain any AAAA in answer section. (sometimes, response contains CNAME but not AAAA answer) If there is no AAAA answer, drop and send A query.
  • If there is no answer to AAAA query within period of time, send A query.
  • Rewrite DNS compression pointer to appropriate value
  • Not rewrite additional section (according to dns64-draft)

Please be noted. This iRule is still in experimental stage and it may contains redundant routine. Any comment/feedback is welcome.

Code :

# virtual server configuration
virtual dns64 {
   pool dns64
   destination 2001:123::1.domain
   ip protocol udp
   rules dns64
}
virtual nat64 {
   translate address enable
   snat automap
   destination 2002:123::.any
   mask ffff:ffff:ffff:ffff:ffff:ffff::
   rules nat64
}
# Pool for DNS server in IPv4 domain
pool dns64 {
   members 172.27.4.209:domain {}
}
# NAT64 iRule
when CLIENT_ACCEPTED {
    node [string range [IP::addr  [IP::local_addr]  mask ::ffff:ffff] 0 end]
}

# DNS64 iRule
when RULE_INIT {
    set static::prefix "200201230000000000000000"
    # timeout in milliseconds
    set static::timeout 2000
    set static::type_a [binary format S 1]
    set static::type_aaaa [binary format S 28]
}
when CLIENT_DATA {

    # first send AAAA as is
    set original_request [UDP::payload]

    set after_id [ after $static::timeout {
        binary scan ${original_request} SSSSSS id flags qdcount ancount nscount 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.
        # also assume no pointer (dns compression) here
        while { $qdcount > 0 } {
            binary scan ${original_request} @${index}c count
            while { $count != 0 } {
                incr index [expr $count + 1]
                binary scan ${original_request} @${index}c count
            }
            incr index
            binary scan ${original_request} @${index}SS qtype qclass
            # change to A record
            if { $qtype == 28 } {
                set modified_request [binary format a* \
                    [string replace ${original_request} ${index} [expr ${index} + 1] ${static::type_a} ]]
            }
            incr index 4
            incr qdcount -1
        }
        serverside { UDP::respond ${modified_request} }
        unset original_request
    } ]
}
when SERVER_DATA {
    # cancel after id (server responds before timeout
    after cancel $after_id
    if { [info exists original_request] } {
        # assume this is a response to AAAA query
        binary scan [UDP::payload] SSSSSS id flags qdcount ancount nscount arcount
        set rcode [ expr $flags & 0xf ]
        if { $rcode == 3 } {
            # forward error response to client
            return
        } 
        # 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
            while { $count != 0 } {
                incr index [expr $count + 1]
                binary scan [UDP::payload] @${index}c count
            }
            incr index
            binary scan [UDP::payload] @${index}SS qtype qclass
            # change A(1) to AAAA(28) record
            if { $qtype == 1 } {
                UDP::payload replace ${index} 2 ${static::type_aaaa}
            }
            incr index 4
            incr qdcount -1
        }
        # The Answer, Authority and Additional Sections
        while { $ancount > 0 } {
            binary scan [UDP::payload] @${index}cc count pointer
            set loop 0
            while { $count != 0 and $loop < 30 } {
                incr loop
                set pointer_prefix [expr ($count >> 6) & 0x3]
                set pointer_index [expr (($count &0x3f)<<8) | $pointer  ]
                if { $pointer_prefix == 3 } {
                    incr index 2
                    break
                } else {
                    incr index [expr $count + 1]
                    binary scan [UDP::payload] @${index}cc count pointer
                }
            }
            binary scan [UDP::payload] @${index}SSIS qtype qclass ttl rdlength
            incr index 10
            if { $qtype == 28 } {
                # forward dns error to client
                log local0.alert "forward as is 2"
                return
            }
            incr index $rdlength
            if { $ancount > 0 } {
                incr ancount -1
            } elseif  { $nscount > 0 } {
                incr nscount -1
            } else {
                incr arcount -1
            }
        }
        # when server return RCODE other than 3 with no AAAA answer, send A query
        binary scan ${original_request} SSSSSS id flags qdcount ancount nscount 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.
        # also assume no pointer (dns compression) here
        while { $qdcount > 0 } {
            binary scan ${original_request} @${index}c count
            while { $count != 0 } {
                incr index [expr $count + 1]
                binary scan ${original_request} @${index}c count
            }
            incr index
            binary scan ${original_request} @${index}SS qtype qclass
            # change to A record
            if { $qtype == 28 } {
                set modified_request [binary format a* \
                    [string replace ${original_request} ${index} [expr ${index} + 1] ${static::type_a} ]]
            }
            incr index 4
            incr qdcount -1
        }
        if { [info exists modified_request] } {
            UDP::respond ${modified_request}
            unset original_request
            UDP::drop
        }
        
    } else {
        # assume this is a response to A query
        set a_index_list ""
        
        binary scan [UDP::payload] SSSSSS id flags qdcount ancount nscount 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
            while { $count != 0 } {
                incr index [expr $count + 1]
                binary scan [UDP::payload] @${index}c count
            }
            incr index
            binary scan [UDP::payload] @${index}SS qtype qclass
            # change A(1) to AAAA(28) record
            if { $qtype == 1 } {
                UDP::payload replace ${index} 2 ${static::type_aaaa}
            } elseif { $qtype == 28 }  {
                # answer to AAAA is not expected at this stage, drop it
                UDP::drop
                return
            }
            incr index 4
            incr qdcount -1
        }
        # The Answer, Authority and Additional Sections
        while { $ancount > 0 || $nscount > 0 || $arcount > 0} {
            binary scan [UDP::payload] @${index}cc count pointer
            set loop 0
            while { $count != 0 and $loop < 30 } {
                incr loop
                set pointer_prefix [expr ($count >> 6) & 0x3]
                set pointer_index [expr (($count &0x3f)<<8) + ($pointer & 0xff)  ]
                if { $pointer_prefix == 3 } {
                    set save_pointer $pointer_index
                    foreach a $a_index_list {
                        if { $pointer_index < $a } {
                            break
                        }
                        incr pointer_index 12
                    }
                    # rewrite DNS compression pointer to appropriate value
                    if { $pointer_index > $save_pointer } {
                        UDP::payload replace $index 2 [binary format S [expr $pointer_index | 0xc000]]
                    }
                    incr index 2
                    break
                } else {
                    incr index [expr $count + 1]
                    binary scan [UDP::payload] @${index}cc count pointer
                }
            }
            # if it is root record
            if { $loop == 0 } { incr index }
            binary scan [UDP::payload] @${index}SSIS qtype qclass ttl rdlength
            incr index 10
            # only modify answer section
            if { $qtype == 1 && $ancount > 0 } {
                lappend a_index_list $index
                
                # change A to AAAA record
                binary scan [UDP::payload] @${index}cccc a b c d
                # change length from 4 to 6
                set rdlength 16
                # change IPv4 to IPv6
                UDP::payload replace [expr ${index} - 10] 14 \
                    [binary format SSISH24cccc 28 $qclass $ttl $rdlength $static::prefix $a $b $c $d]
            } elseif { $qtype == 2 } {
                # rewrite dns compression in RDATA
                # add more condition here (now it only check for qtype=2 (NS)
                binary scan [UDP::payload] @${index}a${rdlength} rdata 
                for { set x 0 } { $x < [expr $rdlength - 1] } { incr x } {
                    binary scan $rdata x${x}cc count pointer
                    set pointer_prefix [expr ($count >> 6) & 0x3]
                    set pointer_index [expr (($count &0x3f)<<8) + ($pointer & 0xff)  ]
                    if { $pointer_prefix == 3 } {
                        set save_pointer $pointer_index
                        foreach a $a_index_list {
                            if { $pointer_index < $a } {
                                break
                            }
                            incr pointer_index 12
                        }
                        # rewrite DNS compression pointer to appropriate value
                        if { $pointer_index > $save_pointer } {
                            UDP::payload replace [expr $index + $x] 2 [binary format S [expr $pointer_index | 0xc000]]
                        }
                    }
                }
            }

            incr index $rdlength
            if { $ancount > 0 } {
                incr ancount -1
            } elseif  { $nscount > 0 } {
                incr nscount -1
            } else {
                incr arcount -1
            }
        }
    }
}
Published Mar 18, 2015
Version 1.0

Was this article helpful?

1 Comment

  • Hi , Is it possible to handle the same scenario ( allows client from IPv6 network to communicate to IPv4 network thru BIG-IP ) in LTM without iRule ? I mean by creating a DNS profile and activating "DNS IPv6 to IPv4" = Secondary and "IPv6 to IPv4 Additional Section Rewrite" = Any in it and assing it to IPV4.0 virtual server ?