It is possible to load balance a layer four 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 for subsequent packets in the same connection.

Configuring through UI

The following are the steps to configure using NSX Advanced Load Balancer UI:

  • Navigate to TemplatesScriptsDataScripts

  • Click CREATE. The NEW DATASCRIPT SET window will appear. Key in a value for Name.

  • Click ADD amd scroll down to view the EVENTS field.

  • You can import the file by clicking on IMPORT FILE button.

  • You can also specify the description in the Description field.

  • In the Protocol Parser field, select Default-TLS from the drop-down menu. That parser needs to be attached to the DataScript so that the script can avail itself of TLS parsing capabilities and API calls.

  • Click SAVE.

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 SNI name not present, close connection
if sname == nil then
avi.vs.log('SNI not present')
avi.vs.close_conn()
else
avi.vs.log("SNI=".. sname)
avi.pool.select(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 can be same as pool name to select a pool based on the SNI name.

Pool Group Selection based on SNI Name

The same DataScript can be used to select previously created Pool Groups having the same name as the SNI name. The avi.poolgroup.select(sname) API can be used to select a Pool Group based on the SNI name. The SNI can also be appended to a string in the avi.pool.select() and avi.poolgroup.select() API.

Pool group already created with PG-SNI

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 SNI name not present, close connection
if sname == nil then
avi.vs.log('SNI not present')
avi.vs.close_conn()
else
avi.vs.log("SNI=".. sname)
avi.poolgroup.select("PG-"..sname)
end
else
avi.vs.close_conn()
end
– Stop invoking DataScript after the first payload.
avi.l4.ds_done()
avi_tls = nil