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.0Nat_Thirasuttakorn
Employee
Joined September 25, 2004
Nat_Thirasuttakorn
Employee
Joined September 25, 2004
1 Comment
Sort By
- Ehsan_A_329941
Nimbostratus
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 ?