v2fly/v2ray-core · critical · errors.Error

failed to listen on

Error message

failed to listen on 

What it means

This panic is raised in the TLS-mirror enrollment-confirmation server's Listen() after the listen string parsed successfully but transportEnvironment's listener failed to bind the address (listener.Listen(s.ctx, netaddr, nil) returned an error). The code immediately panics with 'failed to listen on <listen>' at error severity, so a bind failure — not a returned error — ends the process. Note the address is built as &net.TCPAddr{IP: addr.Address.IP(), Port: int(addr.Port)}; a domain (non-IP) listen host yields a nil IP, and port 0 lets the OS assign a port, both feeding into the bind attempt.

Source

Thrown at transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/server.go:52

	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()
	}
	respData, err := proto.Marshal(enrollmentResp)
	if err != nil {
		return request.Response{}, newError("failed to marshal enrollment confirmation response").Base(err).AtError()
	}

View on GitHub (pinned to db12914161)

Solutions

  1. Check what holds the port (ss -ltnp / lsof -i :<port>) and stop the conflicting process, or pick another port — most common cause is port already in use.
  2. Bind to an address the machine actually owns; prefer 127.0.0.1 for loopback-only or 0.0.0.0 for all interfaces, and avoid hostnames here since the server binds a literal net.TCPAddr.
  3. If you need a privileged port, grant the capability (setcap cap_net_bind_service) or run under a unit with CAP_NET_BIND_SERVICE instead of running as root.
  4. Run a pre-flight bind in your orchestration (net.Listen on the same address before handing config to the core) to turn the panic into an actionable startup check.
  5. Verify the transport environment actually provides a TCP listener (this component expects envctx.EnvironmentFromContext(s.ctx).(environment.TransportEnvironment) with a non-nil Listener()).
  6. If the port must be dynamic, use port 0 and capture the assigned port from the returned listener instead of hardcoding.

Example fix

// before (config)
{ "listen": "tcp:10.0.0.5:443" }  // EADDRINUSE / EACCES / EADDRNOTAVAIL -> panic

// after
// 1. free or change the port, bind an address this host owns, avoid <1024:
{ "listen": "tcp:0.0.0.0:9443" }

// 2. optional pre-flight check in your launcher:
if l, err := net.Listen("tcp", "0.0.0.0:9443"); err != nil {
    return fmt.Errorf("enrollment listen unavailable: %w", err)
} else {
    l.Close()
}
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight bind check before handing the config to the enrollment server:
dest, err := net.ParseDestination(cfg.Listen)
if err != nil {
    return fmt.Errorf("invalid enrollment listen address %q: %w", cfg.Listen, err)
}
probe, err := net.Listen("tcp", fmt.Sprintf("%s:%d", dest.Address.IP(), dest.Port))
if err != nil {
    return fmt.Errorf("enrollment listen address %q not bindable: %w", cfg.Listen, err)
}
probe.Close() // race window is acceptable as an early check

Type guard

func isBindableListenAddress(s string) bool {
    d, err := net.ParseDestination(s)
    if err != nil || d.Network != net.Network_TCP {
        return false
    }
    l, err := net.Listen("tcp", net.JoinHostPort(d.Address.String(), strconv.Itoa(int(d.Port))))
    if err != nil {
        return false
    }
    l.Close()
    return true
}

Try / catch

func startEnrollmentServer(s *rec.Server) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("enrollment server failed to listen: %v", r)
        }
    }()
    l, listenErr := s.Listen(ctx)
    if listenErr != nil {
        return listenErr
    }
    go serve(l)
    return nil
}

Prevention

When it happens

Trigger: The configured port is already in use (another instance of the enrollment server, or any service on that port); binding a privileged port (<1024) without root/capabilities; the host part is a hostname that resolves to an IP the machine does not own, or an interface that is down/absent (e.g. 'tcp:10.0.0.5:9443' on a host without that address); a DNS name in listen making addr.Address.IP() nil so the bind target is wrong; firewall/SELinux denying the bind.

Common situations: Restarting the node while the old process or a systemd unit still holds the port; two enrollment-confirmation instances configured with the same listen; config copied between machines with machine-specific IPs; containers where the configured IP exists on the host but not inside the container netns; changes after a core upgrade that altered transport environment wiring so transportEnvironment.Listener() returns a listener that cannot bind this address type.

Related errors


AI-assisted analysis of v2fly/v2ray-core@db12914161 (2026-08-15). Data as JSON: /api/errors/a1c7de10a2b38761. Report an issue: GitHub.