SOCKS5 SSL Persistence
Problem this snippet solves:
Much requested 2005 iRule contest winner (thanks Adam!)
The judges said: "This iRule addresses application persistence challenges associated with a proprietary service. By handling SOCKS and SSL protocol negotiation through binary rewrites, this iRule not only reduces server outages and increases customer satisfaction, it is a great example of the unique, far-reaching power of iRules and TMOS."
The iRule first responds with the SOCKS 5 handshake so that it can get the next packet and persist based on the session identifier. When load-balancing the request, it proxies the relevant portion of the client’s initial handshake and removes the servers response to the handshake since we already spoofed that to the client earlier, and finally adds a persistence entry for the session id.
Code :
#Written by Adam Kramer (akramer@netifice.com) for Netifice Corporation
#July, 2005
when CLIENT_ACCEPTED {
TCP::collect 2
}
when CLIENT_DATA {
# read initial socks handshake –
# the version number, and the number of auth methods supported
binary scan [TCP::payload] cc socksver numauthmethods
if { $socksver != 5 } {
log local0. "Got non-socks connection from client [IP::remote_addr]"
reject
return
}
# set offset to the beginning of the second packet (SSL negotiation)
set offset [expr {2 + $numauthmethods}]
if { [TCP::payload length] == $offset } {
#only respond if exactly the right amount of data was sent
TCP::respond [binary format H2H2 05 86]
TCP::collect [expr {$offset + 1}]
return
}
# more data than the offset, this means we got the first packet of the SSL negotiation
if { [TCP::payload length] > $offset} {
# 4 bytes is the length of the SOCKS SSL header, 1 byte gets to the SSL version field
# another 41 bytes past that is the session length, immediately following is the session
# binary scan gracefully handles the string being too short,
# so we can safely read all 3 values here
binary scan [TCP::payload] "x[expr {$offset + 5}]cx41ch32" sslversion sessionlength hexid
if { $sslversion != 3 } {
log local0. "Received wrong SSL version in header from client [IP::remote_addr]"
reject
return
}
if { $sessionlength == 0 } {
# this is a new connection, allow normal server selection
return
} else {
persist universal $hexid
return
}
}
# this should never happen, but a bad client might do it
if { [TCP::payload length] < $offset } {
TCP::collect $offset
return
}
}
when SERVER_CONNECTED {
# send current full payload from client to server, we need server's ssl hello
# also delete client payload - replace returns the replaced characters,
# doing both in one shot saves 50,000 cycles
TCP::respond [clientside {TCP::payload replace 0 [TCP::payload length] ""}]
# 5 bytes should do it, only 2 bytes to the first socks handshake
TCP::collect 5
}
when SERVER_DATA {
# remove initial protocol negotiation since we already did that with client
TCP::payload replace 0 2 ""
# 4 bytes for socks ssl header, 44 for offset of session id
binary scan [TCP::payload] "x48h32" hexid
# need to add a session state for the case where the client didn't send a session ID
persist add universal $hexid
}