SNI Name-based Pool Selection in L4 Proxy

Overview

Starting with Avi Vantage 18.2.5, it is possible to load balance a layer 4 application based on a server name in the SNI extension on TLS client hello. The DataScript ensures that it is invoked after having sufficient bytes to inspect the TLS payload. Once load balancing is done, it stops invoking DataScript again for subsequent packets in the same connection.

Avi UI Configuration

  • Navigate to Templates > Scripts

Templates Scripts screen

  • Click on Create. The below DataScript set editor window will appear. Key in a value for Name.

DataScript set editor

  • Scroll down to expose the VS Datascript Evt L4 Request Event Script field. If you select the Upload File option, follow the instructions to upload a file. To confirm the intended script has been uploaded, select the Enter Text option. You will see the contents of the file in the scrolling window. It will look something like the below.

DataScript uploaded and visible

  • At the bottom of the screen, pull down the protocol parser menu and select Default-TLS. That parser needs to be attached to the DataScript so that the script can avail itself of TLS parsing capabilities and API calls.

    Protocol parser pulldown

  • Click Save when done.

Sample DataScript

The Default-TLS protocol parser needs to be selected. By associating this DataScript with the virtual service, the first TLS packet from the client — which ideally is a client hello — will get inspected by the DataScript.

local avi_tls = require "Default-TLS"
-- Buffer minimum of 20 bytes of data
buffered = avi.l4.collect(20)
-- Read the gathered payload
payload = avi.l4.read()
-- Obtain the length of the payload
len = avi_tls.get_req_buffer_size(payload)
if ( buffered < len ) then
-- Wait till len bytes are collected at TCP layer
  avi.l4.collect(len)
end
-- Verify if client hello and handshake.
if ( avi_tls.sanity_check(payload) ) then
-- Parse the TLS payload
   local h = avi_tls.parse_record(payload)
-- Obtain the SNI name
   local sname = avi_tls.get_sni(h)
   if sname == nil then
      avi.vs.log('SNI not present')
      avi.vs.close_conn()
   else
      avi.vs.log("SNI=".. sname)
      avi.vs.persist(sname)
   end
else
   avi.vs.close_conn()
end
-- Stop invoking DataScript after the first payload.
avi.l4.ds_done()
avi_tls = nil

In this example, the SNI name is obtained by parsing the TLS payload.

avi_tls.parse_record parses the payload and the helper API avi_tls.get_sni(h) is used to obtain the SNI name.

This SNI name can be now used to select a pool server or load balance and create a persistence entry for the same.

Alternatively, this can also be used to select a specific pool using avi.pool.select("pool_name").

Note: The pool name and SNI name need not be the same, however, for certain use cases the SNI name could be same as pool name to select a pool based on the SNI name