feat(config): optional Gateway.MaxRangeRequestFileSize (#10997)
adds Gateway.MaxRangeRequestFileSize configuration to protect against CDN bugs where range requests over certain sizes return entire files instead of requested byte ranges, causing unexpected bandwidth costs. - default: 0 (no limit) - returns 501 Not Implemented for oversized range requests - protects against CDNs like Cloudflare that ignore range requests over 5GiB also introduces OptionalBytes type to reduce code duplication when handling byte-size configuration values, replacing manual string parsing with humanize.ParseBytes. migrates existing byte-size configs to use this new type. Fixes: https://github.com/ipfs/boxo/issues/856
Marcin Rataj committed
Nov 12, 2025 at 03:54 UTC
93f8897d7cd2b2728a877ac4776534b1ff56fde0
11 files changed
+256
-36
config/gateway.go
+9
-2
@@ -12,8 +12,9 @@ const (
12
DefaultDiagnosticServiceURL = "https://check.ipfs.network"
13
14
// Gateway limit defaults from boxo
15
- DefaultRetrievalTimeout = gateway.DefaultRetrievalTimeout
16
- DefaultMaxConcurrentRequests = gateway.DefaultMaxConcurrentRequests
15
+ DefaultRetrievalTimeout = gateway.DefaultRetrievalTimeout
16
+ DefaultMaxConcurrentRequests = gateway.DefaultMaxConcurrentRequests
17
+ DefaultMaxRangeRequestFileSize = 0 // 0 means no limit
18
)
19
20
type GatewaySpec struct {
@@ -100,6 +101,12 @@ type Gateway struct {
101
// A value of 0 disables the limit.
102
MaxConcurrentRequests *OptionalInteger `json:",omitempty"`
103
104
+ // MaxRangeRequestFileSize limits the maximum file size for HTTP range requests.
105
+ // Range requests for files larger than this limit return 501 Not Implemented.
106
+ // This protects against CDN issues with large file range requests and prevents
107
+ // excessive bandwidth consumption. A value of 0 disables the limit.
108
+ MaxRangeRequestFileSize *OptionalBytes `json:",omitempty"`
109
+
110
// DiagnosticServiceURL is the URL for a service to diagnose CID retrievability issues.
111
// When the gateway returns a 504 Gateway Timeout error, an "Inspect retrievability of CID"
112
// button will be shown that links to this service with the CID appended as ?cid=<CID-to-diagnose>.
config/import.go
+2
-2
@@ -17,7 +17,7 @@ const (
17
DefaultUnixFSChunker = "size-262144"
18
DefaultHashFunction = "sha2-256"
19
20
- DefaultUnixFSHAMTDirectorySizeThreshold = "256KiB" // https://github.com/ipfs/boxo/blob/6c5a07602aed248acc86598f30ab61923a54a83e/ipld/unixfs/io/directory.go#L26
20
+ DefaultUnixFSHAMTDirectorySizeThreshold = 262144 // 256KiB - https://github.com/ipfs/boxo/blob/6c5a07602aed248acc86598f30ab61923a54a83e/ipld/unixfs/io/directory.go#L26
21
22
// DefaultBatchMaxNodes controls the maximum number of nodes in a
23
// write-batch. The total size of the batch is limited by
@@ -45,7 +45,7 @@ type Import struct {
45
UnixFSFileMaxLinks OptionalInteger
46
UnixFSDirectoryMaxLinks OptionalInteger
47
UnixFSHAMTDirectoryMaxFanout OptionalInteger
48
- UnixFSHAMTDirectorySizeThreshold OptionalString
48
+ UnixFSHAMTDirectorySizeThreshold OptionalBytes
49
BatchMaxNodes OptionalInteger
50
BatchMaxSize OptionalInteger
51
}
config/profile.go
+3
-3
@@ -322,7 +322,7 @@ fetching may be degraded.
322
c.Import.UnixFSFileMaxLinks = *NewOptionalInteger(174)
323
c.Import.UnixFSDirectoryMaxLinks = *NewOptionalInteger(0)
324
c.Import.UnixFSHAMTDirectoryMaxFanout = *NewOptionalInteger(256)
325
- c.Import.UnixFSHAMTDirectorySizeThreshold = *NewOptionalString("256KiB")
325
+ c.Import.UnixFSHAMTDirectorySizeThreshold = *NewOptionalBytes("256KiB")
326
return nil
327
},
328
},
@@ -336,7 +336,7 @@ fetching may be degraded.
336
c.Import.UnixFSFileMaxLinks = *NewOptionalInteger(174)
337
c.Import.UnixFSDirectoryMaxLinks = *NewOptionalInteger(0)
338
c.Import.UnixFSHAMTDirectoryMaxFanout = *NewOptionalInteger(256)
339
- c.Import.UnixFSHAMTDirectorySizeThreshold = *NewOptionalString("256KiB")
339
+ c.Import.UnixFSHAMTDirectorySizeThreshold = *NewOptionalBytes("256KiB")
340
return nil
341
},
342
},
@@ -350,7 +350,7 @@ fetching may be degraded.
350
c.Import.UnixFSFileMaxLinks = *NewOptionalInteger(1024)
351
c.Import.UnixFSDirectoryMaxLinks = *NewOptionalInteger(0) // no limit here, use size-based Import.UnixFSHAMTDirectorySizeThreshold instead
352
c.Import.UnixFSHAMTDirectoryMaxFanout = *NewOptionalInteger(1024)
353
- c.Import.UnixFSHAMTDirectorySizeThreshold = *NewOptionalString("1MiB") // 1MiB
353
+ c.Import.UnixFSHAMTDirectorySizeThreshold = *NewOptionalBytes("1MiB") // 1MiB
354
return nil
355
},
356
},
config/swarm.go
+1
-1
@@ -118,7 +118,7 @@ type ResourceMgr struct {
118
Enabled Flag `json:",omitempty"`
119
Limits swarmLimits `json:",omitempty"`
120
121
- MaxMemory *OptionalString `json:",omitempty"`
121
+ MaxMemory *OptionalBytes `json:",omitempty"`
122
MaxFileDescriptors *OptionalInteger `json:",omitempty"`
123
124
// A list of multiaddrs that can bypass normal system limits (but are still
config/types.go
+75
-2
@@ -7,6 +7,8 @@ import (
7
"io"
8
"strings"
9
"time"
10
+
11
+ humanize "github.com/dustin/go-humanize"
12
)
13
14
// Strings is a helper type that (un)marshals a single string to/from a single
@@ -425,8 +427,79 @@ func (p OptionalString) String() string {
427
}
428
429
var (
428
- _ json.Unmarshaler = (*OptionalInteger)(nil)
429
- _ json.Marshaler = (*OptionalInteger)(nil)
430
+ _ json.Unmarshaler = (*OptionalString)(nil)
431
+ _ json.Marshaler = (*OptionalString)(nil)
432
+)
433
+
434
+// OptionalBytes represents a byte size that has a default value
435
+//
436
+// When encoded in json, Default is encoded as "null".
437
+// Stores the original string representation and parses on access.
438
+// Embeds OptionalString to share common functionality.
439
+type OptionalBytes struct {
440
+ OptionalString
441
+}
442
+
443
+// NewOptionalBytes returns an OptionalBytes from a string.
444
+func NewOptionalBytes(s string) *OptionalBytes {
445
+ return &OptionalBytes{OptionalString{value: &s}}
446
+}
447
+
448
+// IsDefault returns if this is a default optional byte value.
449
+func (p *OptionalBytes) IsDefault() bool {
450
+ if p == nil {
451
+ return true
452
+ }
453
+ return p.OptionalString.IsDefault()
454
+}
455
+
456
+// WithDefault resolves the byte size with the given default.
457
+// Parses the stored string value using humanize.ParseBytes.
458
+func (p *OptionalBytes) WithDefault(defaultValue uint64) (value uint64) {
459
+ if p.IsDefault() {
460
+ return defaultValue
461
+ }
462
+ strValue := p.OptionalString.WithDefault("")
463
+ bytes, err := humanize.ParseBytes(strValue)
464
+ if err != nil {
465
+ // This should never happen as values are validated during UnmarshalJSON.
466
+ // If it does, it indicates either config corruption or a programming error.
467
+ panic(fmt.Sprintf("invalid byte size in OptionalBytes: %q - %v", strValue, err))
468
+ }
469
+ return bytes
470
+}
471
+
472
+// UnmarshalJSON validates the input is a parseable byte size.
473
+func (p *OptionalBytes) UnmarshalJSON(input []byte) error {
474
+ switch string(input) {
475
+ case "null", "undefined":
476
+ *p = OptionalBytes{}
477
+ default:
478
+ var value interface{}
479
+ err := json.Unmarshal(input, &value)
480
+ if err != nil {
481
+ return err
482
+ }
483
+ switch v := value.(type) {
484
+ case float64:
485
+ str := fmt.Sprintf("%.0f", v)
486
+ p.value = &str
487
+ case string:
488
+ _, err := humanize.ParseBytes(v)
489
+ if err != nil {
490
+ return err
491
+ }
492
+ p.value = &v
493
+ default:
494
+ return fmt.Errorf("unable to parse byte size, expected a size string (e.g., \"5GiB\") or a number, but got %T", v)
495
+ }
496
+ }
497
+ return nil
498
+}
499
+
500
+var (
501
+ _ json.Unmarshaler = (*OptionalBytes)(nil)
502
+ _ json.Marshaler = (*OptionalBytes)(nil)
503
)
504
505
type swarmLimits doNotUse
config/types_test.go
+125
@@ -5,6 +5,9 @@ import (
5
"encoding/json"
6
"testing"
7
"time"
8
+
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
)
12
13
func TestOptionalDuration(t *testing.T) {
@@ -509,3 +512,125 @@ func TestOptionalString(t *testing.T) {
512
}
513
}
514
}
515
+
516
+func TestOptionalBytes(t *testing.T) {
517
+ makeStringPointer := func(v string) *string { return &v }
518
+
519
+ t.Run("default value", func(t *testing.T) {
520
+ var b OptionalBytes
521
+ assert.True(t, b.IsDefault())
522
+ assert.Equal(t, uint64(0), b.WithDefault(0))
523
+ assert.Equal(t, uint64(1024), b.WithDefault(1024))
524
+ assert.Equal(t, "default", b.String())
525
+ })
526
+
527
+ t.Run("non-default value", func(t *testing.T) {
528
+ b := OptionalBytes{OptionalString{value: makeStringPointer("1MiB")}}
529
+ assert.False(t, b.IsDefault())
530
+ assert.Equal(t, uint64(1048576), b.WithDefault(512))
531
+ assert.Equal(t, "1MiB", b.String())
532
+ })
533
+
534
+ t.Run("JSON roundtrip", func(t *testing.T) {
535
+ testCases := []struct {
536
+ jsonInput string
537
+ jsonOutput string
538
+ expectedValue string
539
+ }{
540
+ {"null", "null", ""},
541
+ {"\"256KiB\"", "\"256KiB\"", "256KiB"},
542
+ {"\"1MiB\"", "\"1MiB\"", "1MiB"},
543
+ {"\"5GiB\"", "\"5GiB\"", "5GiB"},
544
+ {"\"256KB\"", "\"256KB\"", "256KB"},
545
+ {"1048576", "\"1048576\"", "1048576"},
546
+ }
547
+
548
+ for _, tc := range testCases {
549
+ t.Run(tc.jsonInput, func(t *testing.T) {
550
+ var b OptionalBytes
551
+ err := json.Unmarshal([]byte(tc.jsonInput), &b)
552
+ require.NoError(t, err)
553
+
554
+ if tc.expectedValue == "" {
555
+ assert.Nil(t, b.value)
556
+ } else {
557
+ require.NotNil(t, b.value)
558
+ assert.Equal(t, tc.expectedValue, *b.value)
559
+ }
560
+
561
+ out, err := json.Marshal(b)
562
+ require.NoError(t, err)
563
+ assert.Equal(t, tc.jsonOutput, string(out))
564
+ })
565
+ }
566
+ })
567
+
568
+ t.Run("parsing byte sizes", func(t *testing.T) {
569
+ testCases := []struct {
570
+ input string
571
+ expected uint64
572
+ }{
573
+ {"256KiB", 262144},
574
+ {"1MiB", 1048576},
575
+ {"5GiB", 5368709120},
576
+ {"256KB", 256000},
577
+ {"1048576", 1048576},
578
+ }
579
+
580
+ for _, tc := range testCases {
581
+ t.Run(tc.input, func(t *testing.T) {
582
+ var b OptionalBytes
583
+ err := json.Unmarshal([]byte("\""+tc.input+"\""), &b)
584
+ require.NoError(t, err)
585
+ assert.Equal(t, tc.expected, b.WithDefault(0))
586
+ })
587
+ }
588
+ })
589
+
590
+ t.Run("omitempty", func(t *testing.T) {
591
+ type Foo struct {
592
+ B *OptionalBytes `json:",omitempty"`
593
+ }
594
+
595
+ out, err := json.Marshal(new(Foo))
596
+ require.NoError(t, err)
597
+ assert.Equal(t, "{}", string(out))
598
+
599
+ var foo2 Foo
600
+ err = json.Unmarshal(out, &foo2)
601
+ require.NoError(t, err)
602
+
603
+ if foo2.B != nil {
604
+ assert.Equal(t, uint64(1024), foo2.B.WithDefault(1024))
605
+ assert.True(t, foo2.B.IsDefault())
606
+ } else {
607
+ // When field is omitted, pointer is nil which is also considered default
608
+ t.Log("B is nil, which is acceptable for omitempty")
609
+ }
610
+ })
611
+
612
+ t.Run("invalid values", func(t *testing.T) {
613
+ invalidInputs := []string{
614
+ "\"5XiB\"", "\"invalid\"", "\"\"", "[]", "{}",
615
+ }
616
+
617
+ for _, invalid := range invalidInputs {
618
+ t.Run(invalid, func(t *testing.T) {
619
+ var b OptionalBytes
620
+ err := json.Unmarshal([]byte(invalid), &b)
621
+ assert.Error(t, err)
622
+ })
623
+ }
624
+ })
625
+
626
+ t.Run("panic on invalid stored value", func(t *testing.T) {
627
+ // This tests that if somehow an invalid value gets stored
628
+ // (bypassing UnmarshalJSON validation), WithDefault will panic
629
+ invalidValue := "invalid-size"
630
+ b := OptionalBytes{OptionalString{value: &invalidValue}}
631
+
632
+ assert.Panics(t, func() {
633
+ b.WithDefault(1024)
634
+ }, "should panic on invalid stored value")
635
+ })
636
+}
core/corehttp/gateway.go
+12
-10
@@ -111,9 +111,10 @@ func Libp2pGatewayOption() ServeOption {
111
PublicGateways: nil,
112
Menu: nil,
113
// Apply timeout and concurrency limits from user config
114
- RetrievalTimeout: cfg.Gateway.RetrievalTimeout.WithDefault(config.DefaultRetrievalTimeout),
115
- MaxConcurrentRequests: int(cfg.Gateway.MaxConcurrentRequests.WithDefault(int64(config.DefaultMaxConcurrentRequests))),
116
- DiagnosticServiceURL: "", // Not used since DisableHTMLErrors=true
114
+ RetrievalTimeout: cfg.Gateway.RetrievalTimeout.WithDefault(config.DefaultRetrievalTimeout),
115
+ MaxConcurrentRequests: int(cfg.Gateway.MaxConcurrentRequests.WithDefault(int64(config.DefaultMaxConcurrentRequests))),
116
+ MaxRangeRequestFileSize: int64(cfg.Gateway.MaxRangeRequestFileSize.WithDefault(uint64(config.DefaultMaxRangeRequestFileSize))),
117
+ DiagnosticServiceURL: "", // Not used since DisableHTMLErrors=true
118
}
119
120
handler := gateway.NewHandler(gwConfig, &offlineGatewayErrWrapper{gwimpl: backend})
@@ -266,13 +267,14 @@ func getGatewayConfig(n *core.IpfsNode) (gateway.Config, map[string][]string, er
267
268
// Initialize gateway configuration, with empty PublicGateways, handled after.
269
gwCfg := gateway.Config{
269
- DeserializedResponses: cfg.Gateway.DeserializedResponses.WithDefault(config.DefaultDeserializedResponses),
270
- DisableHTMLErrors: cfg.Gateway.DisableHTMLErrors.WithDefault(config.DefaultDisableHTMLErrors),
271
- NoDNSLink: cfg.Gateway.NoDNSLink,
272
- PublicGateways: map[string]*gateway.PublicGateway{},
273
- RetrievalTimeout: cfg.Gateway.RetrievalTimeout.WithDefault(config.DefaultRetrievalTimeout),
274
- MaxConcurrentRequests: int(cfg.Gateway.MaxConcurrentRequests.WithDefault(int64(config.DefaultMaxConcurrentRequests))),
275
- DiagnosticServiceURL: cfg.Gateway.DiagnosticServiceURL.WithDefault(config.DefaultDiagnosticServiceURL),
270
+ DeserializedResponses: cfg.Gateway.DeserializedResponses.WithDefault(config.DefaultDeserializedResponses),
271
+ DisableHTMLErrors: cfg.Gateway.DisableHTMLErrors.WithDefault(config.DefaultDisableHTMLErrors),
272
+ NoDNSLink: cfg.Gateway.NoDNSLink,
273
+ PublicGateways: map[string]*gateway.PublicGateway{},
274
+ RetrievalTimeout: cfg.Gateway.RetrievalTimeout.WithDefault(config.DefaultRetrievalTimeout),
275
+ MaxConcurrentRequests: int(cfg.Gateway.MaxConcurrentRequests.WithDefault(int64(config.DefaultMaxConcurrentRequests))),
276
+ MaxRangeRequestFileSize: int64(cfg.Gateway.MaxRangeRequestFileSize.WithDefault(uint64(config.DefaultMaxRangeRequestFileSize))),
277
+ DiagnosticServiceURL: cfg.Gateway.DiagnosticServiceURL.WithDefault(config.DefaultDiagnosticServiceURL),
278
}
279
280
// Add default implicit known gateways, such as subdomain gateway on localhost.
core/node/groups.go
+5
-7
@@ -8,7 +8,6 @@ import (
8
"strings"
9
"time"
10
11
- "github.com/dustin/go-humanize"
11
blockstore "github.com/ipfs/boxo/blockstore"
12
offline "github.com/ipfs/boxo/exchange/offline"
13
uio "github.com/ipfs/boxo/ipld/unixfs/io"
@@ -423,7 +422,10 @@ func IPFS(ctx context.Context, bcfg *BuildCfg) fx.Option {
422
logger.Fatal(msg) // conflicting values, hard fail
423
}
424
logger.Error(msg)
426
- cfg.Import.UnixFSHAMTDirectorySizeThreshold = *cfg.Internal.UnixFSShardingSizeThreshold
425
+ // Migrate the old OptionalString value to the new OptionalBytes field.
426
+ // Since OptionalBytes embeds OptionalString, we can construct it directly
427
+ // with the old value, preserving the user's original string (e.g., "256KiB").
428
+ cfg.Import.UnixFSHAMTDirectorySizeThreshold = config.OptionalBytes{OptionalString: *cfg.Internal.UnixFSShardingSizeThreshold}
429
}
430
431
// Validate Import configuration
@@ -437,11 +439,7 @@ func IPFS(ctx context.Context, bcfg *BuildCfg) fx.Option {
439
}
440
441
// Auto-sharding settings
440
- shardingThresholdString := cfg.Import.UnixFSHAMTDirectorySizeThreshold.WithDefault(config.DefaultUnixFSHAMTDirectorySizeThreshold)
441
- shardSingThresholdInt, err := humanize.ParseBytes(shardingThresholdString)
442
- if err != nil {
443
- return fx.Error(err)
444
- }
442
+ shardSingThresholdInt := cfg.Import.UnixFSHAMTDirectorySizeThreshold.WithDefault(config.DefaultUnixFSHAMTDirectorySizeThreshold)
443
shardMaxFanout := cfg.Import.UnixFSHAMTDirectoryMaxFanout.WithDefault(config.DefaultUnixFSHAMTDirectoryMaxFanout)
444
// TODO: avoid overriding this globally, see if we can extend Directory interface like Get/SetMaxLinks from https://github.com/ipfs/boxo/pull/906
445
uio.HAMTShardingSize = int(shardSingThresholdInt)
core/node/libp2p/rcmgr_defaults.go
+3
-7
@@ -19,12 +19,8 @@ var infiniteResourceLimits = rcmgr.InfiniteLimits.ToPartialLimitConfig().System
19
// The defaults follow the documentation in docs/libp2p-resource-management.md.
20
// Any changes in the logic here should be reflected there.
21
func createDefaultLimitConfig(cfg config.SwarmConfig) (limitConfig rcmgr.ConcreteLimitConfig, logMessageForStartup string, err error) {
22
- maxMemoryDefaultString := humanize.Bytes(uint64(memory.TotalMemory()) / 2)
23
- maxMemoryString := cfg.ResourceMgr.MaxMemory.WithDefault(maxMemoryDefaultString)
24
- maxMemory, err := humanize.ParseBytes(maxMemoryString)
25
- if err != nil {
26
- return rcmgr.ConcreteLimitConfig{}, "", err
27
- }
22
+ maxMemoryDefault := uint64(memory.TotalMemory()) / 2
23
+ maxMemory := cfg.ResourceMgr.MaxMemory.WithDefault(maxMemoryDefault)
24
25
maxMemoryMB := maxMemory / (1024 * 1024)
26
maxFD := int(cfg.ResourceMgr.MaxFileDescriptors.WithDefault(int64(fd.GetNumFDs()) / 2))
@@ -142,7 +138,7 @@ Computed default go-libp2p Resource Manager limits based on:
138
139
These can be inspected with 'ipfs swarm resources'.
140
145
-`, maxMemoryString, maxFD)
141
+`, humanize.Bytes(maxMemory), maxFD)
142
143
// We already have a complete value thus pass in an empty ConcreteLimitConfig.
144
return partialLimits.Build(rcmgr.ConcreteLimitConfig{}), msg, nil
docs/changelogs/v0.39.md
+5
@@ -24,6 +24,11 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
24
25
### 🔦 Highlights
26
27
+#### 🚦 Gateway range request limits for CDN compatibility
28
+
29
+The new [`Gateway.MaxRangeRequestFileSize`](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewaymaxrangerequestfilesize) configuration protects against CDN bugs where range requests over a certain size are silently ignored and the entire file is returned instead ([boxo#856](https://github.com/ipfs/boxo/issues/856#issuecomment-2786431369)). This causes unexpected bandwidth costs for both gateway operators and clients who only wanted a small byte range.
30
+
31
+Set this to your CDN's range request limit (e.g., `"5GiB"` for Cloudflare's default plan) to return 501 Not Implemented for oversized range requests, with an error message suggesting verifiable block requests as an alternative.
32
#### 📊 Detailed statistics for Sweep provider with `ipfs provide stat`
33
34
The experimental Sweep provider system ([introduced in
docs/config.md
+16
-2
@@ -66,6 +66,7 @@ config file at runtime.
66
- [`Gateway.DisableHTMLErrors`](#gatewaydisablehtmlerrors)
67
- [`Gateway.ExposeRoutingAPI`](#gatewayexposeroutingapi)
68
- [`Gateway.RetrievalTimeout`](#gatewayretrievaltimeout)
69
+ - [`Gateway.MaxRangeRequestFileSize`](#gatewaymaxrangerequestfilesize)
70
- [`Gateway.MaxConcurrentRequests`](#gatewaymaxconcurrentrequests)
71
- [`Gateway.HTTPHeaders`](#gatewayhttpheaders)
72
- [`Gateway.RootRedirect`](#gatewayrootredirect)
@@ -1159,6 +1160,18 @@ Default: `30s`
1160
1161
Type: `optionalDuration`
1162
1163
+### `Gateway.MaxRangeRequestFileSize`
1164
+
1165
+Maximum file size for HTTP range requests. Range requests for files larger than this limit return 501 Not Implemented.
1166
+
1167
+Protects against CDN bugs where range requests are silently ignored and the entire file is returned instead. For example, Cloudflare's default plan returns the full file for range requests over 5GiB, causing unexpected bandwidth costs for both gateway operators and clients who only wanted a small byte range.
1168
+
1169
+Set this to your CDN's range request limit (e.g., `"5GiB"` for Cloudflare's default plan). The error response suggests using verifiable block requests (application/vnd.ipld.raw) as an alternative.
1170
+
1171
+Default: `0` (no limit)
1172
+
1173
+Type: [`optionalBytes`](#optionalbytes)
1174
+
1175
### `Gateway.MaxConcurrentRequests`
1176
1177
Limits concurrent HTTP requests. Requests beyond limit receive 429 Too Many Requests.
@@ -3145,7 +3158,7 @@ It is possible to inspect the runtime limits via `ipfs swarm resources --help`.
3158
> To set memory limit for the entire Kubo process, use [`GOMEMLIMIT` environment variable](http://web.archive.org/web/20240222201412/https://kupczynski.info/posts/go-container-aware/) which all Go programs recognize, and then set `Swarm.ResourceMgr.MaxMemory` to less than your custom `GOMEMLIMIT`.
3159
3160
Default: `[TOTAL_SYSTEM_MEMORY]/2`
3148
-Type: `optionalBytes`
3161
+Type: [`optionalBytes`](#optionalbytes)
3162
3163
#### `Swarm.ResourceMgr.MaxFileDescriptors`
3164
@@ -3698,7 +3711,7 @@ Commands affected: `ipfs add`, `ipfs daemon` (globally overrides [`boxo/ipld/uni
3711
3712
Default: `256KiB` (may change, inspect `DefaultUnixFSHAMTDirectorySizeThreshold` to confirm)
3713
3701
-Type: `optionalBytes`
3714
+Type: [`optionalBytes`](#optionalbytes)
3715
3716
## `Version`
3717
@@ -4015,6 +4028,7 @@ an implicit default when missing from the config file:
4028
- a string value indicating the number of bytes, including human readable representations:
4029
- [SI sizes](https://en.wikipedia.org/wiki/Metric_prefix#List_of_SI_prefixes) (metric units, powers of 1000), e.g. `1B`, `2kB`, `3MB`, `4GB`, `5TB`, …)
4030
- [IEC sizes](https://en.wikipedia.org/wiki/Binary_prefix#IEC_prefixes) (binary units, powers of 1024), e.g. `1B`, `2KiB`, `3MiB`, `4GiB`, `5TiB`, …)
4031
+- a raw number (will be interpreted as bytes, e.g. `1048576` for 1MiB)
4032
4033
### `optionalString`
4034