Forum Discussion

Matt_Williamson's avatar
Matt_Williamson
Icon for Nimbostratus rankNimbostratus
Mar 29, 2005

iRule to persist on tcp_content

Hi all, I'm trying to write a rule that will search the first 2500 bytes of tcp_content for the "USERNAME=" string and then persist on the next 7 characters after that. Here is my original attempt.

 
 rule client_tcpusrid_persist {  
      when CLIENT_ACCEPTED {  
      set usrid {findstr  [tcp_content(2500) contains "USERNAME="] "USERNAME=" 7 "t"}  
           if {tcp_content(2500) contains "USERNAME="} {  
                persist uie $usrid  
                log local0. "Persisting $userid"  
           }  
      }  
 }  
 

Here's the error I'm getting...

01070151:3: Rule [getfield_test] error: line 1: [undefined procedure: rule] [rule client_tcpusrid_persist {

when CLIENT_ACCEPTED {

set usrid {findstr [tcp_content(2500) contains "USERNAME="] "USERNAME=" 7 "t"}

if {tcp_content(2500) contains "USERNAME="} {

persist uie $usrid

log local0. "Persisting $userid"

}

}

}]

Any help you can give would be great. Thanks.

Matt

10 Replies

  • unRuleY_95363's avatar
    unRuleY_95363
    Historic F5 Account
    Ok, you have a couple of problems.

    The first is that it appears you have typed the "rule {" portion into the GUI text box. When entering rules in the GUI, you must leave out the "rule {}" part of the rule. You enter the at the top and then enter the rule body (the when statements) in the text box.

    When this gets saved into the config file, it then appears with the "rule {...}" part automatically added.

    The second problem is how tcp_content now works in 9.0. In order for the tcp_content (which is really now called TCP::payload) to work, you must first specify how much you want to collect. In your case, it appears that you want to collect 2500 bytes. So, a rule that gathers 2500 bytes would look like this:

     
     when CLIENT_ACCEPTED { 
        TCP::collect 2500 
     } 
     when CLIENT_DATA { 
        set usrid [findstr [TCP::payload] "USERNAME=" 9 "t"] 
        if {$usrid ne ""} { 
           persist uie $usrid 
           log local0. "Persisting $userid" 
        } 
     } 
     

    Hope this helps.
  • Hi,

     

    Could you explain the 9, t, and the ne in your script it seems to work fine but i dont really understand it.

     

    Cheers.

     

  • Check the doc Using UIE Function Commands under Tech Tips. Gives the syntax for the findstr command. An excerpt:

     

     

    findstr

     

    The findstr command finds the string within and returns a sub-string based on the and from the matched location.

     

     

    Note the following;

     

     

    may be either a character or length.

     

    If is not specified, it defaults to zero.

     

    If is not specified, it defaults to the end of the string.

     

    This command (without or is equivalent to the following Tcl command: "string range [string first ] end".

     

    The syntax of the findstr() command is as follows:

     

     

    findstr [ []

     

     

     

    Also, the ne is "not equal", so if the string is usrid has has content, persist on it.

     

     

  • Hi, the script searches for the string "USERNAME=" in the 2500 bytes of tcp data collected. The way the actual stream looks is like this......

     

     

    Displayt..0t..Env.31t..USERNAME=s009999t..Env.30t..

     

     

    The 9 and the "t" tell it to start 9 bytes after "USERNAME=" (since it's 9 bytes long) and stop at "t". This way it's reading "s009999" which is the userid I want to persist on. The value in the "t" position can be what ever fits for you. For the streams I'm looking at they are separted by "t..".

     

     

    Cheers,

     

     

    Matt
  • Thanks, Matt, Jason,

    Jason, I was following your issue with the MSRDP and found it indeed very interesting and was myself quite impressed by the powers of iRules.

    However another question rather basic perhaps ?

    Can you guys tell me what happens in the case of the first TCP::collect, does this execute indefinitely for a TCP connection seen as the TCP::release is for the inner event :

      
     =========================================================== 
     Do not declare the rule in the script this is just for clarity 
     rule NonKeep-alivePersistence { 
      
      Rule entry point 
     when CLIENT_ACCEPTED { 
         TCP::collect 
     } 
     when CLIENT_DATA { 
         TCP::collect 25 
         log local0. "persisting:" 
         set hashKey [findstr [TCP::payload] "X-Client-IP" 13 "\r"] 
      
          if the hashKey is not equal to a whitespace/delimiter 
         if {$hashKey ne ""} { 
             persist uie $hashKey 1200 
             pool TestPool 
             log local0. "Key to persist: $hashKey" 
         } 
          Flush collect data and release event hook point 
         TCP::release 
     } 
     } 
     

    Finally and most importantly the event ClIENT_DATA when used without CLIENT_ACCEPTED does not seem to turn on its own. I got nothing in the snoop or the logs. Can I use CLIENT_DATA to check the data payload of a TCP connection without executing the event on the connection set-up (CLIENT_ACCEPTED) Of course this is crucial if I want to use keep-alive.

    Thanks,

    Vin.
  • Hi again,

    Can you inspect a server response on TCP level, retrieve a value, modify it and send back to client? I need to get the value of a Max-Connections header from a server in an options response. Modify this value to insert the total Max-Connections value for all the servers in the pool and send it on its way back to the client.

    I got the impression from forum that the Wizards like to see a little effort or a hack at a rule from the subscriber so here goes: (its not pretty!)

       
      Rule entry point  
     when SERVER_CONNECTED {  
         TCP::collect  
     }  
     when SERVER_DATA {  
          Collect 30 bytes to see if its an OPTIONS response  
         TCP::collect 30  
         set identifier [findstr [TCP::payload] "OPTIONS" 7 "\r"]  
         if {$identifier ne " "} {  
              if its an OPTIONS response, process the TCP..  
             TCP::collect 250  
              250 bytes should give me the Max-Connections header  
         } else { [[TCP::payload] contains "Max-Connections:"]} {  
             set MaxConn [[TCP::payload] value "Max-Connections:"]  
             TCP::respond $MaxConn  
             log local0. "OptionResponse: $MaxConn"  
         }  
          Flush collect data and release event hook point  
         TCP::release  
     } 
     

    The "Max-Connections: 2000\r\n" header is very simple and tells me how mant TCP connections I can set up on my server.

    I am a little bit weary of TCP::respond, for the moment I have not managed to retrieve the value of Max-Connections.

    Finally I need to be able to modify this value for example by a factor of 10 and return this value to the client.

    Thanks,

    Vin.

  • unRuleY_95363's avatar
    unRuleY_95363
    Historic F5 Account
    In the particular example above, you do not need the TCP::release because there is an implied release if a new collect was not issued in the CLIENT_DATA event. However, doing the TCP::release is certainly not harmful in any way.

     

     

    Anybody going to take a crack at cleaning up Vinny's rule? He did at least hack something together...

     

     

    Some hints:

     

     

    A) You probably only want to do the TCP::collect 250 once in the SERVER_CONNECTED event (you can then get rid of all the TCP::collect's in the SERVER_DATA event).

     

     

    B) Don't use TCP::respond here as it will provide a response to the server. What you will want to do is use:

     

    TCP::payload replace

     

  • Hi unRuleY,

    I was hoping you would reply, I like'd your work on the MSRDP problem.

    Here's the thing, I have 3 different pools of Servers (3 services) I have client infor in the X-Client-IP header (you can refer to my original post,5:07AM) The rule I developed works fine on non persistent TCP as there is only going to be one request with one X-Client-IP in a TCP connection. However when enabling keep-alive and manipulating the appropriate offsets and carriage returns it does not fire.

    Can I use Client data on its own without a client accepted event, how can I loop within the client accepted to retrieve all possible X-Client-IPs in a keep-alive TCP. Here's my hack at it:

       
     when CLIENT_ACCEPTED {  
        TCP::collect   
     }  
     when CLIENT_DATA {  
         For requests arriving using a Content-Length header  
        if { [TCP::payload contains "Content-Length:"] } { 
           set ConLen [TCP::payload value "Content-Length:"] 
           set hashKey [findstr [TCP::payload] "X-Client-IP" 13 "\r"] 
           if {$hashKey ne ""} { 
              persist uie $hashKey 1200 
           } else { [TCP::payload contains "Encapsulated: req-hdr="] } { 
              set hdrStart [TCP::payload value "Encapsulated: req-hdr="] 
              set hashKey1 [findstr [TCP::payload] "X-Client-IP" 13 "\r"] 
              if {$hashKey1 ne ""} { 
                 persist uie $hashKey1 1200 
              } 
           } elseif { [TCP::payload contains "Encapsulated: req-hdr=0, null-body="] } { 
              set hdrOSet [TCP::payload value "Encapsulated: req-hdr=0, null-body="] 
              set hashKey2 [findstr [TCP::payload] "X-Client-IP" 13 "\r"]  
              if {$hashKey2 ne ""} {  
                 persist uie $hashKey2 1200 
              } 
           } 
        } 
        TCP::release 
     } 
     

    As you can see it gets a little complicated with the offsets, so I just want to precise exactly the offset values:

    Encapsulated: req-hdr=0, req-body=412

    The request header starts at 0 and the body at 412 bytes.

    Encapsulated: req-hdr=0, null-body=412

    The request header is 412 bytes, there is no request body.

    The BIP is 9.0.4 I would also appreciate a little help on the server response problem.

    Cheers,

    Vinny.
  • unRuleY_95363's avatar
    unRuleY_95363
    Historic F5 Account
    Wow, that is some complicated iRule you've developed...

    I see a couple of problems. First, is that you do this:

    set ConLen [TCP::payload value "Content-Length:"]

    I'm not sure what you are expecting this to do (and I'm surprised it's not giving you an error). Assuming you want to extract the value of the Content-Length, you more likely want to do this:

    set ConLen [findstr [TCP::payload] "Content-Length:" 15 "\r"]

    The other aspect to understand is that data will not be sent to the server until you issue the TCP::release. However, you may also re-issue a new TCP::collect which will then further collect more data and re-trigger the CLIENT_DATA event. So, it sounds like you would want to determine the size of the initial request, TCP::release that amount and then do another TCP::collect and keep doing that until the connection is closed.

    However, here's another question: How come you can't just use the HTTP profile and then trigger off the HTTP_REQUEST event which already parses HTTP requests/responses? Or is this some non-HTTP protocol?