| 1 | package config |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "fmt" |
| 6 | "os" |
| 7 | "runtime" |
| 8 | "slices" |
| 9 | "strings" |
| 10 | ) |
| 11 | |
| 12 | const ( |
| 13 | DefaultAcceleratedDHTClient = false |
| 14 | DefaultLoopbackAddressesOnLanDHT = false |
| 15 | DefaultRoutingType = "auto" |
| 16 | CidContactRoutingURL = "https://cid.contact" |
| 17 | PublicGoodDelegatedRoutingURL = "https://delegated-ipfs.dev" // cid.contact + amino dht (incl. IPNS PUTs) |
| 18 | EnvHTTPRouters = "IPFS_HTTP_ROUTERS" |
| 19 | EnvHTTPRoutersFilterProtocols = "IPFS_HTTP_ROUTERS_FILTER_PROTOCOLS" |
| 20 | ) |
| 21 | |
| 22 | var ( |
| 23 | // Default filter-protocols to pass along with delegated routing requests (as defined in IPIP-484) |
| 24 | // and also filter out locally |
| 25 | DefaultHTTPRoutersFilterProtocols = getEnvOrDefault(EnvHTTPRoutersFilterProtocols, []string{ |
| 26 | "unknown", // allow results without protocol list, we can do libp2p identify to test them |
| 27 | "transport-bitswap", |
| 28 | // http is added dynamically in routing/delegated.go. |
| 29 | // 'transport-ipfs-gateway-http' |
| 30 | }) |
| 31 | ) |
| 32 | |
| 33 | // Routing defines configuration options for libp2p routing. |
| 34 | type Routing struct { |
| 35 | // Type sets default daemon routing mode. |
| 36 | // |
| 37 | // Can be one of "auto", "autoclient", "dht", "dhtclient", "dhtserver", "none", "delegated", or "custom". |
| 38 | // When unset or set to "auto", DHT and implicit routers are used. |
| 39 | // When "delegated" is set, only HTTP delegated routers and IPNS publishers are used (no DHT). |
| 40 | // When "custom" is set, user-provided Routing.Routers is used. |
| 41 | Type *OptionalString `json:",omitempty"` |
| 42 | |
| 43 | AcceleratedDHTClient Flag `json:",omitempty"` |
| 44 | |
| 45 | LoopbackAddressesOnLanDHT Flag `json:",omitempty"` |
| 46 | |
| 47 | IgnoreProviders []string `json:",omitempty"` |
| 48 | |
| 49 | // Simplified configuration used by default when Routing.Type=auto|autoclient |
| 50 | DelegatedRouters []string |
| 51 | |
| 52 | // Advanced configuration used when Routing.Type=custom |
| 53 | Routers Routers `json:",omitempty"` |
| 54 | Methods Methods `json:",omitempty"` |
| 55 | } |
| 56 | |
| 57 | type Router struct { |
| 58 | // Router type ID. See RouterType for more info. |
| 59 | Type RouterType |
| 60 | |
| 61 | // Parameters are extra configuration that this router might need. |
| 62 | // A common one for HTTP router is "Endpoint". |
| 63 | Parameters any |
| 64 | } |
| 65 | |
| 66 | type ( |
| 67 | Routers map[string]RouterParser |
| 68 | Methods map[MethodName]Method |
| 69 | ) |
| 70 | |
| 71 | func (m Methods) Check() error { |
| 72 | // Check supported methods |
| 73 | for _, mn := range MethodNameList { |
| 74 | _, ok := m[mn] |
| 75 | if !ok { |
| 76 | return fmt.Errorf("method name %q is missing from Routing.Methods config param", mn) |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | // Check unsupported methods |
| 81 | for k := range m { |
| 82 | seen := slices.Contains(MethodNameList, k) |
| 83 | |
| 84 | if seen { |
| 85 | continue |
| 86 | } |
| 87 | |
| 88 | return fmt.Errorf("method name %q is not a supported method on Routing.Methods config param", k) |
| 89 | } |
| 90 | |
| 91 | return nil |
| 92 | } |
| 93 | |
| 94 | type RouterParser struct { |
| 95 | Router |
| 96 | } |
| 97 | |
| 98 | func (r *RouterParser) UnmarshalJSON(b []byte) error { |
| 99 | out := Router{} |
| 100 | out.Parameters = &json.RawMessage{} |
| 101 | if err := json.Unmarshal(b, &out); err != nil { |
| 102 | return err |
| 103 | } |
| 104 | raw := out.Parameters.(*json.RawMessage) |
| 105 | |
| 106 | var p any |
| 107 | switch out.Type { |
| 108 | case RouterTypeHTTP: |
| 109 | p = &HTTPRouterParams{} |
| 110 | case RouterTypeDHT: |
| 111 | p = &DHTRouterParams{} |
| 112 | case RouterTypeSequential: |
| 113 | p = &ComposableRouterParams{} |
| 114 | case RouterTypeParallel: |
| 115 | p = &ComposableRouterParams{} |
| 116 | } |
| 117 | |
| 118 | if err := json.Unmarshal(*raw, &p); err != nil { |
| 119 | return err |
| 120 | } |
| 121 | |
| 122 | r.Router.Type = out.Type |
| 123 | r.Router.Parameters = p |
| 124 | |
| 125 | return nil |
| 126 | } |
| 127 | |
| 128 | // Type is the routing type. |
| 129 | // Depending of the type we need to instantiate different Routing implementations. |
| 130 | type RouterType string |
| 131 | |
| 132 | const ( |
| 133 | RouterTypeHTTP RouterType = "http" // HTTP JSON API for delegated routing systems (IPIP-337). |
| 134 | RouterTypeDHT RouterType = "dht" // DHT router. |
| 135 | RouterTypeSequential RouterType = "sequential" // Router helper to execute several routers sequentially. |
| 136 | RouterTypeParallel RouterType = "parallel" // Router helper to execute several routers in parallel. |
| 137 | ) |
| 138 | |
| 139 | type DHTMode string |
| 140 | |
| 141 | const ( |
| 142 | DHTModeServer DHTMode = "server" |
| 143 | DHTModeClient DHTMode = "client" |
| 144 | DHTModeAuto DHTMode = "auto" |
| 145 | ) |
| 146 | |
| 147 | type MethodName string |
| 148 | |
| 149 | const ( |
| 150 | MethodNameProvide MethodName = "provide" |
| 151 | MethodNameFindProviders MethodName = "find-providers" |
| 152 | MethodNameFindPeers MethodName = "find-peers" |
| 153 | MethodNameGetIPNS MethodName = "get-ipns" |
| 154 | MethodNamePutIPNS MethodName = "put-ipns" |
| 155 | ) |
| 156 | |
| 157 | var MethodNameList = []MethodName{MethodNameProvide, MethodNameFindPeers, MethodNameFindProviders, MethodNameGetIPNS, MethodNamePutIPNS} |
| 158 | |
| 159 | type HTTPRouterParams struct { |
| 160 | // Endpoint is the URL where the routing implementation will point to get the information. |
| 161 | Endpoint string |
| 162 | |
| 163 | // MaxProvideBatchSize determines the maximum amount of CIDs sent per batch. |
| 164 | // Servers might not accept more than 100 elements per batch. 100 elements by default. |
| 165 | MaxProvideBatchSize int |
| 166 | |
| 167 | // MaxProvideConcurrency determines the number of threads used when providing content. GOMAXPROCS by default. |
| 168 | MaxProvideConcurrency int |
| 169 | } |
| 170 | |
| 171 | func (hrp *HTTPRouterParams) FillDefaults() { |
| 172 | if hrp.MaxProvideBatchSize == 0 { |
| 173 | hrp.MaxProvideBatchSize = 100 |
| 174 | } |
| 175 | |
| 176 | if hrp.MaxProvideConcurrency == 0 { |
| 177 | hrp.MaxProvideConcurrency = runtime.GOMAXPROCS(0) |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | type DHTRouterParams struct { |
| 182 | Mode DHTMode |
| 183 | AcceleratedDHTClient bool `json:",omitempty"` |
| 184 | PublicIPNetwork bool |
| 185 | } |
| 186 | |
| 187 | type ComposableRouterParams struct { |
| 188 | Routers []ConfigRouter |
| 189 | Timeout *OptionalDuration `json:",omitempty"` |
| 190 | } |
| 191 | |
| 192 | type ConfigRouter struct { |
| 193 | RouterName string |
| 194 | Timeout Duration |
| 195 | IgnoreErrors bool |
| 196 | ExecuteAfter *OptionalDuration `json:",omitempty"` |
| 197 | } |
| 198 | |
| 199 | type Method struct { |
| 200 | RouterName string |
| 201 | } |
| 202 | |
| 203 | // getEnvOrDefault reads space or comma separated strings from env if present, |
| 204 | // and uses provided defaultValue as a fallback |
| 205 | func getEnvOrDefault(key string, defaultValue []string) []string { |
| 206 | if value, exists := os.LookupEnv(key); exists { |
| 207 | splitFunc := func(r rune) bool { return r == ',' || r == ' ' } |
| 208 | return strings.FieldsFunc(value, splitFunc) |
| 209 | } |
| 210 | return defaultValue |
| 211 | } |
| 212 | |
| 213 | // HasHTTPProviderConfigured checks if the node is configured to use HTTP routers |
| 214 | // for providing content announcements. This is used when determining if the node |
| 215 | // can provide content even when not connected to libp2p peers. |
| 216 | // |
| 217 | // Note: Right now we only support delegated HTTP content providing if Routing.Type=custom |
| 218 | // and Routing.Routers are configured according to: |
| 219 | // https://github.com/ipfs/kubo/blob/master/docs/delegated-routing.md#configuration-file-example |
| 220 | // |
| 221 | // This uses the `ProvideBitswap` request type that is not documented anywhere, |
| 222 | // because we hoped something like IPIP-378 (https://github.com/ipfs/specs/pull/378) |
| 223 | // would get finalized and we'd switch to that. It never happened due to politics, |
| 224 | // and now we are stuck with ProvideBitswap being the only API that works. |
| 225 | // Some people have reverse engineered it (example: |
| 226 | // https://discuss.ipfs.tech/t/only-peers-found-from-dht-seem-to-be-getting-used-as-relays-so-cant-use-http-routers/19545/9) |
| 227 | // and use it, so what we do here is the bare minimum to ensure their use case works |
| 228 | // using this old API until something better is available. |
| 229 | func (c *Config) HasHTTPProviderConfigured() bool { |
| 230 | if len(c.Routing.Routers) == 0 { |
| 231 | // No "custom" routers |
| 232 | return false |
| 233 | } |
| 234 | method, ok := c.Routing.Methods[MethodNameProvide] |
| 235 | if !ok { |
| 236 | // No provide method configured |
| 237 | return false |
| 238 | } |
| 239 | return c.routerSupportsHTTPProviding(method.RouterName) |
| 240 | } |
| 241 | |
| 242 | // routerSupportsHTTPProviding checks if the supplied custom router is or |
| 243 | // includes an HTTP-based router. |
| 244 | func (c *Config) routerSupportsHTTPProviding(routerName string) bool { |
| 245 | rp, ok := c.Routing.Routers[routerName] |
| 246 | if !ok { |
| 247 | // Router configured for providing doesn't exist |
| 248 | return false |
| 249 | } |
| 250 | |
| 251 | switch rp.Type { |
| 252 | case RouterTypeHTTP: |
| 253 | return true |
| 254 | case RouterTypeParallel, RouterTypeSequential: |
| 255 | // Check if any child router supports HTTP |
| 256 | if children, ok := rp.Parameters.(*ComposableRouterParams); ok { |
| 257 | for _, childRouter := range children.Routers { |
| 258 | if c.routerSupportsHTTPProviding(childRouter.RouterName) { |
| 259 | return true |
| 260 | } |
| 261 | } |
| 262 | } |
| 263 | } |
| 264 | return false |
| 265 | } |