We get this question often in the iRules v9.x forum:

"I created an iRule that is supposed to choose a host based on the URI (extension, hostname, agent, method, etc), but it doesn't work like I expected. It seems to be logging what I expect to see, and the first request always seems to go to the right place, but then it almost seems to be ignoring my iRule logic. Is there something wrong with my iRule?"

In most cases, there is actually nothing wrong with the iRule.

What's Really Happening?

Here's what's really happening: The HTTP client is opening a Keep-Alive connection and sending multiple requests on the same connection. Since LTM, by default, load balances CONNECTIONS (not REQUESTS), it is treating this connection like any other TCP connection -- directly proxying each client connection to a single server socket, and maintaining this one-to-one relationship between client and server for the life of the client connection.

"But", you say, "my iRule says to choose a new server!"

"But", I say, "the load balancing decision has already been made, and from LTM's perspective that's water under the bridge as long as the server-side connection remains healthy."

"But", you say, "my iRule says to choose a new server!"

Well, there are a couple of ways to make LTM re-consider its load balancing decision mid-connection. One way is to explicitly detach from the existing server before specifying a new one. (Search for "LB::detach" in the iRules v9.x forum for examples, or check out the LB::detach wiki page.) The other is a bit simpler and adds efficiency you couldn't otherwise build into your iRule: Enable OneConnect.

"OneConnect?", you say. "What does that have to do with choosing a new server?"

I know it's not exactly intuitive, but let's look for a minute at how OneConnect works.

How OneConnect Works (condensed version)

OneConnect was originally designed to allow sharing of server-side HTTP keep-alive connections which otherwise would sit idle or time out waiting for that single client to make a new HTTP request (connection pooling) and also to allow distribution of requests matching different criteria to different servers (content switching).

Without OneConnect enabled, the default LTM behaviour is to persist to the first server selected for the life of the connection.

With OneConnect enabled, LTM actively manages each server-side connection to keep it as busy as possible and serve as many requests as possible before requiring a new handshake. Once a response is sent and the server-side connection goes idle, the client-side connection is maintained as usual, waiting for a new request. The connection table entry associating the client to that server is removed, and the server-side connection is kept open and marked available for re-use. This automatic detachment from the server-side flow could be replicated as mentioned above using the LB::detach command.

Since the server-side connection is automatically detached once each response is complete, it can then be shared serially among multiple client requests. When a new request arrives on a client connection, a server-side connection is automatically chosen or established in the appropriate pool (which may or may not be the same server-side connection which handled the last request from that client connection). This automatic re-selection without a new server-side handshake is the secret sauce that you can't otherwise code into an iRule, allowing requests from the same client-side connection to be efficiently served by different servers, transparent to the client.

Enabling OneConnect

There are a couple of iRules commands in the ONECONNECT namespace, but they're not what you need in this case. (More on that in a future article.)

OneConnect is enabled by simply applying a OneConnect profile to the virtual server. You can find them in Local Traffic / Profiles / Other.

OneConnect configured with the default mask of 0.0.0.0 will result in the most efficient connection pooling, allowing any idle server-side connection to be re-used for any new client-side request, significantly reducing the number of server-side connections. However, re-used server-side connections retain the source IP of the original client, which results in some very misleading server log entries unless you also SNAT all connections.

A OneConnect profile with host mask (255.255.255.255) will allow parsing of all requests and server-side connections will only be re-used for the same client. Without SNAT, OneConnect with a host mask (255.255.255.255) keeps the source address info in the server logs consistent with reality.

You can specify any mask that seems appropriate for your traffic, but OneConnect with any mask will be more efficient than no OneConnect at all, since handshake overhead for your servers will be reduced.

Want more?

You can read more about the OneConnect feature and configuration options in the LTM manual on AskF5.

Here's the post where unRuleY made me smarter about OneConnect & re-selection. (Only a little more than a year ago... how long is that in iRule years?) It's an excellent discussion of the details.

And here's another interesting post in which a customer needed to prevent evaluation of all but the first request in a Keep-Alive HTTP connection.

pz
/deb