{"record":{"id":"ad0197fcdb62efb7","repo":"clash-verge-rev/clash-verge-rev","slug":"listener-address-must-include-a-port","errorCode":null,"errorMessage":"listener address must include a port","messagePattern":"listener address must include a port","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/core/listener.rs","lineNumber":281,"sourceCode":"        .and_then(|address| address.strip_suffix(']'))\n        .unwrap_or(bind_address);\n    Ok(vec![\n        IpAddr::from_str(normalized).with_context(|| format!(\"invalid bind-address {bind_address:?}\"))?,\n    ])\n}\n\nfn parse_listener_address(address: &str) -> Result<(Vec<IpAddr>, u16)> {\n    let address = address.trim();\n    if let Ok(socket) = SocketAddr::from_str(address) {\n        if socket.port() == 0 {\n            bail!(\"listener port must be between 1 and 65535\");\n        }\n        return Ok((vec![socket.ip()], socket.port()));\n    }\n\n    let (host, port) = address\n        .rsplit_once(':')\n        .ok_or_else(|| anyhow!(\"listener address must include a port\"))?;\n    let port = port\n        .parse::<u16>()\n        .with_context(|| format!(\"invalid listener port {port:?}\"))?;\n    if port == 0 {\n        bail!(\"listener port must be between 1 and 65535\");\n    }\n    let host = host\n        .trim()\n        .strip_prefix('[')\n        .and_then(|host| host.strip_suffix(']'))\n        .unwrap_or_else(|| host.trim());\n    if host.eq_ignore_ascii_case(\"localhost\") {\n        return Ok((vec![IpAddr::V4(Ipv4Addr::LOCALHOST)], port));\n    }\n    Ok((\n        vec![IpAddr::from_str(host).with_context(|| format!(\"invalid listener host {host:?}\"))?],\n        port,\n    ))","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/clash-verge-rev/clash-verge-rev/blob/5cad0f2799e74b1105e133bcfc2a45f5c1370ef1/src-tauri/src/core/listener.rs#L263-L299","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nexternal-controller: 127.0.0.1\n// after\nexternal-controller: 127.0.0.1:9090","handlingStrategy":"validation","validationCode":"fn parse_safe(address: &str) -> Result<(Vec<IpAddr>, u16), String> {\n    use std::net::SocketAddr;\n    let t = address.trim();\n    if SocketAddr::from_str(t).is_ok() || t.rsplit_once(':').is_some() {\n        crate::core::listener::parse_listener_address(t).map_err(|e| e.to_string())\n    } else {\n        Err(\"listener address must include a port, e.g. 127.0.0.1:9090\".into())\n    }\n}","typeGuard":"fn looks_like_host_port(s: &str) -> bool {\n    s.trim().rsplit_once(':').is_some()\n}","tryCatchPattern":"if let Err(e) = parse_listener_address(&addr) {\n    if e.to_string().contains(\"must include a port\") {\n        // prompt user for port before retrying\n    }\n}","preventionTips":["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."],"tags":["listener","network","validation","config"],"analyzedSha":"5cad0f2799e74b1105e133bcfc2a45f5c1370ef1","analyzedAt":"2026-08-12T03:25:57.699Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}