Forum Discussion

RoutingLoop_179's avatar
Mar 07, 2013

Dynamic 1:1 SNAT irule

Hi guys - looking for some feedback regarding my irule for 1:1 dynamic NAT. e.g we might use 10/8 on the client inside but then have only have a /17 pool on public outside, although over allocated this works because not all the 10/8 are allocated at one time and after an idle timeout the address is returned to the NAT pool for reuse. The clients can come from an address anywhere within 10/8 so we can't do a simple octet for octet swap as I've seen some other irule examples. We actually do this currently on our CISCO ASA's e.g. http://www.cisco.com/en/US/docs/security/asa/asa82/configuration/guide/nat_dynamic.htmlwp1078939

 

basically a client from within subnet 10/8 will get dynamically allocated a 1:1 NAT until they finish using it for x time. so I've written this rule to handle that, as still reasoably new to irules pretty sure it's not the most efficient way, so interested on feedback and/or if other people have tried to acheive similar.

 

I've use subtables to store a client IP against a 1:1 SNAT address and use the idle timeout of the table as the dynamic SNAT timeout. If there is an existing entry in my client NAT table it uses the returned value as the SNAT address. But If there isn’t an entry in client NAT table for that client, it then loops through my range of external IP addresses to look for a free IP in my NAT reservation table. When it finds free SNAT address it then reserves this for the client and adds an entry to client NAT table. Then SNATs the client as normal, then subsequent client hits will then use the existing entry in the client NAT table.

 

 

irule attached

 

 

11 Replies

  • is it required to use same source port number?

    if not, may we use table/subtable command to store these variables instead of global variable? so, the irule will be cmp-compatible.

    set ::oct3 0
    set ::oct4 1
    

    just my 2 cents.
  • is it required to use same source port

     

    The only requirement is that the client has the same SNAT IP each time and that SNAT IP is only associated with that single client - this is primarily for layer 3 protocols such as GRE/PPTP etc which don't care about ports. Haven't tested the theory but I would open up packet filter on the outside interface to allow incoming IP protocols coming back in, SNAT does this on a port level dynamically but we'll need to also do it for layer 3 protocols (not necessarily dynamic as happy to do it via packet filters on the interfaces). Suppose the behavior is more like a static 1:1 NAT on LTM i.e once setup it lets everything through for that client_IP:NAT_IP mapping.

     

     

    if not, may we use table/subtable command to store these variables instead of global variable? so, the irule will be cmp-compatible

     

    I'm happy with any way to improve it or make it more compatible. This was a bit of a first attempt. What would be ideal is a way to specify the pool subnet as a single variable and it loops through all the IPs in the subnet to find a free one, but i can't think of a way to do it other than the way I've done and specifying first and last values of each octect. I have done a similar thing in PHP with mysql DB, by converting the IP's subnet range etc to decimal and looping through that way, but don't think that very efficient in terms of network traffic as it'll need to keep converting the IP back and forward.

     

     

    I've being trying to think of a way of incorporating more NAT pools - i.e Pool1 is full, look in say Pool2 for free IP etc. and also I've just been trying to think how i could make it more generic with regards to the size of the pool. This attempt is designed around a pool size which only affects last two octets so I suppose theoretically a maximum /16. Anyway to improve, advance it etc is great. I'm dong this a bit of a theory, as F5 CG-NAT module doesn't support it and it it's pretty important for us going forward.

     

     

    As ever thanks for your feedback.

     

    Adrian

     

     

     

     

  • reposting irule code on request - !note! code in two halves:

     

    timing on for testing
    timing on
    when RULE_INIT {
        set ::oct3 0
        set ::oct4 1
        set static::oct3end 0
        set static::oct4end 255
        set static::timeout 600
        set static::debug 1
    
     }
    
    when CLIENT_ACCEPTED {
    
        debugging
        if {$static::debug} {
    
            foreach key [table keys -subtable "ClientDynNat" -notouch] {
            log local0. "table ClientDynNat key: $key"
        }
        }
    
    if { [set SnatTo [table lookup -subtable "ClientDynNat" [IP::client_addr]]] ne "" } {
         to reset idle timeout of dynamic NAT address so it's not re-allocated until client SNAT times out.
        table lookup -subtable "DynNatAddreses" $SnatTo
    
        log local0. "Found exisiting snat in ClientDynNat for [IP::client_addr]: [table lookup -notouch -subtable "ClientDynNat" [IP::client_addr]]"
    
        debugging
        if {$static::debug} {
        log local0. "table lookup in DynNatAddress for $SnatTo: [table lookup -notouch -subtable "DynNatAddreses" $SnatTo]"
        log local0. "table remaining ClientDynNat for [IP::client_addr]: [table timeout -subtable "ClientDynNat" -remaining [IP::client_addr]]"
        log local0. "table remaining DynNatAddress for $SnatTo: [table timeout -subtable "DynNatAddreses" -remaining $SnatTo]"
        }
    
        snat $SnatTo
    
        log local0. "deleting dynnats in tables"
        table delete -subtable "ClientDynNat" -all
        table delete -subtable "DynNatAddreses" -all   
    
    } else {
    
        debugging
        if {$static::debug} {
            foreach key [table keys -subtable "DynNatAddreses" -notouch] {
                log local0. "table DynNatAddreses key: $key"
            }
                log local0. "DynNatAddress table count -- [table keys -subtable "DynNatAddreses" -count]"
                log local0. "no snat for [IP::client_addr]"
        }
  • cont...

    loop through subnet until octet3 has reached it's maximum allocated value 
        while {$::oct3 <= $static::oct3end} {
            set ip 217.39.$::oct3.$::oct4
    
            if { [table lookup -notouch -subtable "DynNatAddreses" $ip] eq "" } {
    
                log local0. "Free IP: $ip found added to clientDynNat table"
    
                reserve the Free IP
                table add -subtable "DynNatAddreses" $ip [IP::client_addr] $static::timeout
                allocate the Free IP as client SNAT address
                table add -subtable "ClientDynNat" [IP::client_addr] $ip $static::timeout
    
            debugging
            if {$static::debug} {
                log local0. "table lookup in DynNatAddress for $ip: [table lookup -notouch -subtable "DynNatAddreses" $ip]"
                log local0. "table remaining ClientDynNat for [IP::client_addr]: [table timeout -subtable "ClientDynNat" -remaining [IP::client_addr]]"
                log local0. "table timeout DynNatAddreses for $ip: [table timeout -subtable "DynNatAddreses" -remaining $ip]"
            }   
                SNAT the client to the free IP
                snat $ip
                 break the loop - found and allocated a address so no point in cycling through rest of Pool of IP's
                break           
            }
            increment the last octect of pool
            incr ::oct4
            if last octect has reached the end of the range increment next octet of IP and start looping through again
            if {$::oct4 > $static::oct4end} {
                incr ::oct3
                set ::oct4 0
            }
        }   
    }
    
    
    
    }
    
    • Vladimir_Bojko1's avatar
      Vladimir_Bojko1
      Historic F5 Account
      Hi, I have checked your iRule and it works quite well. the only thing that can happen is a race condition when two clients at the same time lookup the same IP and then write their IPs in the sable. to prevent this you anc use following statemen instead: if { [table add -subtable "DynNatAddreses" $ip [IP::client_addr] $static::timeout] eq "[IP::client_addr]" } { log local0. "Free IP: $ip found added to clientDynNat table" allocate the Free IP as client SNAT address table add -subtable "ClientDynNat" [IP::client_addr] $ip $static::timeout When you use the "table add" statement instead. If the entry already exists it is not inserted. If the entry does not exist it will add the table entry. Anyhow the response is the key value. You hae to check if the response equals the client IP you try to acces. If so add the second table entry, if not go to the next step.
    • Vladimir_Bojko1's avatar
      Vladimir_Bojko1
      Historic F5 Account
      here my iRule: timing on for testing timing on when RULE_INIT { set ::oct3 1 set ::oct4 1 set static::oct3end 1 set static::oct4end 255 set static::timeout 60 set static::debug 1 } when CLIENT_ACCEPTED { debugging if {$static::debug} { foreach key [table keys -subtable "ClientDynNat" -notouch] { log local0. "table ClientDynNat key: $key" } } if { [set SnatTo [table lookup -subtable "ClientDynNat" [IP::client_addr]]] ne "" } { to reset idle timeout of dynamic NAT address so it's not re-allocated until client SNAT times out. table lookup -subtable "DynNatAddreses" $SnatTo log local0. "Found exisiting snat in ClientDynNat for [IP::client_addr]: [table lookup -notouch -subtable "ClientDynNat" [IP::client_addr]]" debugging if {$static::debug} { log local0. "table lookup in DynNatAddress for $SnatTo: [table lookup -notouch -subtable "DynNatAddreses" $SnatTo]" log local0. "table remaining ClientDynNat for [IP::client_addr]: [table timeout -subtable "ClientDynNat" -remaining [IP::client_addr]]" log local0. "table remaining DynNatAddress for $SnatTo: [table timeout -subtable "DynNatAddreses" -remaining $SnatTo]" } snat $SnatTo log local0. "deleting dynnats in tables" table delete -subtable "ClientDynNat" -all table delete -subtable "DynNatAddreses" -all } else { debugging if {$static::debug} { foreach key [table keys -subtable "DynNatAddreses" -notouch] { log local0. "table DynNatAddreses key: $key" } log local0. "DynNatAddress table count -- [table keys -subtable "DynNatAddreses" -count]" log local0. "no snat for [IP::client_addr]" } loop through subnet until octet3 has reached it's maximum allocated value while {$::oct3 <= $static::oct3end} { set ip 217.39.$::oct3.$::oct4 if { [table add -subtable "DynNatAddreses" $ip [IP::client_addr] $static::timeout] eq "[IP::client_addr]" } { log local0. "Free IP: $ip found added to clientDynNat table" allocate the Free IP as client SNAT address table add -subtable "ClientDynNat" [IP::client_addr] $ip $static::timeout debugging if {$static::debug} { log local0. "table lookup in DynNatAddress for $ip: [table lookup -notouch -subtable "DynNatAddreses" $ip]" log local0. "table lookup in ClientDyn Nat for [IP::client_addr]: [table lookup -subtable "ClientDynNat" [IP::client_addr]]" log local0. "table remaining ClientDynNat for [IP::client_addr]: [table timeout -subtable "ClientDynNat" -remaining [IP::client_addr]]" log local0. "table timeout DynNatAddreses for $ip: [table timeout -subtable "DynNatAddreses" -remaining $ip]" } SNAT the client to the free IP snat $ip break the loop - found and allocated a address so no point in cycling through rest of Pool of IP's break } increment the last octect of pool incr ::oct4 if last octect has reached the end of the range increment next octet of IP and start looping through again if {$::oct4 > $static::oct4end} { incr ::oct3 set ::oct4 0 } } } }
    • Vladimir_Bojko1's avatar
      Vladimir_Bojko1
      Historic F5 Account
      In my case I needed also to allow incomming connections on to the clinet IPs. So I had to do only Address translation without port translation. To do this I defined one network virtual server on the external VLAN of type performanceL4. Destination IP is the IP range you defined in the iRule, here: 217.39/16. Important: You have to enable Address translation, which is per default disabled with netwrok virtuals here an example: ltm virtual CGNAT1-incomming { destination 217.39.0.0:any mask 255.255.0.0 profiles { fastL4 { } } rules { dynamic_NAT_incomming } source 0.0.0.0/0 translate-port disabled vlans { external } vlans-enabled vs-index 29 } then you add following iRule to it: when CLIENT_ACCEPTED { node [table lookup -subtable "DynNatAddreses" [IP::local_addr]] } now the 1:1 IP address translations works bidirectional. btw, testet with 11.4.1 regards
  • Be sure to use a non SP-DAG configured platform. With SP-DAG enabled VLAN, the IP address is linked to a TMM. If you try to SNAT with a different IP than the one linked to the landing TMM, you will have TMM to TMM packets forwarding, which can impact the overall performance.

     

  • I´ve a similar scenario, which I do today with a 1: 1 snat list, however, I´ve more than 500 IPs to use in POOL, only that while one NAT IP is in use it cannot be used by another. As I understand it, this iRule has this role, does anyone have it working in a stable production environment? If so, can you share?