Files
gluetun/internal/publicip/api/multi.go
Quentin McGaw a61302f135 feat(publicip): resilient public ip fetcher (#2518)
- `PUBLICIP_API` accepts a comma separated list of ip data sources, where the first one is the base default one, and sources after it are backup sources used if we are rate limited.
- `PUBLICIP_API` defaults to `ipinfo,ifconfigco,ip2location,cloudflare` such that it now has `ifconfigco,ip2location,cloudflare` as backup ip data sources.
- `PUBLICIP_API_TOKEN` accepts a comma separated list of ip data source tokens, each corresponding by position to the APIs listed in `PUBLICIP_API`.
- logs ip data source when logging public ip information
- assume a rate limiting error is for 30 days (no persistence)
- ready for future live settings updates
  - consider an ip data source no longer banned if the token changes
  - keeps track of ban times when updating the list of fetchers
2024-10-19 15:21:14 +02:00

58 lines
1.3 KiB
Go

package api
import (
"context"
"net/netip"
"github.com/qdm12/gluetun/internal/models"
)
// FetchMultiInfo obtains the public IP address information for every IP
// addresses provided and returns a slice of results with the corresponding
// order as to the IP addresses slice order.
// If an error is encountered, all the operations are canceled and
// an error is returned, so the results returned should be considered
// incomplete in this case.
func FetchMultiInfo(ctx context.Context, fetcher InfoFetcher, ips []netip.Addr) (
results []models.PublicIP, err error,
) {
ctx, cancel := context.WithCancel(ctx)
type asyncResult struct {
index int
result models.PublicIP
err error
}
resultsCh := make(chan asyncResult)
for i, ip := range ips {
go func(index int, ip netip.Addr) {
aResult := asyncResult{
index: index,
}
aResult.result, aResult.err = fetcher.FetchInfo(ctx, ip)
resultsCh <- aResult
}(i, ip)
}
results = make([]models.PublicIP, len(ips))
for range len(ips) {
aResult := <-resultsCh
if aResult.err != nil {
if err == nil {
// Cancel on the first error encountered
err = aResult.err
cancel()
}
continue // ignore errors after the first one
}
results[aResult.index] = aResult.result
}
close(resultsCh)
cancel()
return results, err
}