XTLS/Xray-core · error
MaxSendingWindow must be >= Mtu
Error message
MaxSendingWindow must be >= Mtu
What it means
Thrown by KCPConfig.Build() when GetSendingBufferSize() returns 0. That helper derives the send buffer size from MaxSendingWindow (in MB) combined with Mtu; if the window is too small to hold even one MTU-sized packet, the buffer size computes to zero and the transport could never queue a packet. Hence the message 'MaxSendingWindow must be >= Mtu'.
Source
Thrown at infra/conf/transport_method.go:572
}
if c.CwndMultiplier != nil {
config.CwndMultiplier = *c.CwndMultiplier
}
if c.MaxSendingWindow != nil {
config.MaxSendingWindow = *c.MaxSendingWindow
}
if config.Mtu < 21 {
return nil, errors.New("Mtu must be at least 21").AtError()
}
if config.Tti < 10 || config.Tti > 1000 {
return nil, errors.New("invalid mKCP TTI: ", c.Tti).AtError()
}
if config.CwndMultiplier < 1 {
return nil, errors.New("CwndMultiplier must be at least 1").AtError()
}
if config.GetSendingBufferSize() == 0 {
return nil, errors.New("MaxSendingWindow must be >= Mtu").AtError()
}
return config, nil
}
type GRPCConfig struct {
Authority string `json:"authority"`
ServiceName string `json:"serviceName"`
MultiMode bool `json:"multiMode"`
IdleTimeout int32 `json:"idle_timeout"`
HealthCheckTimeout int32 `json:"health_check_timeout"`
PermitWithoutStream bool `json:"permit_without_stream"`
InitialWindowsSize int32 `json:"initial_windows_size"`
UserAgent string `json:"user_agent"`
}
func (g *GRPCConfig) Build() (proto.Message, error) {
if g.IdleTimeout <= 0 {View on GitHub (pinned to 7d214f8b09)
Solutions
- Set maxSendingWindow large enough that (value_in_MB * 1e6) / mtu >= 1, in practice use 10-1024.
- Verify mtu is a sane value (1200-1500), not something enormous.
- Remove maxSendingWindow to let the default apply.
Example fix
// before
"kcpSettings": { "mtu": 1350, "maxSendingWindow": 0 }
// after
"kcpSettings": { "mtu": 1350, "maxSendingWindow": 50 } Defensive patterns
Strategy: validation
Validate before calling
mtu := 1350
if kcp.Mtu != nil { mtu = int(*kcp.Mtu) }
windowBytes := uint64(0)
if kcp.MaxSendingWindow != nil { windowBytes = uint64(*kcp.MaxSendingWindow) * 1024 * 1024 }
if windowBytes != 0 && windowBytes/uint64(mtu) == 0 {
return errors.New("maxSendingWindow too small for mtu")
} Try / catch
if _, err := kcpCfg.Build(); err != nil && strings.Contains(err.Error(), "MaxSendingWindow") {
kcp.MaxSendingWindow = nil // use default and rebuild
_, err = kcpCfg.Build()
if err != nil { return err }
} Prevention
- Never set maxSendingWindow to 0 intending 'unlimited' — omit it instead.
- When raising mtu, sanity-check the window still covers many packets.
When it happens
Trigger: Setting maxSendingWindow (MB) so small that windowMB*1e6/mtu rounds to 0 — e.g. mtu 1350 with maxSendingWindow 0 or a fraction too small — or leaving maxSendingWindow unset while a huge custom mtu makes the division floor to zero.
Common situations: Users setting maxSendingWindow: 0 thinking it means 'unlimited', or pairing a very large mtu with the minimum window size.
Related errors
- Mtu must be at least 21
- invalid mKCP TTI:
- CwndMultiplier must be at least 1
- invalid header
- gecko: invalid min/max packet size
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/5c6034ea744e42bd.
Report an issue: GitHub.