We run a cluster of directory servers (4 masters, 2 hubs, 14 slaves) behind a set of F5 Bigip load balancers.  Our Bigip admins recently decided to switch the boxes to "one-armed" mode and that services would have to use X-Forwarded-For headers or equivalent to get the actual client IP address.  Obviously, LDAP has no equivalent.

So, I've hacked something together and I'm posting it looking for feedback.  If the stuff is actually usable for other folks, that's good too.

On the load balancer side, the code is specifically for the F5 Bigips.  But if other load balancers have similar abilities to trigger on events and can insert binary data into the datastream, it should be adaptable.

Essentially what I've done is defined a new LDAP Extended Operation with a payload that's a string containing the source IP address of the incoming connection.  The load balancer sends this LDAP operation as soon as it opens a connection to the LDAP server, before any other traffic gets sent.  On the directory server side, I've written an Extended Operation plugin that then logs the string.  All we need is logging, so that's good enough for us.  There's room for improvement there though, like making the address available for IP based ACls (which we don't use).

In order to insert an LDAP operation into the stream, the code on the load balancer needs to choose an LDAP message-id that hopefully the real client isn't going to use.  Going on the assumption that the client will start at 0 and increment up, I had the code insert a message-id of 0x70000000.  I initially thought I'd have to have the code on the load balancer look for the response message and throw it away so the client doesn't see it, but I've found that the clients just seem to ignore it with no ill effects, so I haven't bothered filtering the response out.  The less the code on the load balancer does, the better.

There's a possibility that the client could send it's own xff extended operation, but since the load balancer always sends first, we can just ignore any subsequent log entries.

On the directory server plugin side, I needed to be able to log this to the access log, not to the error log.  The slapi_log_access function isn't declared in the plugin header file, so I had to declare it manually.

Here's what our log entries look like now, for both 636 (SSL) and 389 (cleartext before starttls):

[17/May/2016:15:34:22 -0400] conn=230843 fd=156 slot=156 SSL connection from 130.207.167.12 to 130.207.183.16
[17/May/2016:15:34:22 -0400] conn=230843 op=0 EXT oid="1.3.6.1.4.1.636.2.11.11.1" name="forwarded-for extended op"
[17/May/2016:15:34:22 -0400] conn=230843 op=0 forwarded for 130.207.167.12
[17/May/2016:15:34:22 -0400] conn=230843 op=0 RESULT err=0 tag=120 nentries=0 etime=0

[17/May/2016:15:34:22 -0400] conn=230844 fd=156 slot=156 connection from 130.207.167.12 to 130.207.183.16
[17/May/2016:15:34:22 -0400] conn=230844 op=0 EXT oid="1.3.6.1.4.1.636.2.11.11.1" name="forwarded-for extended op"
[17/May/2016:15:34:22 -0400] conn=230844 op=0 forwarded for 130.207.167.12
[17/May/2016:15:34:22 -0400] conn=230844 op=0 RESULT err=0 tag=120 nentries=0 etime=0

We haven't switched our Bigips yet, so the "connection from" line still shows the actual client IP address.

F5 Bigip code fragments are called "irules" and are written in TCL.  The tar file below contains two different irule files, one for cleartext streams and one for SSL streams.  By SSL streams, I mean where SSL connections from the client are terminated at the load balancer and then re-SSLed to the ldap server.  Sorry, I'm not writing the other kind.