{"record":{"id":"5c6034ea744e42bd","repo":"XTLS/Xray-core","slug":"maxsendingwindow-must-be-mtu","errorCode":null,"errorMessage":"MaxSendingWindow must be >= Mtu","messagePattern":"MaxSendingWindow must be >= Mtu","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"infra/conf/transport_method.go","lineNumber":572,"sourceCode":"\t}\n\tif c.CwndMultiplier != nil {\n\t\tconfig.CwndMultiplier = *c.CwndMultiplier\n\t}\n\tif c.MaxSendingWindow != nil {\n\t\tconfig.MaxSendingWindow = *c.MaxSendingWindow\n\t}\n\n\tif config.Mtu < 21 {\n\t\treturn nil, errors.New(\"Mtu must be at least 21\").AtError()\n\t}\n\tif config.Tti < 10 || config.Tti > 1000 {\n\t\treturn nil, errors.New(\"invalid mKCP TTI: \", c.Tti).AtError()\n\t}\n\tif config.CwndMultiplier < 1 {\n\t\treturn nil, errors.New(\"CwndMultiplier must be at least 1\").AtError()\n\t}\n\tif config.GetSendingBufferSize() == 0 {\n\t\treturn nil, errors.New(\"MaxSendingWindow must be >= Mtu\").AtError()\n\t}\n\n\treturn config, nil\n}\n\ntype GRPCConfig struct {\n\tAuthority           string `json:\"authority\"`\n\tServiceName         string `json:\"serviceName\"`\n\tMultiMode           bool   `json:\"multiMode\"`\n\tIdleTimeout         int32  `json:\"idle_timeout\"`\n\tHealthCheckTimeout  int32  `json:\"health_check_timeout\"`\n\tPermitWithoutStream bool   `json:\"permit_without_stream\"`\n\tInitialWindowsSize  int32  `json:\"initial_windows_size\"`\n\tUserAgent           string `json:\"user_agent\"`\n}\n\nfunc (g *GRPCConfig) Build() (proto.Message, error) {\n\tif g.IdleTimeout <= 0 {","sourceCodeStart":554,"sourceCodeEnd":590,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/infra/conf/transport_method.go#L554-L590","documentation":"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'.","triggerScenarios":"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.","commonSituations":"Users setting maxSendingWindow: 0 thinking it means 'unlimited', or pairing a very large mtu with the minimum window size.","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."],"exampleFix":"// before\n\"kcpSettings\": { \"mtu\": 1350, \"maxSendingWindow\": 0 }\n// after\n\"kcpSettings\": { \"mtu\": 1350, \"maxSendingWindow\": 50 }","handlingStrategy":"validation","validationCode":"mtu := 1350\nif kcp.Mtu != nil { mtu = int(*kcp.Mtu) }\nwindowBytes := uint64(0)\nif kcp.MaxSendingWindow != nil { windowBytes = uint64(*kcp.MaxSendingWindow) * 1024 * 1024 }\nif windowBytes != 0 && windowBytes/uint64(mtu) == 0 {\n    return errors.New(\"maxSendingWindow too small for mtu\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := kcpCfg.Build(); err != nil && strings.Contains(err.Error(), \"MaxSendingWindow\") {\n    kcp.MaxSendingWindow = nil // use default and rebuild\n    _, err = kcpCfg.Build()\n    if err != nil { return err }\n}","preventionTips":["Never set maxSendingWindow to 0 intending 'unlimited' — omit it instead.","When raising mtu, sanity-check the window still covers many packets."],"tags":["config","mkcp","transport","window","validation","go"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}