v2fly/v2ray-core · error

no inbound metadata

Error message

no inbound metadata

What it means

This is a hard Go panic raised in the VMess inbound's Process() after the VMess request header is decoded and logged. The handler assumes the context carries session.Inbound metadata, which is attached exclusively by the proxyman inbound worker (app/proxyman/inbound/worker.go via session.ContextWithInbound) when the connection is accepted by a registered inbound handler. session.InboundFromContext(ctx) returning nil proves the proxy ran outside that pipeline, so it panics rather than dispatching traffic with no accounting/metadata.

Source

Thrown at proxy/vmess/inbound/inbound.go:275

	if request.Command != protocol.RequestCommandMux {
		ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
			From:   connection.RemoteAddr(),
			To:     request.Destination(),
			Status: log.AccessAccepted,
			Reason: "",
			Email:  request.User.Email,
		})
	}

	newError("received request for ", request.Destination()).WriteToLog(session.ExportIDToError(ctx))

	if err := connection.SetReadDeadline(time.Time{}); err != nil {
		newError("unable to set back read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
	}

	inbound := session.InboundFromContext(ctx)
	if inbound == nil {
		panic("no inbound metadata")
	}
	inbound.User = request.User

	sessionPolicy = h.policyManager.ForLevel(request.User.Level)

	ctx, cancel := context.WithCancel(ctx)
	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)

	ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
	link, err := dispatcher.Dispatch(ctx, request.Destination())
	if err != nil {
		return newError("failed to dispatch request to ", request.Destination()).Base(err)
	}

	requestDone := func() error {
		defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)

		bodyReader, err := svrSession.DecodeRequestBody(request, reader)

View on GitHub (pinned to db12914161)

Solutions

  1. Route connections through a registered inbound handler so proxyman's worker attaches the Inbound before the proxy runs.
  2. In custom entry points/tests, set the metadata explicitly: ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: "vmess-in"}) prior to Process().
  3. Grep custom code paths between accept and Process for context shadowing or reassignment; the ctx reaching Process must be a descendant of the one the worker built.
  4. Mirror the working patterns: app/tun/handler_tcp.go:89, app/reverse/bridge.go:105, app/proxyman/inbound/worker.go:83.
  5. Wrap risky new paths in recover() during development so the panic names the missing wiring instead of crashing the whole node.

Example fix

// before
func (h *customHandler) handle(conn net.Conn) {
    ctx := context.Background()
    go h.vmessInbound.Process(ctx, link, conn) // panic: no inbound metadata
}

// after
func (h *customHandler) handle(conn net.Conn) {
    ctx := session.ContextWithInbound(context.Background(), &session.Inbound{
        Tag:    h.tag,
        Source: net.DestinationFromAddr(conn.RemoteAddr()),
    })
    go h.vmessInbound.Process(ctx, link, conn)
}
Defensive patterns

Strategy: validation

Validate before calling

// guard before dispatching into the VMess inbound:
if session.InboundFromContext(ctx) == nil {
    ctx = session.ContextWithInbound(ctx, &session.Inbound{
        Tag:    "vmess-in",
        Source: net.DestinationFromAddr(conn.RemoteAddr()),
    })
}
go vmessInbound.Process(ctx, link, conn)

Type guard

func hasInboundMetadata(ctx context.Context) bool {
    return session.InboundFromContext(ctx) != nil
}

Try / catch

func (h *Host) dispatchVMess(ctx context.Context, c net.Conn) {
    defer func() {
        if r := recover(); r != nil {
            h.log.Error("vmess inbound panic: missing session.Inbound in context?", "panic", r)
        }
    }()
    _ = h.vmessInbound.Process(ctx, h.link, c)
}

Prevention

When it happens

Trigger: Directly invoking the VMess inbound handler with a hand-built context (tests, benchmarks, custom servers); a custom accept loop or transport that calls proxy.Process without first calling session.ContextWithInbound; middleware between the listener and the proxy that replaces the context (e.g. wraps it in a struct not delegating Value, or assigns a new context); integrating the proxy behind an API/websocket shim that spawns its own goroutines with fresh contexts.

Common situations: Unit tests for the VMess decoder that drive the full handler with context.Background(); porting the inbound into a standalone service; adding transports like TUN/socket-activation without replicating app/tun/handler_tcp.go's ContextWithInbound call; bugs where ctx is shadowed (ctx := ...) inside a helper before Process is reached.

Related errors


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