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
- 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.
- 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.
- 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.
- 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.
- Verify the transport environment actually provides a TCP listener (this component expects envctx.EnvironmentFromContext(s.ctx).(environment.TransportEnvironment) with a non-nil Listener()).
- 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
- Check port availability (ss -ltnp / lsof -i) before deploying, and give each instance a unique listen port.
- Bind only addresses the host owns (127.0.0.1 or 0.0.0.0); avoid hostnames — the server binds a literal net.TCPAddr.
- Avoid privileged ports (<1024) or grant CAP_NET_BIND_SERVICE to the service unit.
- In orchestration, run a pre-flight net.Listen on the same address so failures appear as deployment errors rather than runtime panics.
- If the port may be taken by a stale instance, add a kill/cleanup step or enable socket reuse at the supervisor level instead of relying on retry.
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.