feat(deploy): bind-address flags for browser-facing ports + nginx docs
By default hbbs and hbbr bind every port to the wildcard, which collides with operators wanting to put nginx/Caddy in front of the dashboard (443) and the two browser-facing WebSocket ports (21118 rendezvous, 21119 relay) for TLS termination. Operators reported having to choose between exposing hbbs directly (no TLS for `wss://`, breaks browsers since the page is HTTPS) or moving the daemon to a different port. New flags: - hbbs `--http-listen=<HOST>` pins the HTTP API + dashboard port. - hbbs `--ws-listen=<HOST>` pins the WS rendezvous port (port + 2). - hbbr `--ws-listen=<HOST>` pins the WS relay port (port + 2). All default to the wildcard (current behaviour). Set to `127.0.0.1` to free up the corresponding public port for nginx. The plain TCP/UDP ports used by desktop clients (21115 NAT test, 21116 rendezvous, 21117 relay) intentionally stay on the wildcard — desktop clients bring their own framing + secretbox encryption and don't go through nginx. Implementation: a small `bind_tcp_listener(host, port)` helper in common.rs that falls through to the existing `listen_any` when host is empty, otherwise binds explicitly. Reused for both ws_port (rendezvous + relay) and the http_port; the latter just builds a `SocketAddr` inline since axum::serve takes one. Documentation: new "TLS deployment with nginx" section in docs/CONFIGURATION.md covering the port plan, the bind flags, full example nginx vhost config (three server blocks: 443 dashboard, 21118 WSS rendezvous, 21119 WSS relay) with the WebSocket Upgrade plumbing and bump-up timeouts that long sessions need, plus the firewall list and the four common failure modes (SSL protocol error, connection refused, 502, hung 200 instead of 101). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+155
-1
@@ -355,9 +355,163 @@ unconditionally. **Direct peer-to-peer is never attempted.**
|
|||||||
### Network requirements
|
### Network requirements
|
||||||
|
|
||||||
- The **relay host** advertised to clients (`--relay-servers=<HOSTS>` on hbbs) must resolve and be reachable from the end-user's browser on port 21119. The relay is what carries the actual session bytes — if a user's browser can't open `ws://<relay-host>:21119/`, the session dies after the rendezvous step. A common gotcha: setting `--relay-servers=hbbr-internal.local` works for desktop clients on the LAN but breaks for browsers off-LAN.
|
- The **relay host** advertised to clients (`--relay-servers=<HOSTS>` on hbbs) must resolve and be reachable from the end-user's browser on port 21119. The relay is what carries the actual session bytes — if a user's browser can't open `ws://<relay-host>:21119/`, the session dies after the rendezvous step. A common gotcha: setting `--relay-servers=hbbr-internal.local` works for desktop clients on the LAN but breaks for browsers off-LAN.
|
||||||
- **Reverse proxies** must forward the WebSocket upgrade for both 21118 (rendezvous) and 21119 (relay). Caddy: `reverse_proxy /ws/* hbbs:21118` plus equivalent for 21119; nginx: the standard `proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";` block.
|
|
||||||
- Audit rows are written under the admin's cookie via the existing `/api/audit/conn` endpoint; no new server endpoint.
|
- Audit rows are written under the admin's cookie via the existing `/api/audit/conn` endpoint; no new server endpoint.
|
||||||
|
|
||||||
|
### TLS deployment with nginx
|
||||||
|
|
||||||
|
The dashboard and the two browser-facing WebSocket ports (21118 = rendezvous, 21119 = relay) all need TLS in front of them when accessed from a browser, since the page is served over HTTPS and mixed-content `ws://` is blocked. nginx is the canonical setup; Caddy works similarly with much less ceremony.
|
||||||
|
|
||||||
|
#### Port plan
|
||||||
|
|
||||||
|
| Public port | TLS terminator | Backed by |
|
||||||
|
|---|---|---|
|
||||||
|
| 443/tcp | nginx | `127.0.0.1:21114` (hbbs HTTP API + dashboard) |
|
||||||
|
| 21118/tcp | nginx | `127.0.0.1:21118` (hbbs WS rendezvous) |
|
||||||
|
| 21119/tcp | nginx | `127.0.0.1:21119` (hbbr WS relay) |
|
||||||
|
| 21115/tcp | — | hbbs (NAT test, plain TCP, desktop clients only) |
|
||||||
|
| 21116/tcp+udp | — | hbbs (main rendezvous, desktop clients only) |
|
||||||
|
| 21117/tcp | — | hbbr (relay for desktop clients, plain TCP) |
|
||||||
|
|
||||||
|
Desktop clients use plain TCP/UDP on 21115 / 21116 / 21117 and bring their own framing + secretbox encryption — no TLS needed. Only browsers go through nginx.
|
||||||
|
|
||||||
|
#### Pin hbbs / hbbr to localhost
|
||||||
|
|
||||||
|
By default both binaries bind every port to the wildcard (`[::]`), which collides with nginx wanting to take the same public port. Use the bind flags so nginx can claim the public port and forward to localhost:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# hbbs — desktop-client ports stay on the wildcard, browser ports go local
|
||||||
|
./hbbs --port 21116 \
|
||||||
|
--http-port 21114 --http-listen 127.0.0.1 \
|
||||||
|
--ws-listen 127.0.0.1 \
|
||||||
|
--relay-servers rd.example.com \
|
||||||
|
--public-base-url https://rd.example.com \
|
||||||
|
# ... rest of your flags
|
||||||
|
|
||||||
|
# hbbr — TCP relay (21117) stays public, WS relay (21119) goes local
|
||||||
|
./hbbr --port 21117 --ws-listen 127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
After restart, `ss -tlnp` should show:
|
||||||
|
|
||||||
|
```
|
||||||
|
LISTEN 127.0.0.1:21114 <-- hbbs HTTP, fronted by nginx 443
|
||||||
|
LISTEN 127.0.0.1:21118 <-- hbbs WS, fronted by nginx 21118
|
||||||
|
LISTEN 127.0.0.1:21119 <-- hbbr WS, fronted by nginx 21119
|
||||||
|
LISTEN 0.0.0.0:21115 <-- hbbs NAT test (public)
|
||||||
|
LISTEN 0.0.0.0:21116 <-- hbbs rendezvous tcp (public)
|
||||||
|
LISTEN 0.0.0.0:21117 <-- hbbr relay tcp (public)
|
||||||
|
# plus 0.0.0.0:21116/udp
|
||||||
|
```
|
||||||
|
|
||||||
|
#### nginx site config
|
||||||
|
|
||||||
|
Three `server { }` blocks. The dashboard one is normal HTTP/2 + reverse-proxy; the two WS blocks need the `Upgrade`/`Connection` headers and a long `proxy_read_timeout` so idle web sessions don't get severed mid-screen-share.
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# /etc/nginx/sites-available/rustdesk
|
||||||
|
|
||||||
|
# Helper for WS upgrade — referenced by both WS blocks below.
|
||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. Dashboard + admin API on 443
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name rd.example.com;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/rd.example.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/rd.example.com/privkey.pem;
|
||||||
|
|
||||||
|
# The dashboard streams audit logs / device events via plain HTTP today
|
||||||
|
# but we still need WS-upgrade pass-through here for the /admin/connect/
|
||||||
|
# SPA's own asset requests are HTTP, but if you ever proxy ws under
|
||||||
|
# /ws/* in the future, this stays correct.
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:21114;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Force HTTPS — drop this block if you don't need port 80 at all.
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name rd.example.com;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. WSS rendezvous on 21118
|
||||||
|
server {
|
||||||
|
listen 21118 ssl http2;
|
||||||
|
listen [::]:21118 ssl http2;
|
||||||
|
server_name rd.example.com;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/rd.example.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/rd.example.com/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:21118;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
# Web sessions can sit idle on the rendezvous WS; bump the read
|
||||||
|
# timeout so nginx doesn't reset the connection before the relay
|
||||||
|
# leg finishes negotiating.
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. WSS relay on 21119
|
||||||
|
server {
|
||||||
|
listen 21119 ssl http2;
|
||||||
|
listen [::]:21119 ssl http2;
|
||||||
|
server_name rd.example.com;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/rd.example.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/rd.example.com/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:21119;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
# Relay carries the live session for as long as the user is
|
||||||
|
# remote-controlling. Pick a value larger than the longest
|
||||||
|
# session you expect (24h here).
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
proxy_send_timeout 86400s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The Let's Encrypt cert covers all three ports — same hostname, just different listen ports. With certbot's nginx plugin the cert was already obtained for the 443 block; the other two blocks just point at the same files.
|
||||||
|
|
||||||
|
Open the firewall for **80, 443, 21115, 21116, 21117, 21118, 21119** (TCP) and **21116** (UDP). Everything else can stay closed.
|
||||||
|
|
||||||
|
Verify after reload: in DevTools → Network, `wss://rd.example.com:21118/` and `wss://rd.example.com:21119/` should each show status `101 Switching Protocols`.
|
||||||
|
|
||||||
|
Common failure modes:
|
||||||
|
|
||||||
|
- **`ERR_SSL_PROTOCOL_ERROR`** on 21118 or 21119 — nginx isn't terminating TLS on that port. Check the listener block + cert paths.
|
||||||
|
- **`ERR_CONNECTION_REFUSED`** — firewall is blocking the public port, OR nginx itself isn't listening on it (check `ss -tlnp`).
|
||||||
|
- **`502 Bad Gateway`** at the dashboard — hbbs isn't running, or `--http-listen` doesn't match what nginx is `proxy_pass`ing to.
|
||||||
|
- **WS upgrade hangs / 200 instead of 101** — `Upgrade` / `Connection` headers aren't being forwarded. The `$connection_upgrade` map at the top of the config is what makes this work; without it, `proxy_set_header Connection "upgrade"` would also work but breaks plain HTTP requests.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
| Feature | Status |
|
| Feature | Status |
|
||||||
|
|||||||
+23
-1
@@ -1,6 +1,7 @@
|
|||||||
use clap::App;
|
use clap::App;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err, anyhow::{Context, Result}, get_version_number, log, tokio, ResultType
|
allow_err, anyhow::{Context, Result}, get_version_number, log, tcp::listen_any, tokio,
|
||||||
|
tokio::net::TcpListener, ResultType,
|
||||||
};
|
};
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use sodiumoxide::crypto::sign;
|
use sodiumoxide::crypto::sign;
|
||||||
@@ -11,6 +12,27 @@ use std::{
|
|||||||
time::{Instant, SystemTime},
|
time::{Instant, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Bind a TCP listener for `port`. When `host` is empty (the default for
|
||||||
|
/// every flag that accepts it), falls through to `listen_any` which binds
|
||||||
|
/// the dual-stack `[::]` wildcard. When `host` is set, binds only to that
|
||||||
|
/// address — used by deployments that put nginx/Caddy out front for TLS
|
||||||
|
/// termination on the WS / HTTP ports and want hbbs/hbbr's plain sockets
|
||||||
|
/// reachable only from localhost.
|
||||||
|
pub async fn bind_tcp_listener(host: &str, port: i32) -> ResultType<TcpListener> {
|
||||||
|
if host.is_empty() {
|
||||||
|
return listen_any(port as u16).await;
|
||||||
|
}
|
||||||
|
let host_with_brackets = if host.contains(':') && !host.starts_with('[') {
|
||||||
|
format!("[{}]", host)
|
||||||
|
} else {
|
||||||
|
host.to_string()
|
||||||
|
};
|
||||||
|
let addr: SocketAddr = format!("{}:{}", host_with_brackets, port).parse()?;
|
||||||
|
let l = TcpListener::bind(addr).await?;
|
||||||
|
log::info!("listen on tcp {}", addr);
|
||||||
|
Ok(l)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn get_expired_time() -> Instant {
|
pub(crate) fn get_expired_time() -> Instant {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ fn main() -> ResultType<()> {
|
|||||||
let args = format!(
|
let args = format!(
|
||||||
"-p, --port=[NUMBER(default={RELAY_PORT})] 'Sets the listening port'
|
"-p, --port=[NUMBER(default={RELAY_PORT})] 'Sets the listening port'
|
||||||
-k, --key=[KEY] 'Only allow the client with the same key'
|
-k, --key=[KEY] 'Only allow the client with the same key'
|
||||||
|
--ws-listen=[HOST] 'Bind address for the browser-facing WebSocket relay port (port+2). Default = wildcard. Set to 127.0.0.1 (or ::1) when a reverse proxy claims the public port for TLS termination.'
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
let matches = App::new("hbbr")
|
let matches = App::new("hbbr")
|
||||||
@@ -40,6 +41,7 @@ fn main() -> ResultType<()> {
|
|||||||
matches
|
matches
|
||||||
.value_of("key")
|
.value_of("key")
|
||||||
.unwrap_or(&std::env::var("KEY").unwrap_or_default()),
|
.unwrap_or(&std::env::var("KEY").unwrap_or_default()),
|
||||||
|
matches.value_of("ws-listen").unwrap_or(""),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ fn main() -> ResultType<()> {
|
|||||||
-r, --relay-servers=[HOST] 'Sets the default relay servers, separated by comma'
|
-r, --relay-servers=[HOST] 'Sets the default relay servers, separated by comma'
|
||||||
-M, --rmem=[NUMBER(default={RMEM})] 'Sets UDP recv buffer size, set system rmem_max first, e.g., sudo sysctl -w net.core.rmem_max=52428800. vi /etc/sysctl.conf, net.core.rmem_max=52428800, sudo sysctl –p'
|
-M, --rmem=[NUMBER(default={RMEM})] 'Sets UDP recv buffer size, set system rmem_max first, e.g., sudo sysctl -w net.core.rmem_max=52428800. vi /etc/sysctl.conf, net.core.rmem_max=52428800, sudo sysctl –p'
|
||||||
--http-port=[NUMBER(default=21114)] 'HTTP management API port (0 disables)'
|
--http-port=[NUMBER(default=21114)] 'HTTP management API port (0 disables)'
|
||||||
|
--http-listen=[HOST] 'Bind address for --http-port. Default = wildcard. Set to 127.0.0.1 (or ::1) when nginx/Caddy fronts this port for TLS.'
|
||||||
|
--ws-listen=[HOST] 'Bind address for the browser-facing WebSocket rendezvous port (port+2). Default = wildcard. Set to 127.0.0.1 (or ::1) when a reverse proxy claims the public port for TLS termination.'
|
||||||
--bootstrap-admin-username=[USERNAME] 'Username to seed on first startup if users table is empty'
|
--bootstrap-admin-username=[USERNAME] 'Username to seed on first startup if users table is empty'
|
||||||
--bootstrap-admin-password=[PASSWORD] 'Password to seed on first startup if users table is empty'
|
--bootstrap-admin-password=[PASSWORD] 'Password to seed on first startup if users table is empty'
|
||||||
--ab-legacy-mode=[on|off] 'When on, /api/ab/personal returns 404 to force legacy single-blob AB'
|
--ab-legacy-mode=[on|off] 'When on, /api/ab/personal returns 404 to force legacy single-blob AB'
|
||||||
@@ -58,6 +60,8 @@ fn main() -> ResultType<()> {
|
|||||||
&get_arg_or("key", "-".to_owned()),
|
&get_arg_or("key", "-".to_owned()),
|
||||||
rmem,
|
rmem,
|
||||||
http_port,
|
http_port,
|
||||||
|
&get_arg("ws-listen"),
|
||||||
|
&get_arg("http-listen"),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -46,7 +46,7 @@ const BLACKLIST_FILE: &str = "blacklist.txt";
|
|||||||
const BLOCKLIST_FILE: &str = "blocklist.txt";
|
const BLOCKLIST_FILE: &str = "blocklist.txt";
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread")]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
pub async fn start(port: &str, key: &str, ws_listen: &str) -> ResultType<()> {
|
||||||
let key = get_server_sk(key);
|
let key = get_server_sk(key);
|
||||||
if let Ok(mut file) = std::fs::File::open(BLACKLIST_FILE) {
|
if let Ok(mut file) = std::fs::File::open(BLACKLIST_FILE) {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
@@ -82,10 +82,21 @@ pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
|||||||
log::info!("Listening on tcp :{}", port);
|
log::info!("Listening on tcp :{}", port);
|
||||||
let port2 = port + 2;
|
let port2 = port + 2;
|
||||||
log::info!("Listening on websocket :{}", port2);
|
log::info!("Listening on websocket :{}", port2);
|
||||||
|
// The WS port (21119 default) is the only browser-facing endpoint at
|
||||||
|
// hbbr — operators put nginx/Caddy in front of it for TLS. Allow
|
||||||
|
// pinning it to localhost so the reverse proxy can claim the public
|
||||||
|
// port without colliding. The plain TCP relay port (21117) is for
|
||||||
|
// desktop clients and stays on the wildcard.
|
||||||
|
let ws_listen = ws_listen.to_owned();
|
||||||
let main_task = async move {
|
let main_task = async move {
|
||||||
loop {
|
loop {
|
||||||
log::info!("Start");
|
log::info!("Start");
|
||||||
io_loop(listen_any(port).await?, listen_any(port2).await?, &key).await;
|
io_loop(
|
||||||
|
listen_any(port).await?,
|
||||||
|
crate::common::bind_tcp_listener(&ws_listen, port2 as i32).await?,
|
||||||
|
&key,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let listen_signal = crate::common::listen_signal();
|
let listen_signal = crate::common::listen_signal();
|
||||||
|
|||||||
@@ -110,10 +110,16 @@ impl RendezvousServer {
|
|||||||
key: &str,
|
key: &str,
|
||||||
rmem: usize,
|
rmem: usize,
|
||||||
http_port: i32,
|
http_port: i32,
|
||||||
|
ws_listen: &str,
|
||||||
|
http_listen: &str,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
let (key, sk) = Self::get_server_sk(key);
|
let (key, sk) = Self::get_server_sk(key);
|
||||||
let nat_port = port - 1;
|
let nat_port = port - 1;
|
||||||
let ws_port = port + 2;
|
let ws_port = port + 2;
|
||||||
|
// Capture the bind addresses as owned Strings so the async move
|
||||||
|
// closures below can hold onto them across reconnect retries.
|
||||||
|
let ws_listen = ws_listen.to_owned();
|
||||||
|
let http_listen = http_listen.to_owned();
|
||||||
let pm = PeerMap::new().await?;
|
let pm = PeerMap::new().await?;
|
||||||
// M1: build the HTTP API state and seed the admin user if requested.
|
// M1: build the HTTP API state and seed the admin user if requested.
|
||||||
// Done here (right after PeerMap::new) so the API server, the seeding,
|
// Done here (right after PeerMap::new) so the API server, the seeding,
|
||||||
@@ -199,7 +205,11 @@ impl RendezvousServer {
|
|||||||
rs.parse_relay_servers(&get_arg("relay-servers"));
|
rs.parse_relay_servers(&get_arg("relay-servers"));
|
||||||
let mut listener = create_tcp_listener(port).await?;
|
let mut listener = create_tcp_listener(port).await?;
|
||||||
let mut listener2 = create_tcp_listener(nat_port).await?;
|
let mut listener2 = create_tcp_listener(nat_port).await?;
|
||||||
let mut listener3 = create_tcp_listener(ws_port).await?;
|
// The WS port is the only browser-facing endpoint at hbbs — it's
|
||||||
|
// the one operators put nginx/Caddy in front of for TLS. Allow
|
||||||
|
// pinning it to localhost so the reverse proxy can claim
|
||||||
|
// `[::]:21118` without colliding.
|
||||||
|
let mut listener3 = crate::common::bind_tcp_listener(&ws_listen, ws_port).await?;
|
||||||
let test_addr = std::env::var("TEST_HBBS").unwrap_or_default();
|
let test_addr = std::env::var("TEST_HBBS").unwrap_or_default();
|
||||||
if std::env::var("ALWAYS_USE_RELAY")
|
if std::env::var("ALWAYS_USE_RELAY")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@@ -266,7 +276,7 @@ impl RendezvousServer {
|
|||||||
}
|
}
|
||||||
LoopFailure::Listener3 => {
|
LoopFailure::Listener3 => {
|
||||||
drop(listener3);
|
drop(listener3);
|
||||||
listener3 = create_tcp_listener(ws_port).await?;
|
listener3 = crate::common::bind_tcp_listener(&ws_listen, ws_port).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,7 +288,15 @@ impl RendezvousServer {
|
|||||||
let api_task: std::pin::Pin<
|
let api_task: std::pin::Pin<
|
||||||
Box<dyn std::future::Future<Output = ResultType<()>> + Send>,
|
Box<dyn std::future::Future<Output = ResultType<()>> + Send>,
|
||||||
> = if http_port > 0 {
|
> = if http_port > 0 {
|
||||||
let addr: SocketAddr = format!("0.0.0.0:{http_port}").parse()?;
|
let bind_host = if http_listen.is_empty() { "0.0.0.0" } else { http_listen.as_str() };
|
||||||
|
// Allow IPv6 / [::1] / hostnames — wrap bare IPv6 in brackets for the URL form.
|
||||||
|
let host_with_brackets = if bind_host.contains(':') && !bind_host.starts_with('[') {
|
||||||
|
format!("[{}]", bind_host)
|
||||||
|
} else {
|
||||||
|
bind_host.to_string()
|
||||||
|
};
|
||||||
|
let addr: SocketAddr = format!("{}:{}", host_with_brackets, http_port).parse()?;
|
||||||
|
log::info!("HTTP API listening on {}", addr);
|
||||||
let st = api_state.clone();
|
let st = api_state.clone();
|
||||||
Box::pin(crate::api::serve(addr, st))
|
Box::pin(crate::api::serve(addr, st))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user