When clients connect via different hostnames (client1.myserver.com, client2.myserver.com) that resolve to the same IP address, we need to route them to different ports (1234, 1235) on the backend server based on the requested hostname. This goes beyond standard port forwarding and requires SNI (Server Name Indication) inspection for TCP connections.
Here are three viable approaches with different complexity levels:
1. Reverse Proxy (Recommended):
- HAProxy with SNI inspection
- Nginx stream module (for TCP)
- Traefik with TCP routing
2. Firewall-Level:
- iptables with recent module
- nftables with set maps
3. Custom Proxy:
- Node.js net module
- Java NIO sockets
This configuration handles TLS termination and port routing:
frontend tcp_front
bind :443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
use_backend client1_back if { req_ssl_sni -i client1.myserver.com }
use_backend client2_back if { req_ssl_sni -i client2.myserver.com }
backend client1_back
mode tcp
server server1 127.0.0.1:1234
backend client2_back
mode tcp
server server2 127.0.0.1:1235
For cases needing programmatic control:
const net = require('net');
const tls = require('tls');
const portMap = {
'client1.myserver.com': 1234,
'client2.myserver.com': 1235
};
const server = tls.createServer({
SNICallback: (hostname, cb) => {
const ctx = tls.createSecureContext({/* certs */});
cb(null, ctx);
}
});
server.on('connection', (client) => {
const hostname = client.servername;
const targetPort = portMap[hostname];
if (!targetPort) {
client.end();
return;
}
const proxy = net.createConnection(targetPort, '127.0.0.1');
client.pipe(proxy).pipe(client);
});
server.listen(443);
For runtime updates without restarting:
// In HAProxy:
echo "set map /etc/haproxy/port_map.txt client3.myserver.com 1236" | socat stdio /var/run/haproxy/admin.sock
// In Node.js:
function updatePortMap(newMap) {
portMap = {...portMap, ...newMap};
}
When handling thousands of concurrent connections:
- HAProxy handles ~50K connections with moderate resources
- Custom Node.js solution maxes out around 10K connections
- Java NIO implementation can reach 100K+ connections
Key security measures to implement:
- Rate limiting per hostname
- TLS 1.2+ enforcement
- Connection timeout settings
- Backend health checks
- IP whitelisting (when applicable)
When implementing hostname-based port routing for non-HTTP TCP connections, we face a fundamental networking limitation: once DNS resolves multiple hostnames to the same IP address, the original hostname information isn't available at the TCP layer. This requires creative solutions at either the network or application layer.
Here are three viable technical approaches, each with different trade-offs:
1. Using a Reverse Proxy with SNI Inspection
While traditionally for HTTP, some proxies like HAProxy can inspect TLS handshakes for Server Name Indication (SNI) even for non-HTTP traffic:
# haproxy.cfg snippet frontend tcp_front bind :443 mode tcp tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } use_backend client1_back if { req_ssl_sni -i client1.myserver.com } use_backend client2_back if { req_ssl_sni -i client2.myserver.com } backend client1_back mode tcp server server1 127.0.0.1:1234 backend client2_back mode tcp server server2 127.0.0.1:1235
2. IPTables with Recent Module
For source-IP based routing when hostnames aren't available:
#!/bin/bash # Mark packets from specific IPs iptables -t mangle -A PREROUTING -s 192.168.1.100 -j MARK --set-mark 100 iptables -t mangle -A PREROUTING -s 192.168.1.101 -j MARK --set-mark 101 # Route marked packets to different ports iptables -t nat -A PREROUTING -p tcp -m mark --mark 100 -j REDIRECT --to-port 1234 iptables -t nat -A PREROUTING -p tcp -m mark --mark 101 -j REDIRECT --to-port 1235
3. Custom Proxy in Node.js
A JavaScript implementation using the net module:
const net = require('net'); const tls = require('tls'); const portMap = { 'client1.myserver.com': 1234, 'client2.myserver.com': 1235 }; const server = tls.createServer({ // Your TLS options here }, (socket) => { const servername = socket.servername; if (portMap[servername]) { const proxy = net.connect(portMap[servername], () => { socket.pipe(proxy); proxy.pipe(socket); }); } else { socket.end(); } }); server.listen(443);
When implementing dynamic port routing:
- Connection overhead increases with each proxy layer
- SNI inspection adds ~100ms latency during TLS handshake
- Custom solutions in high-level languages may bottleneck at ~1k connections/sec
- Kernel-level solutions (like IPTables) handle higher throughput
For runtime changes without restarting services:
// Node.js dynamic reload example let portMap = require('./port-config.json'); fs.watch('./port-config.json', () => { try { portMap = require('./port-config.json'); } catch (e) { /* handle error */ } }); // For IPTables, use persistent rules and reload: iptables-restore < /etc/iptables/rules.v4