master
go 319 lines 10.1 KB
Raw
1 package config
2
3 import (
4 "maps"
5 "math/rand/v2"
6 "strings"
7
8 "github.com/ipfs/boxo/autoconf"
9 logging "github.com/ipfs/go-log/v2"
10 peer "github.com/libp2p/go-libp2p/core/peer"
11 )
12
13 var log = logging.Logger("config")
14
15 // AutoConf contains the configuration for the autoconf subsystem
16 type AutoConf struct {
17 // URL is the HTTP(S) URL to fetch the autoconf.json from
18 // Default: see boxo/autoconf.MainnetAutoConfURL
19 URL *OptionalString `json:",omitempty"`
20
21 // Enabled determines whether to use autoconf
22 // Default: true
23 Enabled Flag `json:",omitempty"`
24
25 // RefreshInterval is how often to refresh autoconf data
26 // Default: 24h
27 RefreshInterval *OptionalDuration `json:",omitempty"`
28
29 // TLSInsecureSkipVerify allows skipping TLS verification (for testing only)
30 // Default: false
31 TLSInsecureSkipVerify Flag `json:",omitempty"`
32 }
33
34 const (
35 // AutoPlaceholder is the string used as a placeholder for autoconf values
36 AutoPlaceholder = "auto"
37
38 // DefaultAutoConfEnabled is the default value for AutoConf.Enabled
39 DefaultAutoConfEnabled = true
40
41 // DefaultAutoConfURL is the default URL for fetching autoconf
42 DefaultAutoConfURL = autoconf.MainnetAutoConfURL
43
44 // DefaultAutoConfRefreshInterval is the default interval for refreshing autoconf data
45 DefaultAutoConfRefreshInterval = autoconf.DefaultRefreshInterval
46
47 // AutoConf client configuration constants
48 DefaultAutoConfCacheSize = autoconf.DefaultCacheSize
49 DefaultAutoConfTimeout = autoconf.DefaultTimeout
50 )
51
52 // getNativeSystems returns the list of systems that should be used natively based on routing type
53 func getNativeSystems(routingType string) []string {
54 switch routingType {
55 case "dht", "dhtclient", "dhtserver":
56 return []string{autoconf.SystemAminoDHT} // Only native DHT
57 case "auto", "autoclient":
58 return []string{autoconf.SystemAminoDHT} // Native DHT, delegated others
59 case "delegated":
60 return []string{} // Everything delegated
61 case "none":
62 return []string{} // No native systems
63 default:
64 return []string{} // Custom mode
65 }
66 }
67
68 // selectRandomResolver picks a random resolver from a list for load balancing
69 func selectRandomResolver(resolvers []string) string {
70 if len(resolvers) == 0 {
71 return ""
72 }
73 return resolvers[rand.IntN(len(resolvers))]
74 }
75
76 // DNSResolversWithAutoConf returns DNS resolvers with "auto" values replaced by autoconf values
77 func (c *Config) DNSResolversWithAutoConf() map[string]string {
78 if c.DNS.Resolvers == nil {
79 return nil
80 }
81
82 resolved := make(map[string]string)
83 autoConf := c.getAutoConf()
84 autoExpanded := 0
85
86 // Process each configured resolver
87 for domain, resolver := range c.DNS.Resolvers {
88 if resolver == AutoPlaceholder {
89 // Try to resolve from autoconf
90 if autoConf != nil && autoConf.DNSResolvers != nil {
91 if resolvers, exists := autoConf.DNSResolvers[domain]; exists && len(resolvers) > 0 {
92 resolved[domain] = selectRandomResolver(resolvers)
93 autoExpanded++
94 }
95 }
96 // If autoConf is disabled or domain not found, skip this "auto" resolver
97 } else {
98 // Keep custom resolver as-is
99 resolved[domain] = resolver
100 }
101 }
102
103 // Add default resolvers from autoconf that aren't already configured
104 if autoConf != nil && autoConf.DNSResolvers != nil {
105 for domain, resolvers := range autoConf.DNSResolvers {
106 if _, exists := resolved[domain]; !exists && len(resolvers) > 0 {
107 resolved[domain] = selectRandomResolver(resolvers)
108 }
109 }
110 }
111
112 // Log expansion statistics
113 if autoExpanded > 0 {
114 log.Debugf("expanded %d 'auto' DNS.Resolvers from autoconf", autoExpanded)
115 }
116
117 return resolved
118 }
119
120 // expandAutoConfSlice is a generic helper for expanding "auto" placeholders in string slices
121 // It handles the common pattern of: iterate through slice, expand "auto" once, keep custom values
122 func expandAutoConfSlice(sourceSlice []string, autoConfData []string) []string {
123 var resolved []string
124 autoExpanded := false
125
126 for _, item := range sourceSlice {
127 if item == AutoPlaceholder {
128 // Replace with autoconf data (only once)
129 if autoConfData != nil && !autoExpanded {
130 resolved = append(resolved, autoConfData...)
131 autoExpanded = true
132 }
133 // If autoConfData is nil or already expanded, skip redundant "auto" entries silently
134 } else {
135 // Keep custom item
136 resolved = append(resolved, item)
137 }
138 }
139
140 return resolved
141 }
142
143 // BootstrapWithAutoConf returns bootstrap config with "auto" values replaced by autoconf values
144 func (c *Config) BootstrapWithAutoConf() []string {
145 autoConf := c.getAutoConf()
146 var autoConfData []string
147
148 if autoConf != nil {
149 routingType := c.Routing.Type.WithDefault(DefaultRoutingType)
150 nativeSystems := getNativeSystems(routingType)
151 autoConfData = autoConf.GetBootstrapPeers(nativeSystems...)
152 log.Debugf("BootstrapWithAutoConf: processing with routing type: %s", routingType)
153 } else {
154 log.Debugf("BootstrapWithAutoConf: autoConf disabled, using original config")
155 }
156
157 result := expandAutoConfSlice(c.Bootstrap, autoConfData)
158 log.Debugf("BootstrapWithAutoConf: final result contains %d peers", len(result))
159 return result
160 }
161
162 // getAutoConf is a helper to get autoconf data with fallbacks
163 func (c *Config) getAutoConf() *autoconf.Config {
164 if !c.AutoConf.Enabled.WithDefault(DefaultAutoConfEnabled) {
165 log.Debugf("getAutoConf: AutoConf disabled, returning nil")
166 return nil
167 }
168
169 // Create or get cached client with config
170 client, err := GetAutoConfClient(c)
171 if err != nil {
172 log.Debugf("getAutoConf: client creation failed - %v", err)
173 return nil
174 }
175
176 // Use GetCached to avoid network I/O during config operations
177 // This ensures config retrieval doesn't block on network operations
178 result := client.GetCached()
179
180 log.Debugf("getAutoConf: returning autoconf data")
181 return result
182 }
183
184 // BootstrapPeersWithAutoConf returns bootstrap peers with "auto" values replaced by autoconf values
185 // and parsed into peer.AddrInfo structures
186 func (c *Config) BootstrapPeersWithAutoConf() ([]peer.AddrInfo, error) {
187 bootstrapStrings := c.BootstrapWithAutoConf()
188 return ParseBootstrapPeers(bootstrapStrings)
189 }
190
191 // DelegatedRoutersWithAutoConf returns delegated router URLs without trailing slashes
192 func (c *Config) DelegatedRoutersWithAutoConf() []string {
193 autoConf := c.getAutoConf()
194
195 // Use autoconf to expand the endpoints with supported paths for read operations
196 routingType := c.Routing.Type.WithDefault(DefaultRoutingType)
197 nativeSystems := getNativeSystems(routingType)
198 return autoconf.ExpandDelegatedEndpoints(
199 c.Routing.DelegatedRouters,
200 autoConf,
201 nativeSystems,
202 // Kubo supports all read paths
203 autoconf.RoutingV1ProvidersPath,
204 autoconf.RoutingV1PeersPath,
205 autoconf.RoutingV1IPNSPath,
206 )
207 }
208
209 // DelegatedPublishersWithAutoConf returns delegated publisher URLs without trailing slashes
210 func (c *Config) DelegatedPublishersWithAutoConf() []string {
211 autoConf := c.getAutoConf()
212
213 // Use autoconf to expand the endpoints with IPNS write path
214 routingType := c.Routing.Type.WithDefault(DefaultRoutingType)
215 nativeSystems := getNativeSystems(routingType)
216 return autoconf.ExpandDelegatedEndpoints(
217 c.Ipns.DelegatedPublishers,
218 autoConf,
219 nativeSystems,
220 autoconf.RoutingV1IPNSPath, // Only IPNS operations (for write)
221 )
222 }
223
224 // expandConfigField expands a specific config field with autoconf values
225 // Handles both top-level fields ("Bootstrap") and nested fields ("DNS.Resolvers")
226 func (c *Config) expandConfigField(expandedCfg map[string]any, fieldPath string) {
227 // Check if this field supports autoconf expansion
228 expandFunc, supported := supportedAutoConfFields[fieldPath]
229 if !supported {
230 return
231 }
232
233 // Handle top-level fields (no dot in path)
234 if !strings.Contains(fieldPath, ".") {
235 if _, exists := expandedCfg[fieldPath]; exists {
236 expandedCfg[fieldPath] = expandFunc(c)
237 }
238 return
239 }
240
241 // Handle nested fields (section.field format)
242 parts := strings.SplitN(fieldPath, ".", 2)
243 if len(parts) != 2 {
244 return
245 }
246
247 sectionName, fieldName := parts[0], parts[1]
248 if section, exists := expandedCfg[sectionName]; exists {
249 if sectionMap, ok := section.(map[string]any); ok {
250 if _, exists := sectionMap[fieldName]; exists {
251 sectionMap[fieldName] = expandFunc(c)
252 expandedCfg[sectionName] = sectionMap
253 }
254 }
255 }
256 }
257
258 // ExpandAutoConfValues expands "auto" placeholders in config with their actual values using the same methods as the daemon
259 func (c *Config) ExpandAutoConfValues(cfg map[string]any) (map[string]any, error) {
260 // Create a deep copy of the config map to avoid modifying the original
261 expandedCfg := maps.Clone(cfg)
262
263 // Use the same expansion methods that the daemon uses - ensures runtime consistency
264 // Unified expansion for all supported autoconf fields
265 c.expandConfigField(expandedCfg, "Bootstrap")
266 c.expandConfigField(expandedCfg, "DNS.Resolvers")
267 c.expandConfigField(expandedCfg, "Routing.DelegatedRouters")
268 c.expandConfigField(expandedCfg, "Ipns.DelegatedPublishers")
269
270 return expandedCfg, nil
271 }
272
273 // supportedAutoConfFields maps field keys to their expansion functions
274 var supportedAutoConfFields = map[string]func(*Config) any{
275 "Bootstrap": func(c *Config) any {
276 expanded := c.BootstrapWithAutoConf()
277 return stringSliceToInterfaceSlice(expanded)
278 },
279 "DNS.Resolvers": func(c *Config) any {
280 expanded := c.DNSResolversWithAutoConf()
281 return stringMapToInterfaceMap(expanded)
282 },
283 "Routing.DelegatedRouters": func(c *Config) any {
284 expanded := c.DelegatedRoutersWithAutoConf()
285 return stringSliceToInterfaceSlice(expanded)
286 },
287 "Ipns.DelegatedPublishers": func(c *Config) any {
288 expanded := c.DelegatedPublishersWithAutoConf()
289 return stringSliceToInterfaceSlice(expanded)
290 },
291 }
292
293 // ExpandConfigField expands auto values for a specific config field using the same methods as the daemon
294 func (c *Config) ExpandConfigField(key string, value any) any {
295 if expandFunc, supported := supportedAutoConfFields[key]; supported {
296 return expandFunc(c)
297 }
298
299 // Return original value if no expansion needed (not a field that supports auto values)
300 return value
301 }
302
303 // Helper functions for type conversion between string types and any types for JSON compatibility
304
305 func stringSliceToInterfaceSlice(slice []string) []any {
306 result := make([]any, len(slice))
307 for i, v := range slice {
308 result[i] = v
309 }
310 return result
311 }
312
313 func stringMapToInterfaceMap(m map[string]string) map[string]any {
314 result := make(map[string]any)
315 for k, v := range m {
316 result[k] = v
317 }
318 return result
319 }