clash-verge-rev/clash-verge-rev · error · anyhow::Error
listener address must include a port
Error message
listener address must include a port
What it means
Thrown by parse_listener_address when the input cannot be parsed as a single SocketAddr AND does not contain a `:` separator from which a host:port split can be derived. The parser requires either a full socket address or at minimum a `host:port` pair so it can bind listeners deterministically.
Source
Thrown at src-tauri/src/core/listener.rs:281
.and_then(|address| address.strip_suffix(']'))
.unwrap_or(bind_address);
Ok(vec![
IpAddr::from_str(normalized).with_context(|| format!("invalid bind-address {bind_address:?}"))?,
])
}
fn parse_listener_address(address: &str) -> Result<(Vec<IpAddr>, u16)> {
let address = address.trim();
if let Ok(socket) = SocketAddr::from_str(address) {
if socket.port() == 0 {
bail!("listener port must be between 1 and 65535");
}
return Ok((vec![socket.ip()], socket.port()));
}
let (host, port) = address
.rsplit_once(':')
.ok_or_else(|| anyhow!("listener address must include a port"))?;
let port = port
.parse::<u16>()
.with_context(|| format!("invalid listener port {port:?}"))?;
if port == 0 {
bail!("listener port must be between 1 and 65535");
}
let host = host
.trim()
.strip_prefix('[')
.and_then(|host| host.strip_suffix(']'))
.unwrap_or_else(|| host.trim());
if host.eq_ignore_ascii_case("localhost") {
return Ok((vec![IpAddr::V4(Ipv4Addr::LOCALHOST)], port));
}
Ok((
vec![IpAddr::from_str(host).with_context(|| format!("invalid listener host {host:?}"))?],
port,
))View on GitHub (pinned to 5cad0f2799)
Solutions
- Provide the address in `host:port` form, e.g. `127.0.0.1:9090`.
- For IPv6, use bracketed form: `[::1]:9090`.
- Use the literal `localhost:PORT` if you want both v4/v6 loopback.
- Validate the address with `SocketAddr::from_str` before submitting it as a listener binding.
Example fix
// before external-controller: 127.0.0.1 // after external-controller: 127.0.0.1:9090
Defensive patterns
Strategy: validation
Validate before calling
fn parse_safe(address: &str) -> Result<(Vec<IpAddr>, u16), String> {
use std::net::SocketAddr;
let t = address.trim();
if SocketAddr::from_str(t).is_ok() || t.rsplit_once(':').is_some() {
crate::core::listener::parse_listener_address(t).map_err(|e| e.to_string())
} else {
Err("listener address must include a port, e.g. 127.0.0.1:9090".into())
}
} Type guard
fn looks_like_host_port(s: &str) -> bool {
s.trim().rsplit_once(':').is_some()
} Try / catch
if let Err(e) = parse_listener_address(&addr) {
if e.to_string().contains("must include a port") {
// prompt user for port before retrying
}
} Prevention
- Always store listener addresses in host:port form in the config UI.
- Use bracketed form for IPv6: [::1]:9090.
- Validate with SocketAddr::from_str before persisting to the YAML.
- Reject empty / scheme-only strings at the form layer.
When it happens
Trigger: Passing a bare hostname or IP without a port (e.g. `127.0.0.1`, `localhost`); passing an empty string; passing an IPv6 address without brackets and port; passing a malformed mixed-port / external-controller / redir-port / tproxy-port / mixed-listener address.
Common situations: Clash/Mihomo YAML config sets `external-controller: 127.0.0.1` (missing `:9090`); user edits the mixed-port or bind address and drops the port; a profile import supplies a listener address field that omits the port.
Related errors
- {key} must be an integer between 1 and 65535
- failed to parse subscription URL: {:?}, input: {}
- file field is required when file_data is provided
- {outcome}
- Invalid vless uri
AI-assisted analysis of clash-verge-rev/clash-verge-rev@5cad0f2799 (2026-08-12).
Data as JSON: /api/errors/ad0197fcdb62efb7.
Report an issue: GitHub.