Port forwarding, SOCKS5 & HTTP proxy

In addition to device access relayed through the Service, RADKit can also provide direct access to TCP ports on a device through either Port Forwarding or SOCKS5/HTTP proxy. This is helpful for certain services that do not run natively through the connector, e.g., WebUI access to a SD-WAN vManage controller or an ISE appliance. This page describes how to achieve such access with Port Forwarding and SOCKS5 proxy.

Known limitations:

  • The jumphost in device setting is ignored in port forwarding. It means that port forwarding will be established directly to the device host even when jumphost is specified. This may change in a future release.

  • The SOCKS5 proxy does not resolve any domain names that do not follow the <device_name>.<service_serial>.proxy pattern. It means that if the page contains absolute URLs that are pointing to external JS, CSS, images, etc. accessible only from the device’s local network, those will not be resolved.

Service configuration

A prerequisite for port forwarding is setting up port forwarding capabilities in RADKit Service. By default, port forwarding is not allowed. It is necessary to explicitly add individual ports or port ranges that are meant to be accessible on a per-device basis.

The list of forwarded TCP ports follows this syntax:

  • en empty field means that port forwarding is disallowed for this particular device;

  • a single port is defined as an integer: 8000, 8080 etc.

  • port ranges can be defined as integers separated by a dash: 1-1024, 1000-2000 etc.

  • multiple entries are separated by a semicolon: 8000;1-1024;8080;1050-2000

The forwarded TCP ports can be configured when adding or editing a device in the RADKit Service WebUI:

../_images/edit_device_new.png

Client operation

Port forwarding

The port forwarding feature allows you to bridge connections from a local port on the host running the RADKit Client to a destination port on a device behind the RADKit Service.

Danger

Exposed local ports are unprotected (there is no way to add an authentication layer, as these are raw TCP sockets). Please use with caution. It is recommended to always restrict the local address to localhost, to avoid using this feature on shared hosts, and to stop the port forwarding as soon as it is no longer needed.

Starting port forwarders

To start port forwarding, use the Device.forward_tcp_port() method which returns a radkit_client.sync.port_forwarding.PortForwarder object`:

>>> port_forwarder = csr1.forward_tcp_port(local_port=4443, destination_port=443)

>>> port_forwarder.status
<PortForwarderStatus.RUNNING: 'RUNNING'>

The forwarded port can then be accessed on localhost:

$ openssl s_client -showcerts -connect localhost:4443
CONNECTED(00000005)
...

Note

In the case of port forwarding, if credentials are required in order to access the service on the forwarded port, those credentials will have to be shared with the user behind the RADKit Client. Even if the RADKit Service has correct device credentials configured for the forwarded protocol, it cannot inject those credentials - the TCP connection is between the Client and the device. Temporary credentials will have to be shared set up on the device and shared with the user behind the RADKit Client for the duration of the troubleshooting session.

Monitoring port forwarders

There are several ways to check the state and statistics for active port forwarders:

  • Via the port forwarder object itself:

    >>> port_forwarder
    [RUNNING] <radkit_client.sync.port_forwarding.TCPPortForwarder object at 0x1150a52e0>
    
    ----------------  --------------
    status            RUNNING
    serial            xxxx-yyyy-zzzz
    device_name       csr1
    local_port        4443
    destination_port  443
    #active           0
    #failed           0
    #closed           0
    #total            0
    bytes up          0
    bytes down        0
    exception         None
    ----------------  --------------
    
  • At the Device, Service or Client level, to see all port forwarders created for a given object:

    >>> csr1.port_forwards
    <radkit_client.sync.state.TCPPortForwardsDict object at 0x1139164f0>
    
    Port forwards for device: csr1
    
    key    status    serial          device_name    local_port    destination_port    #active    #failed    #closed    #total    bytes up    bytes down    exception
    -----  --------  --------------  -------------  ------------  ------------------  ---------  ---------  ---------  --------  ----------  ------------  -----------
    0      STOPPED   xxxx-yyyy-zzzz  csr1           4443          443                 0          0          3          3         6331        4909          None
    1      RUNNING   xxxx-yyyy-zzzz  csr1           4443          443                 0          0          0          0         0           0             None
    
    >>> service.port_forwards
    ...
    
    >>> client.port_forwards
    ...
    

For deeper insights you can check each individual connection made through the port forwarder:

  • To list all of the connections:

    >>> port_forwarder.connections
    <radkit_client.sync.port_forwarding.Connections object at 0x10bd372b0>
    Tunneled connections made at localhost:4443 to dev:8443:
    
    key    uuid                                  endpoint   status    opened                      closed                      exception
    -----  ------------------------------------  ---------  --------  --------------------------  --------------------------  --------------------------------------------------------------------------------
    0      8904bb0c-f75a-48ce-a4cd-3cb693a21a5e  dev:8443   FAILED    2023-02-15 09:52:36.323209  2023-02-15 09:52:37.008297  Performing action failed: Tunnel fail error: OS error: Connection refused [de...
    1      604771f5-64fc-426f-9ae0-1f9e95845644  dev:8443   FAILED    2023-02-15 09:52:36.324510  2023-02-15 09:52:37.008004  Performing action failed: Tunnel fail error: OS error: Connection refused [de...
    
  • To check details of a particular connection:

    >>> port_forwarder.connections[0]
    <radkit_client.async_.port_forwarding.Connection object at 0x10b6b7550>
    
    ---------  --------------------------------------------------------------------------------
    uuid       8904bb0c-f75a-48ce-a4cd-3cb693a21a5e
    endpoint   dev:8443
    status     FAILED
    opened     2023-02-15 09:52:36.323209
    closed     2023-02-15 09:52:37.008297
    exception  Performing action failed: Tunnel fail error: OS error: Connection refused [de...
    ---------  --------------------------------------------------------------------------------
    

Controlling port forwarders

Every port forwarder can be in one of three states, from radkit_client.sync.port_forwarding.PortForwarderStatus:

  • RUNNING - port forwarding is active

  • STOPPED - forwarding stopped, can be resumed at any time

  • FAILED - there was an exception

The current status can be checked through the PortForwarder.status attribute.

To stop or start a port forwarder, use PortForwarder.start() or PortForwarder.stop().

>>> port_forwarder.status
<PortForwarderStatus.RUNNING: 'RUNNING'>

>>> port_forwarder.stop()

>>> port_forwarder.status
<PortForwarderStatus.STOPPED: 'STOPPED'>

SOCKS5 proxy

RADKit also provides a SOCKS5 proxy service that allows you to connect a web browser or any other compatible software to a device on a given port (or set of ports) at runtime. The SOCKS proxy takes advantage of both RADKit’s TCP tunneling and HTTP proxying capabilities.

The proxy server can be started on the Client by calling Client.start_socks_proxy() and passing in the proxy port and proxy credentials as arguments:

>>> socks_proxy = client.start_socks_proxy(4000, username="radkit-user", password="sEcret!123")

>>> socks_proxy
[RUNNING] <radkit_client.sync.port_forwarding.ProxyPortForwarder object at 0x10666bb20>

--------------  --------
status          RUNNING
local_port      4000
#active         0
#failed         0
#closed         0
#total          0
protocol        SOCKS_V5
bytes up        0
bytes down      0
exception       None
--------------  --------

Danger

You can also create an unprotected SOCKS5 proxy, but it is highly recommended that you enable authentication for production use cases. In the following example, we will enable SOCKS5 proxy without requiring proxy credentials, but limit proxy access with the local_address parameter set to localhost only (the default):

>>> socks_proxy = client.start_socks_proxy(4000, local_address="localhost")

Note

Some browsers do not support SOCKS authentication. In that case, restrict to localhost.

The SOCKS proxy resolves FQDNs that match any of these patterns:

  • <device_name>.<service_serial>.tcp.proxy.

  • <device_name>.<service_serial>.http.proxy.

  • index.proxy (overview of all services).

The TCP proxying will tunnel direct TCP connections to the actual device on the given port number. This means it will support any protocol on top of TCP. It also means that if a TLS certificate is presented, it’s the device itself that will terminate the TLS connection. The RADKit service won’t interfere and establish any authentication to the device.

The HTTP proxying on the other hand, uses the HTTP forwarding capabilities from RADKit. This means that for known device types, the RADKit service will intercept the connections and establish an authenticated session. HTTP proxying is only made available on port 80 and 443. When port 443 (HTTPS) is used, the TLS certificate that is presented is a local certificate from radkit_client; not a certificate from the device. Any incoming HTTP request here is handled by the SOCKS proxy and turned into a RADKit HTTP request.

Warning

DNS delegation of the .proxy TLD to the SOCKS proxy must be enabled in the browser. Without it, device/service names will not resolve correctly. Make sure to exclude any normal DNS domains that should not be proxied.

Go to http://index.proxy or https://index.proxy to see a web interface with an overview of all services.

Example URL to access device test-vm on Service xxxx-yyyy-zzzz:

http://test-vm.xxxx-yyyy-zzzz.tcp.proxy/

Example browser network settings with SOCKS5 proxy enabled:

Example 1 (Firefox - manual):

../_images/ff_socks_proxy.png

Example 2: (Firefox - PAC file):

PAC file is available under this link: https://prod.radkit-cloud.cisco.com/pac. It accepts two optional query string parameters: browser and port. By default, the PAC file uses port 4000 and detects the browser automatically.

../_images/ff_socks_proxy_pac.png

After establishing connections through the SOCKS5 proxy to a device, you can check the proxy statistics using:

>>> client.socks_proxy
[RUNNING] <radkit_client.sync.port_forwarding.ProxyPortForwarder object at 0x110cd6700>

--------------  --------
status          RUNNING
local_port      4000
#active         2
#failed         6
#closed         11
#total          19
protocol        SOCKS_V5
bytes up        173164
bytes down      16655528
exception       None
--------------  --------

The SOCKS5 proxy can be stopped by calling Client.stop_socks_proxy(). Alternatively, since the SOCKS5 proxy object is also a PortForwarder, it can be controlled in the same way as shown in Controlling port forwarders.

Additionally the SOCKS proxy object provides a dedicated property which lists all the connections grouped by endpoint:

>>> client.socks_proxy.endpoint_connections
<radkit_client.sync.port_forwarding.EndpointConnectionsDict object at 0x1049ceb60>
Tunneled endpoint connections:

endpoint                          #active    #failed    #closed    #total
--------------------------------  ---------  ---------  ---------  --------
test-vm.xxxx-yyyy-zzzz.proxy:443  0          2          0          2

And each connection endpoint contains the list of all of the connections started for this particular endpoint:

>>> client.socks_proxy.endpoint_connections['test-vm.xxxx-yyyy-zzzz.proxy:443'].connections
<radkit_client.async_.port_forwarding.ConnectionsDict object at 0x106fbe830>

Connections:

key    uuid                                  endpoint                          status    opened                      closed                      exception
-----  ------------------------------------  --------------------------------  --------  --------------------------  --------------------------  --------------------------------------------------------------------------------
0      72eaae03-33b0-4859-80f5-c815483c5724  test-vm.xxxx-yyyy-zzzz.proxy:443  FAILED    2023-02-15 10:04:54.578336  2023-02-15 10:04:59.752738  Performing action failed: Tunnel fail error: OS error: Connection refused [te...
1      a66804a6-df2c-43d4-97a1-90688259abff  test-vm.xxxx-yyyy-zzzz.proxy:443  FAILED    2023-02-15 10:04:54.830709  2023-02-15 10:05:00.052461  Performing action failed: Tunnel fail error: OS error: Connection refused [te...

HTTP proxy

Similar to the SOCKS proxy, RADKit client can also behave like an HTTP proxy. Start the HTTP proxy like this:

>>> http_proxy = client.start_http_proxy(4001, username="radkit-user", password="sEcret!123")

[RUNNING] <radkit_client.sync.port_forwarding.ProxyPortForwarder object at 0xffff81435fa0>
----------  -------
status      RUNNING
local_port  4001
#active     0
#failed     0
#closed     6
#total      6
protocol    HTTP
bytes up    0
bytes down  0
exception   None
----------  -------

Use http://localhost:4001 as an HTTP proxy in your browser.
Then navigate to: https://index.proxy/

Now, we can use http://localhost:4001 for both the HTTP_PROXY and HTTPS_PROXY in a web browser. When configured, https://index.proxy should show the proxy index, similar to the SOCKS proxy.

Like for SOCKS, a PAC file is available for HTTP under this link: https://prod.radkit-cloud.cisco.com/pac?protocol=HTTP. It accepts two optional query string parameters: browser and port. By default, the PAC file uses port 4000 and detects the browser automatically.

The HTTP proxy can be stopped by calling Client.stop_http_proxy().