tls-router 🛂
Route incoming TLS clients to backend plaintext TCP servers based on the ALPN (protocol) and SNI (servername) of the TLS handshake.
Tls Router can serve both encrypted (TLS) and plaintext (TCP) traffic on the same port. It detects whether a client is using TLS based on the first three bytes of the first data packet, also known as the TLS "ClientHello" handshake. Plaintext TCP clients are directed to an optional fallback backend.
Performance impact on network throughput should be minimal since all the data processing is handled by the internals of Node.js, specifically OpenSSL and libuv. No streaming data is processed at the JavaScript level.
This tool can be used with any TLS/TCP based connections. Its initial purpose is to allow legacy Gopher servers to transparently support Gopher over TLS (using ALPN) and virtual hosting multiple domains on the same IP address (using SNI).
Usage
Node.js must be available on the system, exposing the global node
, npm
, and npx
commands.
Global Installation
System-wide installation makes the tls-router
command globally available. The trade-off is that only a single version can be installed.
npm install --global tls-router
tls-router [options]
Local Installation
To include tls-router
in a package for Node.js, use local installation to allow dependency versioning.
npm install --development tls-router
... or:
npm install --production tls-router
Then use tls-router
via its API in your application code, or run the local command as npx tls-router
, or use it within an npm-run
script inside your package.json
file.
npx tls-router [options]
... or:
{
"name": "my-app",
"scripts": {
"my-script": "tls-router"
},
"devDepencencies": {
"tls-router": "*"
}
}
npm run my-script -- [options]
On-demand Installation
To run tls-router
as a one-off command, without permanent global installation nor requiring a package.json
project file, use the handy npx
command that comes with Node.js. This downloads and installs into a temporary location, then cleans up after the command terminates. Downloads are cached so subsequent runs should be faster.
npx tls-router [options]
CLI
Example: Gopher over TLS with various ways to configure routing rules.
npx tls-router
# Accept both TLS and TCP clients on port 70
--listen 70
# Offered ALPN names
--alpn gopher
# TLS crypto files
--public-certificate cert.pem
--private-key key.pem
--certificate-authority ca.pem
# TCP connections are piped to a local backend server on port 7777
--plaintext localhost:7777
# ALPN=gopher SNI=example.net to localhost:7000 (DNS lookup)
--route gopher:example.net:localhost:7000
# ALPN=gopher SNI=example.net to 127.0.0.1:7000 (IPv4)
--route gopher:example.net:127.0.0.1:7000
# ALPN=gopher SNI=example.net to [::1]:7000 (IPv6)
--route gopher:example.net:[::1]:7000
# ALPN=* SNI=example.net to 127.0.0.1:7000
--route :example.net:127.0.0.1:7000
# ALPN=gopher SNI=* to 127.0.0.1:7000
--route gopher::127.0.0.1:7000
# all trafic as plaintext to localhost:7000
--route 7000
# all traffic as plaintext to 127.0.0.1:7000 (IPv4)
--route 127.0.0.1:7000
# all traffic as plaintext to [::1]:7000 (IPv6)
--route '[::1]:7000'
# ALPN=* SNI=example.net to 127.0.0.1:7000
--route :example.net:127.0.0.1:7000
CLI options can also be specified using:
- Environment variables with the
TLS_ROUTER_
prefix. E.g.TLS_ROUTER_LISTEN=70
- JSON configuration file whose path is specified with the
--config
option. E.g.--config options.json
For more information run with the options:
-
--help
to see a list of all available options. -
--version
to show the current application version.
API
Example: Accept Gopher over TLS on port 70. All TLS clients are routed to a regular (plaintext, non-TLS) Gopher server on port 7000
. Plaintext TCP clients are routed to a fallback server on port 7777
.
const { TlsRouter } = require('tls-router')
const router = new TlsRouter((rule, client, backend) => {
console.log(`Client proxied to ${backend.port}`)
})
const [key, cert, ca] = await Promise.all([
readFile('./private-key.pem'),
readFile('./public-certificate.pem'),
readFile('./certificate-authority.pem')
])
router.setSecureContext({
key, cert, ca,
ALPNProtocols: ['gopher']
})
router.route({ port: 7000 })
router.plaintext = { port: 7777 }
router.listen(70)
new TlsRouter([options][, routedConnectionListener])
Class: Extends: net.Server
Also implements several methods of, but does not inherit from, tls.TLSServer
.
options
- Passed to the net.Server
constructor. See: https://nodejs.org/api/net.html#net_new_net_server_options_connectionlistener
-
ttfbTimeout
- The maximum time-to-first-byte before a client is disconnected. Default:10000
routedConnectionListener
- Optional handler for the routedConnection
event.
routedConnection
Event: Emitted when an incoming TLS connection is routed to a backend.
Arguments:
-
rule
- The matching rule, if any. This isundefined
if the client is a plaintext connection. See:router.route()
-
client
- The incomingtls.TLSSocket
(TLS) ornet.Socket
(TCP) connection. -
backend
- The forwardednet.Socket
(TCP) connection.
plaintextConnection
Event: Emits once a socket is confirmed to be TCP and not TLS. Use this instead of the connection
event.
See: https://nodejs.org/api/net.html#net_event_connection
secureConnection
Event: See: https://nodejs.org/api/tls.html#tls_event_secureconnection
missingRoute
Event: When a TLS client connects but no route matches its SNI and ALPN, thi event is emitted. The handler receives the incoming net.Socket
(TCP) instance.
It is up to the event handler to deal with the connection. If this event is not listened for, the client connection is gracefully closed.
clientError
Event: Fires when either the client (TLS) or backend (TCP) socket throws an error.
Arguments:
-
error
- Error instance -
client
orbackend
-net.Socket
ortls.TLSSocket
instance respectively
router.plaintext
Destination address for incoming plaintext TCP connections.
Set to an object with properties:
address
family
port
See: server.address()
Detault: undefined
router.route(...rule)
Each rule
has the properties:
-
sni
- Match the server domain name provided by the TLS client. Default: Any server name matches. -
alpn
- Match the negotiated protocol provided by the TLS client. Default: Any protocol name matches. -
port
- TCP port of the route destination. Required. -
address
- TCP IP address or DNS hostname of the route destination. Default:localhost
-
family
- The IP type, either4
,6
, or0
. Default:0
(auto).
router.getTicketKeys()
See: https://nodejs.org/api/tls.html#tls_server_getticketkeys
router.setTicketKeys(keys)
See: https://nodejs.org/api/tls.html#tls_server_setticketkeys_keys
router.setSecureContext(options)
See: https://nodejs.org/api/tls.html#tls_server_setsecurecontext_options