v2fly/v2ray-core · critical · errors.Error
invalid listen address
Error message
invalid listen address
What it means
This panic is raised in the TLS-mirror enrollment-confirmation server's Listen() when v2net.ParseDestination fails to parse the configured listen string (ServerConfig.listen, field 3 of the roundtripperenrollmentconfirmation ServerConfig proto). ParseDestination (common/net/destination.go:30) only accepts forms like 'tcp:1.2.3.4:5678', 'udp:host:port', 'unix:/path', or a bare 'host:port' (defaulting to TCP); anything else — missing port, non-numeric port, port out of range, malformed IPv6 — returns an error, and this code converts it into a process-killing panic with AtError severity.
Source
Thrown at transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/server.go:47
}
type Server struct {
config *ServerConfig
ctx context.Context
enrollmentProcessor tlsmirror.ConnectionEnrollmentConfirmationProcessor
rttServer request.RoundTripperServer
}
func (s *Server) OnConnectionEnrollmentConfirmationServerInstanceConfigReady(config tlsmirror.ConnectionEnrollmentConfirmationServerInstanceConfig) {
s.enrollmentProcessor = config.EnrollmentProcessor
}
func (s *Server) Listen(ctx context.Context) (v2net.Listener, error) {
transportEnvironment := envctx.EnvironmentFromContext(s.ctx).(environment.TransportEnvironment)
listener := transportEnvironment.Listener()
addr, err := v2net.ParseDestination(s.config.Listen)
if err != nil {
panic(newError("invalid listen address " + s.config.Listen).Base(err).AtError())
}
netaddr := &net.TCPAddr{IP: addr.Address.IP(), Port: int(addr.Port)}
l, err := listener.Listen(s.ctx, netaddr, nil)
if err != nil {
panic(newError("failed to listen on " + s.config.Listen).Base(err).AtError())
}
return l, nil
}
func (s *Server) OnRoundTrip(ctx context.Context, req request.Request, opts ...request.RoundTripperOption) (resp request.Response, err error) {
enrollmentReq := &tlsmirror.EnrollmentConfirmationReq{}
err = proto.Unmarshal(req.Data, enrollmentReq)
if err != nil {
return request.Response{}, newError("failed to unmarshal enrollment confirmation request").Base(err).AtError()
}
enrollmentResp, err := s.enrollmentProcessor.VerifyConnectionEnrollment(enrollmentReq)
if err != nil {
return request.Response{}, newError("failed to process enrollment confirmation request").Base(err).AtError()View on GitHub (pinned to db12914161)
Solutions
- Fix the listen string to the exact expected format 'tcp:IP:PORT' (e.g. 'tcp:127.0.0.1:9443'); note the prefix has a single colon, no slashes.
- Validate the address before startup with v2net.ParseDestination in your config loader (or a unit test over shipped configs) so errors surface as config errors, not panics.
- For IPv6, use bracketed host: 'tcp:[::1]:9443'.
- If you meant a Unix socket, use the 'unix:/path/to/socket' form — but be aware this code then builds a net.TCPAddr from addr.Address.IP(), so for this server stick to tcp with a literal IP.
Example fix
// before (config)
{
"listen": "127.0.0.1" // ParseDestination fails: missing port -> panic
}
// after
{
"listen": "tcp:127.0.0.1:9443"
} Defensive patterns
Strategy: validation
Validate before calling
// validate the enrollment-confirmation listen string before starting the server:
dest, err := net.ParseDestination(cfg.Listen)
if err != nil {
return fmt.Errorf("invalid enrollment listen address %q: %w", cfg.Listen, err)
}
if dest.Network != net.Network_TCP {
return fmt.Errorf("enrollment listen must be tcp, got %q", cfg.Listen)
}
// optional: reject hostnames, this server binds a literal TCPAddr
if !dest.Address.IsIP() {
return fmt.Errorf("enrollment listen must use a literal IP, got %q", dest.Address.String())
} Type guard
func isValidListenAddress(s string) bool {
d, err := net.ParseDestination(s)
return err == nil && d.Network == net.Network_TCP && d.Address.IsIP() && d.Port > 0
} Try / catch
// wrap server startup so a bad config surfaces as an error, not a crash:
func startEnrollmentServer(s *rec.Server) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("enrollment server startup failed: %v", r)
}
}()
l, err := s.Listen(ctx)
if err != nil {
return err
}
_ = l // serve...
return nil
} Prevention
- Use the exact format 'tcp:IP:PORT' (single colon prefix, no scheme slashes) for the listen field.
- Add a config-validation pass (or unit test) that runs net.ParseDestination over every listen value in shipped configs.
- Bracket IPv6 hosts: 'tcp:[::1]:9443'.
- Prefer literal IPs over hostnames since this component binds a net.TCPAddr directly.
When it happens
Trigger: Setting ServerConfig.listen to a bare IP or hostname with no port ('127.0.0.1'); a port that is not a number or exceeds 65535 ('tcp:0.0.0.0:99999'); IPv6 without brackets or the tcp: prefix ('::1:443'); leaving the field empty-ish with stray whitespace; JSON/TOML config typos where the value is a number or an object rather than the expected string.
Common situations: First-time setup of the tlsmirror enrollment confirmation component where users copy an address format from another tool (e.g. 'localhost' alone, or a URL like 'tcp://host:port' with a double slash); config generated by scripts that concatenate host and port incorrectly; version migrations where the listen key was renamed and silently ends up zero-valued/empty.
Related errors
AI-assisted analysis of v2fly/v2ray-core@db12914161 (2026-08-15).
Data as JSON: /api/errors/179463956c895221.
Report an issue: GitHub.