Load Balancing Passive FTP on Avi Vantage

Overview

This article explains configuring Avi virtual service to load balance passive FTP.

In passive FTP, the client sends a PASV command to the server on port 21. The server responds with the server IP address data port that is greater than 1023 to connect to. On using a virtual IP on the load balancer for passive FTP, the server IP has to be changed to the virtual IP on the load balancer so that the client connects to the load balancer instead of connecting to the server directly. A DataScript is used for changing the server IP to a virtual IP configured in the FTP payload in the server response.

Starting with NSX Advanced Load Balancer version 22.1.1, the FTP can be configured using Native FTP profile. For more details, refer to Native FTP Profile guide. This KB is specific to Non-Native Passive FTP profile or Passive FTP on standard L4 Application profile.

Configuring Avi Vantage

To configure Avi Vantage for load balancing passive FTP, follow the steps below:

  1. Configuring health monitor for FTP
  2. Configuring pool with the required FTP servers
  3. Configuring Layer 4 response DataScript for FTP
  4. Configuring Layer 4 virtual service with port configuration for the datachannel

Configuring Health Monitor

To configure an external health monitor for FTP, on Avi UI navigate to Templates > Profiles > Health Monitors and click on Create.

  • Enter a name for the health monitor.
  • Select External option from Type drop-down list.
  • Enter a relevant value in the Send Interval field.

Under External Settings,

  • Enter port number 21 in the Health Monitor Port field.
  • Paste the below bash script for FTP health monitor in the Script Code section.
 
#!/bin/bash
    curl -s ftp://$IP/$path --ftp-pasv -u $user:$pass
 
  • Enter the username, password, and the filepath in the Script Variables section.

step1

The filepath is the absolute path of the file to be checked in the health monitor. The curl opens up an FTP connection using the username and password provided to the servers in the pool and requests for a directory listing in the path provided. The curl runs in silent mode (as specified by the option -s) and returns a directory listing output only if a file exists in the filepath and the health monitor will pass. If no file exists in the filepath, the health monitor will fail. The path is optional, and if not specified, the curl will retrieve the root directory listing.

Configuring Pool

To configure the pool with required FTP servers, on Avi UI navigate to Applications > Pools and click on Create Pool.

  • Enter a name for the pool.
  • Enter port number 21 in the Default Server Port field.
  • Under Load Balance select Consistent Hash > Source IP Address.

Consistent Hash with Source IP Address is chosen as the load balancing algorithm to avoid a different server being selected by each SE if a virtual server is scaled out to multiple Service Engines.

  • Click on +Add Active Monitor and from the dropdown list select the health monitor configured in the previous step - FTP.

step2-a

  • Navigate to the Servers tab and add the relevant servers.

  • Navigate to the Advanced tab. Under Other Settings, click on the checkbox for Disable Port Translation to enable the option.

The FTP data channel will be established on an ephemeral port and this port has to be used to send the traffic to the server without any modification. Hence, Disable Port Translation has to be enabled.

step2-b

Configuring DataScript

To configure the Layer 4 response DataScript, on Avi UI navigate to Templates > Scripts > DataScripts and click on Create.

Add the below DataScript to the VS Datascript Evt L4 Response Event Script section and click on Save.


-- Handle passive FTP 227 response rewrite (server IP to VIP)
function string.tohex(str)
	return (str:gsub('.', function (c)
    	return string.format('%02X', string.byte(c))
	end))
end
-- Do not run DS for data ports
if avi.vs.port() ~= '21' then
	avi.l4.ds_done()
end
-- Read entire payload (assumption that entire response we are looking for is a single packet)
local payload = avi.l4.read()
local p1, p2 = string.match(payload, '227 Entering Passive Mode %(%d+,%d+,%d+,%d+,(%d+),(%d+)%)%.\r\n')
if p1 ~= nil then
	local vip_ip = string.gsub(avi.vs.ip(), '%.', ',')
	local rewrite = '227 Entering Passive Mode (' .. vip_ip .. ',' .. p1 .. ',' .. p2 ..').\r\n'
	avi.l4.modify(string.tohex(rewrite))
	rewrite_len = rewrite:len()
	payload_len = payload:len()
	if rewrite_len < payload_len then
    	avi.l4.discard(payload_len-rewrite_len, rewrite_len)
	end
end  

step3

Configure Virtual Service

To configure a Layer 4 virtual service for FTP, on Avi UI navigate to Applications > Virtual Service, click on Create Virtual Service, and select Advanced Setup.

Under Profiles,

  • For Application Profile, select System-L4-Application option from the drop-down list.
  • For TCP/UDP Profile, System-TCP-Proxy option from the drop-down list.

Under Service Port, click on Switch to Advanced. Under Services, enter the port range as 1024 TO 65534, and enter port 21.

Note: From a security perspective, it is recommended to identify the specific passive port range configured on the FTP servers and to configure this port range under the Virtual Service rather than the full range of high ports.

Under Pool, select the pool configured option as FTP from the drop-down list

Click on Next.
In the Policies tab, under DataScripts, click on + Add DataScript. From the drop-down, select FTP-DataScript option, the DataScript configured in the previous section.

Note: If the FTP server has the option to override the IP address that server will advertise in response to the PASV command. The datascript should not be needed if the FTP server is configured to advertise the VIP address in response to the PASV command.

Click on Save DataScript.

For instance, the following is the CLI for l4-fte-response DataScript:


-- Handle passive FTP 227 response rewrite (server IP to VIP)
function string.tohex(str)
	return (str:gsub('.', function (c)
        return string.format('%02X', string.byte(c))
	end))
end
-- Do not run DS for data ports
if avi.vs.port() ~= '21' then
	avi.l4.ds_done()
end
-- Read entire payload (assumption that entire response we are looking for is a single packet)
local payload = avi.l4.read()
local p1, p2, tail = string.match(payload, '227 Entering Passive Mode %(%d+,%d+,%d+,%d+,(%d+),(%d+)%)(.?\r\n)')
if p1 ~= nil then
	local vip_ip = string.gsub(avi.vs.ip(), '%.', ',')
	local rewrite = '227 Entering Passive Mode (' .. vip_ip .. ',' .. p1 .. ',' .. p2 .. ')' .. tail
	avi.l4.modify(string.tohex(rewrite))
	rewrite_len = rewrite:len()
	payload_len = payload:len()
	if rewrite_len < payload_len then
        avi.l4.discard(payload_len-rewrite_len, rewrite_len)
	end
end

Click on Next to navigate to the next two tabs and Save the configuration.

Additional Configuration

The FTP servers can enforce that the control and data connections are sourced from the same IP. Hence, the Service Engines that load balances the control and data traffic should be the same. This can be achieved by deploying the Service Engines in an active/standby high availability mode.

For a deployment in active/active mode with native Layer 2 scaleout, to ensure the same Service Engine load balances the traffic to the FTP servers, configure the following on the virtual service using the CLI:


[admin:10-10-10-1]: > configure virtualservice virtual-service-name
[admin:10-10-10-1]: virtualservice> flow_dist consistent_hash_source_ip_address
[admin:10-10-10-1]: virtualservice> save
 

Note: On using BGP/ ECMP scaleout, as in the deployment for FTP load balancing in Azure or GCP, the flow will reach the Service Engines based on the routing hash done on the upstream device. Hence, the above CLI configuration is not applicable for BGP/ ECMP scaleout.

The virtual service is now ready for load balancing FTP. The FTP server IP for clients would be the VIP configured on the FTP virtual service.