How to have your client and server too:
I want to address a few things before delving into how to solve this problem, because there are some twists and turns ahead.
First, this configuration does make sense since azzurrow has a forwarding virtual server. There's no real load balancing going on here; we already know what the server is from the original TCP destination address. The LTM in this setup is acting as a sort of transparent proxy.
Second, there have been some very clever attempts at solving this problem, and I'd like to commend you all for trying to come up with ways to use the tools at your disposal. Thanks also to hoolio for succinctly explaining the serverside command.
Third, as rfcarr mentions, there is a CR for implementing timers, and we're working on implementing them, but no promises for when they'll be released. However, a timer is fundamentally a bad way to solve this problem as it would introduce some amount of unnecessary and annoying latency into connections, and would result in weird timing issues that are frustrating and hard to debug.
Fortunately, the theoretical solution is at hand. As explained above, the LTM already knows what the server is as soon as the client connects to it. So in theory, it could connect to the server and see which side sends it data first. The problem, as you have all discovered, is that TCP::collect (which you will need to call in order to inspect the data) prevents the serverside connection from being established.
There is a way around this, and azzurrow, you were shockingly close to what it is. The TCP::collect command will take a (currently undocumented) second numeric parameter, which is not a timeout value, but rather a "skip" value, a number of bytes to skip (allow through) before doing a collect. By providing this, the serverside connection is not held up and will be initiated normally. In this case, we don't actually want to skip any bytes, so we can specify a "0", and this will fortunately still initiate a serverside connection. Now, this parameter isn't documented yet for a reason: this mode of operation is not well-tested. Until relatively recently, I suspect it was never used, in fact. So using this parameter carries a high risk of unexpected behavior. I'd highly recommend extensive testing before deployment. Also, several bugs related to this were fixed in v9.4.5, so I'd strongly recommend an upgrade to that version before using it. If that is absolutely not an option, the at the very least you should build your iRule on v9.4.5 and then test on whatever version you can use in production to see if it still works for you. More caveats are discussed below.
So, now that you know this, how can you use it? Here's a simple example:
when CLIENT_ACCEPTED {
TCP::collect 20 0
}
when SERVER_CONNECTED {
TCP::collect 20
}
when CLIENT_DATA {
log local0. "Got client data first"
event disable all
}
when SERVER_DATA {
log local0. "Got server data first"
event disable all
}
This rule will log a message as to which side sent data first, and not much else. How about something more complicated? I have not tested this precise version of the code, but I have used similar iRules, and this does pass the syntax checker:
when CLIENT_ACCEPTED {
TCP::collect 8 0
set server_hold 0
log local0. "Buildup -- collecting data"
}
when CLIENT_DATA {
Since we're here, the clientside sent data first
log local0. "Got client data"
Extract the HTTP command, if any.
Find the first space in the text, and trim off
any characters after it. If there's no space,
$spcpos will be -1, so [string range] will
return the empty string.
set spcpos [string first " " [TCP::payload 8]]
set command [string range [TCP::payload 8] 0 $spcpos]
Now see if this is a recognized HTTP command
if { [matchclass $command equals ::http_cmds] } {
log local0. "HTTP traffic -- command $command"
if { $server_hold eq 1 } {
serverside { TCP::release }
}
event SERVER_CONNECTED disable
event SERVER_DATA disable
return
}
log local0. "Not HTTP -- staying in passthru"
event disable all
}
when SERVER_CONNECTED {
log local0. "Server connection established"
TCP::collect
set server_hold 1
}
when SERVER_DATA {
log local0. "Banner protocol -- staying in passthru"
clientside { TCP::release }
TCP::release
event disable all
}
This detects HTTP connections by extracting the command and comparing it against a http_cmds class, very similarly to what you wanted your original rule to do. The server_hold logic is to handle the case where the client might send us data before the serverside connection has completed (so SERVER_CONNECTED may or may not have fired by the time CLIENT_DATA does).
However, despite the fact that this iRule is looking for HTTP data, note that there are no HTTP events or commands here. This rule will not work if you have an HTTP profile on your virtual, as the HTTP profile will also prevent a serverside connection from being initiated. The clever way around that would be to run HTTP::disable in CLIENT_ACCEPTED and then run HTTP::enable after you knew you were dealing with HTTP traffic... except that in many versions of LTM, HTTP::enable just doesn't work. This is something else that was fixed in v9.4.5, so if true HTTP functionality is something you need, then v9.4.5 is the way to go. Again, this may work in earlier versions (like v9.3.x), but it may crash your box instead, so "test, test, test" is the order of the day. As I said earlier, v9.4.5 also fixes some other problems with using this mode of operation, so even if you don't need HTTP but you do want to do something fancy like use the "virtual " command, or LB::detach or something, then you'll still need to upgrade to that version.
I hope this helps.