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
}
Published Mar 18, 2015
Version 1.0

Was this article helpful?

No CommentsBe the first to comment