XTLS/Xray-core · error
not a Service.
Error message
not a Service.
What it means
Thrown by validatePaddingSchedule when a single paddingTurn sets both a list of explicit padding variants (turn.variants) and a send-length range (turn.sendMinLength/turn.sendMaxLength). The padding engine supports two mutually exclusive ways to size the bytes a side sends: precomputed variants or a randomized send range. Combining them is ambiguous, so the schedule is rejected before any traffic flows.
Source
Thrown at app/commander/commander.go:50
listen: config.Listen,
}
common.Must(core.RequireFeatures(ctx, func(om outbound.Manager) {
c.ohm = om
}))
for _, rawConfig := range config.Service {
config, err := rawConfig.GetInstance()
if err != nil {
return nil, err
}
rawService, err := common.CreateObject(ctx, config)
if err != nil {
return nil, err
}
service, ok := rawService.(Service)
if !ok {
return nil, errors.New("not a Service.")
}
c.services = append(c.services, service)
}
return c, nil
}
// Type implements common.HasType.
func (c *Commander) Type() interface{} {
return (*Commander)(nil)
}
// Start implements common.Runnable.
func (c *Commander) Start() error {
c.Lock()
c.server = grpc.NewServer()
for _, service := range c.services {
service.Register(c.server)View on GitHub (pinned to 7d214f8b09)
Solutions
- Remove sendMinLength/sendMaxLength from the turn so only variants size the traffic
- Or remove the variants array and express the turn purely as minLength/maxLength plus sendMinLength/sendMaxLength
- Re-run the connection; validation happens at schedule start, so no restart state is carried over
Example fix
// before
paddingTurn{
direction: paddingClientToServer,
variants: []paddingVariant{...},
sendMinLength: 100,
sendMaxLength: 400,
}
// after
paddingTurn{
direction: paddingClientToServer,
variants: []paddingVariant{...},
} Defensive patterns
Strategy: validation
Validate before calling
func turnIsConsistent(t paddingTurn) bool {
hasSendRange := t.sendMinLength != 0 || t.sendMaxLength != 0
return !(len(t.variants) > 0 && hasSendRange)
}
// before dialing:
for i, t := range schedule {
if !turnIsConsistent(t) {
return fmt.Errorf("turn %d mixes variants and send range", i)
}
} Prevention
- Build schedules through one constructor that enforces either variants or a send range, never both
- Assert turnIsConsistent in schedule unit tests
- Encode the invariant in JSON schema/validation for user-supplied configs
When it happens
Trigger: A paddingTurn literal (or decoded JSON config) that has a non-empty variants array while sendMinLength or sendMaxLength is non-zero. runPaddingSchedule calls validatePaddingSchedule up front, so this fires immediately at handshake/startup, before any network I/O.
Common situations: Copying a variants-based schedule and pasting a send-range block into the same turn; migrating config from range-style padding to variant-style padding without removing the old sendMinLength/sendMaxLength fields; defaults in a shared template that pre-populate sendMinLength.
Related errors
- Dispatcher: Invalid destination.
- FakeDNSEngine is not initialized, but such a sniffer is used
- Failed to convert address to Net IP.
- unexpected client IP length
- failed to create hosts
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/78fb9f246427b050.
Report an issue: GitHub.