This section explains various Datascript functions and their configuration.

Log Custom Data

Create a local log with custom data. This log is visible in the application logs and can be sent to remote logging servers if the user-defined filter is configured in the virtual service’s analytics profile. This logged data will be appended to standard log data generated for the request.

avi.vs.log("This is a custom log. The clients IP address is " .. avi.vs.client_ip())

Persist on JsessionID

The following example looks at a server-generated cookie, using the value to persist the client to the server for an hour. This use case can also be performed through the native app-cookie persistence method.

Add the following to the HTTP request.

if avi.http.get_cookie("OAM_JSESSIONID") then
  if avi.vs.table_lookup(avi.http.get_cookie("OAM_JSESSIONID")) then
    avi.pool.select(avi.vs.table_lookup(avi.http.get_cookie("JSESSIONID")))
  end
end

Add the following to the HTTP response.

if avi.http.get_cookie("JSESSIONID") ~= "" then
  avi.vs.table_insert(avi.http.get_cookie("JSESSIONID"), avi.pool.server_ip(), 3600)
end

Redirect HTTP to HTTPS

If the client is accessing a secure section of a website and is not using SSL, then redirect them to HTTPS using a 302 Redirect.

if string.beginswith(avi.http.get_path(), "/secure") and avi.vs.port() ~= "443" then
  avi.http.redirect("https://" .. avi.http.hostname() .. avi.http.get_uri(), 302)
end

Blocklist Enforcement of IP Addresses

Block clients coming from a designated blocklist group based on their client IP address. To implement this rule, create an IP group matching the name used in the following rule:

var = avi.vs.client_ip()
if avi.ipgroup.contains("IP-Group", var) then
  avi.vs.log("Blacklisted IP" .. var.. "attempting to log in")
  avi.http.close_conn()
end

Content Switching — Basic

This rule directs clients to a pool based on an exact match of the HTTP path.

if avi.http.get_path() == "/sales/" then
  avi.pool.select("sales-pool")
  elseif avi.http.get_path() == "/dev/" then
    avi.pool.select("dev-pool")
    elseif avi.http.get_path() == "/marketing/" then
      avi.pool.select("marketing")
end

Content Switching — Advanced

This rule directs clients to different pools or servers within a pool based on a match against their path:

if string.beginswith(avi.http.get_path(), "/sales/") then
  avi.pool.select("sales-pool")
  elseif avi.http.get_path() == "/engineering/" then
    avi.pool.select("engineering-pool", "apache1")
    elseif avi.http.get_path() == "/marketing/" then
    avi.pool.select("marketing", "10.10.31.201")
end

Content Switching — Large List

For extremely long lists, it is preferable to separate the DataScript code from the data it uses. This makes adding or modifying the list of mappings easy without giving users (or automation API calls) access to the underlying rule. This approach takes advantage of a string group, which contains the list and is referenced by the DataScript.

The string group must be set to key/value pair, with sample entries such as:

  • /marketing marketing-pool

  • /dev dev-pool

  • /checkout default-pool

  • /nonprod nonprod-pool

  • /test nonprod-pool

  • /sales sales

val, match = avi.stringgroup.beginswith("StringGroup", avi.http.get_path())
if match then
  avi.pool.select(val)
else
  avi.pool.select("default-pool")
end

Rewrite Client Request

Add a query to the request based on the country code of the HTTP hostname.

For instance, if the site is www.avi.es/path/index.html?query=true, the new request would be www.avi.es/path/index.html?es=0123&query=true.

host = avi.http.host()
-- Create an array of mappings
TLD_map =

{es="123", fi="456", cn="789"}
if not host then
-- The host header does not exist, so close the connection
  avi.http.close_conn()
elseif string.sub(host, 1, #"www.avi.") == "www.avi." then
  i,j = string.find(host, "avi")
  TLD = string.sub(host, j+2, -1)
  new_query = "?mandate=" .. TLD_map[TLD]
  old_query = avi.http.get_query()

if old_query > 0 then
  new_query = new_query .. "&" .. old_query
end
avi.http.redirect("www.avi.com" .. avi.http.get_path() .. new_query, 302)
end

Generate a Different Redirect URL, Based on Different Host Names

Check if the hostname begins with www.avi. then get the ccTLD from the host. Form the beginning part of the new query using the ccTLD map table to get the value of the query argument mandate. Append the old query, if it exists, to the new query. Generate the HTTP redirect to the new URL using the domain www.avi.com and the new query.

local ccTLD_map = {es="01345F", fi="5146FF", cn="45DFX", io="123456", ly="ABC123"}
host = avi.http.host()

if not host then
  avi.http.close_conn()
elseif string.sub(host, 1, #"www.avi.") == "www.avi." then
  i,j = string.find(host, "avi")
  ccTLD = string.sub(host, j+2, -1)
  new_query = "?mandate=" .. ccTLD_map[ccTLD]
  old_query = avi.http.get_query()

  if #old_query > 0 then
    new_query = new_query .. "&" .. old_query
  end
    avi.http.redirect("www.avi.com" ..
      avi.http.get_path() ..
      new_query, 302)
end

Rewrite Publicly Available Hosts & URLs to Private Hosts and URLs &dmash

This can be a common requirement for OpenShift environments, since routes are not typically open and accessible directly from the internet.

host = avi.http.get_header("Host")
path = avi.http.get_path()
if string.contains(host, "rhmap.global.acme.com") and string.beginswith (path, "/router/test") then
   avi.vs.log("router/test match")
   avi.http.replace_header ("Host", avi.http.get_path_tokens(3,3) ..".".."apps.test-a.0341.o2.we1.csl.cd2.acme.com")
   avi.vs.log ("router/test new header")
   if avi.http.get_path_tokens(4) then
        avi.http.set_path ("/"..avi.http.get_path_tokens(4))
     else
        avi.http.set_path ("/")
   end
 elseif string.contains(host, "rhmap.global.acme.com") and string.beginswith (path, "/router/dev") then
   avi.vs.log("router/dev match")
   avi.http.replace_header ("Host", avi.http.get_path_tokens(3,3) ..".".."apps.test-a.0341.o2.we1.csl.cd2.acme.com")
   avi.vs.log ("router/dev new header")
   if avi.http.get_path_tokens(4) then
        avi.http.set_path ("/"..avi.http.get_path_tokens(4))
   else
        avi.http.set_path ("/")
   end
elseif string.contains(host, "rhmap.global.acme.com") and string.beginswith (path, "/core") then
   avi.vs.log("core match")
   avi.http.replace_header ("Host", "rhmap.apps.test-a.0341.o2.we1.csl.cd2.acme.com")
   avi.vs.log ("core new header")
      avi.http.set_path ("/"..avi.http.get_path_tokens(2))
else
   avi.vs.log ("nothing matches")
end

Block Client IPs that Exceed the Limit of Failure Responses

Note the following configurable settings in the script:

  • The failure responses are set to 400, 401, 402, 403, 404, and 405.
  • MAX_FAILCOUNT - The number of failure responses allowed within a period of time (FAILCOUNT_DURATION) before the IP is blocked.
  • FAILCOUNT_DURATION - The time period in seconds during which an IP is allowed to have the maximum number of failure responses without being blocked. The minimum value for this setting is 300 seconds (5 minutes).
  • BLOCKLIST_DURATION - The time period in seconds that an IP will be blocked after too many failure responses. The maximum value for this setting is 32400 seconds (9 hours).

HTTP Request Event:

failcount_table="FAILCOUNT"
blocklist_suffix="BLK"
client_ip = avi.vs.client_ip()
headers = avi.http.get_header()
-- iterate across set of headers
for header,value in pairs(headers) do
  if header=="true-client-ip" then
    hdr_value=value
    avi.vs.log(hdr_value)
  end
end
avi.vs.log(hdr_value)
client_ip_blk_key = hdr_value .. blocklist_suffix

blk_status, blk_remaining_time = avi.vs.table_lookup(failcount_table, client_ip_blk_key, 0)
if blk_status == 'BLOCKLIST' then
	  msg = "Client IP " .. hdr_value .. " in penalty box for another ".. blk_remaining_time ..  " seconds"
   	avi.vs.log(msg ..  " - respond")
   	avi.http.response(429, {content_type="text/plain"}, msg .. "\n")
   	-- avi.http.close_conn()
end

HTTP Response Event:

MAX_FAILCOUNT=100
FAILCOUNT_DURATION=300
BLOCKLIST_DURATION=600
-- Minimum value of AVI table entry lifetime/expiry-time is 300 secs (5 mins) and Maximum value is 32400 (9 hrs)
failcount_table="FAILCOUNT"
blocklist_suffix="BLK"
client_ip = avi.vs.client_ip()
headers = avi.http.get_header()
-- iterate across set of headers
for header,value in pairs(headers) do
  if header=="true-client-ip" then
    hdr_value=value
    avi.vs.log(hdr_value)
  end
end
client_ip_blk_key =hdr_value .. blocklist_suffix

function Set (list)
  local set = {}
  for _, l in ipairs(list) do set[l] = true end
  return set
end

local items = Set { "400", "401", "402", "403" , "404", "405" }
response = avi.http.status()

if items[response] then
    fail_count, fc_remaining_time = avi.vs.table_lookup(failcount_table, hdr_value, 0)
    blk_status, status_remaining_time = avi.vs.table_lookup(failcount_table, client_ip_blk_key, 0)

    -- Increment the counter and check if the counter exceeded MAX_FAILCOUNT.

    if (fail_count == nil) and (blk_status == nil) then
      avi.vs.log("New Client IP " .. hdr_value .. "to the failcount table")
      avi.vs.table_insert(failcount_table, hdr_value, 1, FAILCOUNT_DURATION)
      avi.vs.table_insert(failcount_table, client_ip_blk_key, 'MONITOR' , FAILCOUNT_DURATION)
    elseif (tonumber(fail_count) >= MAX_FAILCOUNT) and (blk_status == 'MONITOR') then
      avi.vs.log("Client IP " .. hdr_value .. "exceeded the failcount adding to blocklist")
      avi.vs.table_remove(failcount_table, client_ip_blk_key)
      avi.vs.table_insert(failcount_table, client_ip_blk_key, 'BLOCKLIST', BLOCKLIST_DURATION)
    elseif (blk_status == nil) then
      avi.vs.log("Client IP " .. hdr_value .. " refresh timer")
      avi.vs.table_insert(failcount_table, hdr_value, 1, FAILCOUNT_DURATION)
      avi.vs.table_insert(failcount_table, client_ip_blk_key, 'MONITOR' , FAILCOUNT_DURATION)
    elseif (tonumber(fail_count) < MAX_FAILCOUNT) and (blk_status == 'MONITOR') then 
      avi.vs.log("Client IP: " .. hdr_value .. " incremented fail_count: " .. tonumber(fail_count) + 1 .. " remaining time: " .. status_remaining_time)
      avi.vs.table_remove(failcount_table, hdr_value)
      count = tonumber (fail_count) + 1
      avi.vs.table_insert(failcount_table, hdr_value, tostring(count), FAILCOUNT_DURATION)
    end
end