|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "github.com/dustin/go-humanize" |
| 5 | + "github.com/pbnjay/memory" |
| 6 | + |
| 7 | + "github.com/ipfs/someguy/internal/fd" |
| 8 | + "github.com/libp2p/go-libp2p" |
| 9 | + "github.com/libp2p/go-libp2p/core/network" |
| 10 | + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" |
| 11 | +) |
| 12 | + |
| 13 | +// Note: this comes from rainbow/rcmgr.go with minimal adaptations. |
| 14 | + |
| 15 | +var infiniteResourceLimits = rcmgr.InfiniteLimits.ToPartialLimitConfig().System |
| 16 | + |
| 17 | +func makeResourceMgrs(maxMemory uint64, maxFD int, connMgrHighWater int) (rm network.ResourceManager, err error) { |
| 18 | + if maxMemory == 0 { |
| 19 | + maxMemory = uint64((float64(memory.TotalMemory()) * 0.85)) |
| 20 | + } |
| 21 | + if maxFD == 0 { |
| 22 | + maxFD = fd.GetNumFDs() / 2 |
| 23 | + } |
| 24 | + return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(makeResourceManagerConfig(maxMemory, maxFD, connMgrHighWater))) |
| 25 | +} |
| 26 | + |
| 27 | +func makeResourceManagerConfig(maxMemory uint64, maxFD int, connMgrHighWater int) (limitConfig rcmgr.ConcreteLimitConfig) { |
| 28 | + if maxMemory == 0 { |
| 29 | + maxMemory = uint64((float64(memory.TotalMemory()) * 0.85)) |
| 30 | + } |
| 31 | + if maxFD == 0 { |
| 32 | + maxFD = fd.GetNumFDs() / 2 |
| 33 | + } |
| 34 | + |
| 35 | + maxMemoryMB := maxMemory / (1024 * 1024) |
| 36 | + |
| 37 | + // At least as of 2023-01-25, it's possible to open a connection that |
| 38 | + // doesn't ask for any memory usage with the libp2p Resource Manager/Accountant |
| 39 | + // (see https://github.com/libp2p/go-libp2p/issues/2010#issuecomment-1404280736). |
| 40 | + // As a result, we can't currently rely on Memory limits to full protect us. |
| 41 | + // Until https://github.com/libp2p/go-libp2p/issues/2010 is addressed, |
| 42 | + // we take a proxy now of restricting to 1 inbound connection per MB. |
| 43 | + // Note: this is more generous than go-libp2p's default autoscaled limits which do |
| 44 | + // 64 connections per 1GB |
| 45 | + // (see https://github.com/libp2p/go-libp2p/blob/master/p2p/host/resource-manager/limit_defaults.go#L357 ). |
| 46 | + systemConnsInbound := int(1 * maxMemoryMB) |
| 47 | + |
| 48 | + partialLimits := rcmgr.PartialLimitConfig{ |
| 49 | + System: rcmgr.ResourceLimits{ |
| 50 | + Memory: rcmgr.LimitVal64(maxMemory), |
| 51 | + FD: rcmgr.LimitVal(maxFD), |
| 52 | + |
| 53 | + Conns: rcmgr.Unlimited, |
| 54 | + ConnsInbound: rcmgr.LimitVal(systemConnsInbound), |
| 55 | + ConnsOutbound: rcmgr.Unlimited, |
| 56 | + |
| 57 | + Streams: rcmgr.Unlimited, |
| 58 | + StreamsOutbound: rcmgr.Unlimited, |
| 59 | + StreamsInbound: rcmgr.Unlimited, |
| 60 | + }, |
| 61 | + |
| 62 | + // Transient connections won't cause any memory to be accounted for by the resource manager/accountant. |
| 63 | + // Only established connections do. |
| 64 | + // As a result, we can't rely on System.Memory to protect us from a bunch of transient connection being opened. |
| 65 | + // We limit the same values as the System scope, but only allow the Transient scope to take 25% of what is allowed for the System scope. |
| 66 | + Transient: rcmgr.ResourceLimits{ |
| 67 | + Memory: rcmgr.LimitVal64(maxMemory / 4), |
| 68 | + FD: rcmgr.LimitVal(maxFD / 4), |
| 69 | + |
| 70 | + Conns: rcmgr.Unlimited, |
| 71 | + ConnsInbound: rcmgr.LimitVal(systemConnsInbound / 4), |
| 72 | + ConnsOutbound: rcmgr.Unlimited, |
| 73 | + |
| 74 | + Streams: rcmgr.Unlimited, |
| 75 | + StreamsInbound: rcmgr.Unlimited, |
| 76 | + StreamsOutbound: rcmgr.Unlimited, |
| 77 | + }, |
| 78 | + |
| 79 | + // Lets get out of the way of the allow list functionality. |
| 80 | + // If someone specified "Swarm.ResourceMgr.Allowlist" we should let it go through. |
| 81 | + AllowlistedSystem: infiniteResourceLimits, |
| 82 | + |
| 83 | + AllowlistedTransient: infiniteResourceLimits, |
| 84 | + |
| 85 | + // Keep it simple by not having Service, ServicePeer, Protocol, ProtocolPeer, Conn, or Stream limits. |
| 86 | + ServiceDefault: infiniteResourceLimits, |
| 87 | + |
| 88 | + ServicePeerDefault: infiniteResourceLimits, |
| 89 | + |
| 90 | + ProtocolDefault: infiniteResourceLimits, |
| 91 | + |
| 92 | + ProtocolPeerDefault: infiniteResourceLimits, |
| 93 | + |
| 94 | + Conn: infiniteResourceLimits, |
| 95 | + |
| 96 | + Stream: infiniteResourceLimits, |
| 97 | + |
| 98 | + // Limit the resources consumed by a peer. |
| 99 | + // This doesn't protect us against intentional DoS attacks since an attacker can easily spin up multiple peers. |
| 100 | + // We specify this limit against unintentional DoS attacks (e.g., a peer has a bug and is sending too much traffic intentionally). |
| 101 | + // In that case we want to keep that peer's resource consumption contained. |
| 102 | + // To keep this simple, we only constrain inbound connections and streams. |
| 103 | + PeerDefault: rcmgr.ResourceLimits{ |
| 104 | + Memory: rcmgr.Unlimited64, |
| 105 | + FD: rcmgr.Unlimited, |
| 106 | + Conns: rcmgr.Unlimited, |
| 107 | + ConnsInbound: rcmgr.DefaultLimit, |
| 108 | + ConnsOutbound: rcmgr.Unlimited, |
| 109 | + Streams: rcmgr.Unlimited, |
| 110 | + StreamsInbound: rcmgr.DefaultLimit, |
| 111 | + StreamsOutbound: rcmgr.Unlimited, |
| 112 | + }, |
| 113 | + } |
| 114 | + |
| 115 | + scalingLimitConfig := rcmgr.DefaultLimits |
| 116 | + libp2p.SetDefaultServiceLimits(&scalingLimitConfig) |
| 117 | + |
| 118 | + // Anything set above in partialLimits that had a value of rcmgr.DefaultLimit will be overridden. |
| 119 | + // Anything in scalingLimitConfig that wasn't defined in partialLimits above will be added (e.g., libp2p's default service limits). |
| 120 | + partialLimits = partialLimits.Build(scalingLimitConfig.Scale(int64(maxMemory), maxFD)).ToPartialLimitConfig() |
| 121 | + |
| 122 | + // Simple checks to override autoscaling ensuring limits make sense versus the connmgr values. |
| 123 | + // There are ways to break this, but this should catch most problems already. |
| 124 | + // We might improve this in the future. |
| 125 | + // See: https://github.com/ipfs/kubo/issues/9545 |
| 126 | + if partialLimits.System.ConnsInbound > rcmgr.DefaultLimit { |
| 127 | + maxInboundConns := int(partialLimits.System.ConnsInbound) |
| 128 | + if connmgrHighWaterTimesTwo := connMgrHighWater * 2; maxInboundConns < connmgrHighWaterTimesTwo { |
| 129 | + maxInboundConns = connmgrHighWaterTimesTwo |
| 130 | + } |
| 131 | + |
| 132 | + if maxInboundConns < 800 { |
| 133 | + maxInboundConns = 800 |
| 134 | + } |
| 135 | + |
| 136 | + // Scale System.StreamsInbound as well, but use the existing ratio of StreamsInbound to ConnsInbound |
| 137 | + if partialLimits.System.StreamsInbound > rcmgr.DefaultLimit { |
| 138 | + partialLimits.System.StreamsInbound = rcmgr.LimitVal(int64(maxInboundConns) * int64(partialLimits.System.StreamsInbound) / int64(partialLimits.System.ConnsInbound)) |
| 139 | + } |
| 140 | + partialLimits.System.ConnsInbound = rcmgr.LimitVal(maxInboundConns) |
| 141 | + } |
| 142 | + |
| 143 | + logger.Infof(` |
| 144 | +
|
| 145 | +go-libp2p Resource Manager limits based on: |
| 146 | + - --max-memory: %s |
| 147 | + - --max-fd: %d |
| 148 | +
|
| 149 | +`, humanize.Bytes(maxMemory), maxFD) |
| 150 | + |
| 151 | + // We already have a complete value thus pass in an empty ConcreteLimitConfig. |
| 152 | + return partialLimits.Build(rcmgr.ConcreteLimitConfig{}) |
| 153 | +} |
0 commit comments