vADC Docs

Tunnelling multiple protocols through the same port

by on ‎04-26-2013 04:48 AM - edited on ‎07-08-2015 12:52 PM by PaulWallace (6,296 Views)

In Stingray, each virtual server is configured to manage traffic of a particular protocol.  For example, the HTTP virtual server type expects to see HTTP traffic, and automatically applies a number of optimizations - keepalive pooling, HTTP upgrades, pipelines - and offers a set of HTTP-specific functionality (caching, compression etc).

 

A virtual server is bound to a specific port number (e.g. 80 for HTTP, 443 for HTTPS) and a set of IP addresses.  Although you can configure several virtual servers to listen on the same port, they must be bound to different IP addresses; you cannot have two virtual servers bound to the same IP: port pair as Stingray will not know which virtual server to route traffic to.

 

"But I need to use one port for several different applications!"

 

Sometimes, perhaps due to firewall restrictions, you can't publish services on arbitrary ports.  Perhaps you can only publish services on port 80 and 443; all other ports are judged unsafe and are firewalled off. Furthermore, it may not be possible to publish several external IP addresses.

 

You need to accept traffic for several different protocols on the same IP: port pair.  Each protocol needs a particular virtual server to manage it;  How can you achieve this?

 

The scenario

 

Let's imagine you are hosting several very different services:

 

  • A plain-text web application that needs an HTTP virtual server listening on port 80
  • A second web application listening for HTTPS traffic listening on port 443
  • An XML-based service load-balanced across several servers listening on port 180
  • SSH login to a back-end server (this is a 'server-first' protocol) listening on port 22

 

Clearly, you'll need four different virtual servers (one for each service), but due to firewall limitations, all traffic must be tunnelled to port 80 on a single IP address.  How can you resolve this?

 

The solution - version 1

 

The solution is relatively straightforward for the first three protocols.  They are all 'client-first' protocols (see Feature Brief: Server First, Client First and Generic Streaming Protocols), so Stingray can read the initial data written from the client.

 

Virtual servers to handle individual protocols

 

First, create three internal virtual servers, listening on unused private ports (I've added 7000 to the public ports).  Each virtual server should be configured to manage its protocol appropriately, and to forward traffic to the correct target pool of servers.  You can test each virtual server by directing your client application to the correct port (e.g. http://stingray-ip-address:7080/), provided that you can access the relevant port (e.g. you are behind the firewall):

 

tunnel2.png

For security, you can bind these virtual servers to localhost so that they can only be accessed from the Stingray device.

 

A public 'demultiplexing' virtual server

 

Create three 'loopback' pools (one for each protocol), directing traffic to localhost:7080, localhost:7180 and localhost:7443.

 

Create a 'public' virtual server listening on port 80 that interrogates traffic using the following rule, and then selects the appropriate pool based on the data the clients send.  The virtual server should be 'client first', meaning that it will wait for data from the client connection before triggering any rules:

 

tunnel3.png

 

# Get what data we have...
$data = request.get();

# SSL/TLS record layer:

# handshake(22), ProtocolVersion.major(3), ProtocolVersion.minor(0-3)
if( string.regexmatch( $data, '^\026\003[\000-\003]' )) {

   # Looks like SSLv3 or TLS v1/2/3

   pool.use( "Internal HTTPS loopback" );

}

if( string.startsWithI( $data, "<xml" )) {

   # Looks like our XML-based protocol

   pool.use( "Internal XML loopback" );

}

if( string.regexmatch( $data, "^(GET |POST |PUT |DELETE |OPTIONS |HEAD )" )) {

   # Looks like HTTP

   pool.use( "Internal HTTP loopback" );

}

log.info( "Request: '".$data."' unrecognised!" );
connection.discard();

 

The Detect protocol rule is triggered once we receive client data

 

Now you can target all your client applications at port 80, tunnel through the firewall and demultiplex the traffic on the Stingray device.

 

The solution - version 2

 

You may have noticed that we omitted SSH from the first version of the solution.

 

SSH is a challenging protocol to manage in this way because it is 'server first' - the client connects and waits for the server to respond with a banner (greeting) before writing any data on the connection.  This means that we cannot use the approach above to identify the protocol type before we select a pool.

 

However, there's a good workaround.  We can modify the solution presented above so that it waits for client data.  If it does not receive any data within (for example) 5 seconds, then assume that the connection is the server-first SSH type.

 

First, create a "SSH" virtual server and pool listening on (for example) 7022 and directing traffic to your target SSH virtual server (for example, localhost:22 - the local SSH on the Stingray host):

 

tunnel4.png

 

Note that this is a 'Generic server first' virtual server type, because that's the appropriate type for SSH.

 

Second, create an additional 'loopback' pool named 'Internal SSH loopback' that forwards traffic to localhost:7022 (the SSH virtual server).

 

Thirdly, reconfigure the Port 80 listener public virtual server to be 'Generic streaming' rather than 'Generic client first'.  This means that it will run the request rule immediately on a client connection, rather than waiting for client data.

 

Finally, update the request rule to read the client data.  Because request.get() returns whatever is in the network buffer for client data, we spin and poll this buffer every 10 ms until we either get some data, or we timeout after 5 seconds.

 

# Get what data we have...
$data = request.get();

$count = 500;

while( $data == "" && $count-- > 0 ) {

   connection.sleep( 10 ); # milliseconds

   $data = request.get();

}

if( $data == "" ) {

   # We've waited long enough... this must be a server-first protocol

   pool.use( "Internal SSH loopback" );

}

# SSL/TLS record layer:

# handshake(22), ProtocolVersion.major(3), ProtocolVersion.minor(0-3)

if( string.regexmatch( $data, '^\026\003[\000-\003]' )) {

   # Looks like SSLv3 or TLS v1/2/3

   pool.use( "Internal HTTPS loopback" );

}

if( string.startsWithI( $data, "<xml" )) {

   # Looks like our XML-based protocol

   pool.use( "Internal XML loopback" );

}

if( string.regexmatch( $data, "^(GET |POST |PUT |DELETE |OPTIONS |HEAD )" )) {

   # Looks like HTTP

   pool.use( "Internal HTTP loopback" );

}

log.info( "Request: '".$data."' unrecognised!" );
connection.discard();

 

This solution isn't perfect (the spin and poll may incur a hit for a busy service over a slow network connection) but it's an effective solution for the single-port firewall problem and explains how to tunnel SSH over port 80 (not that you'd ever do such a thing, would you?)

 

Read more

 

Contributors