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
This commit is contained in:
Quentin McGaw
2024-10-19 15:21:14 +02:00
committed by GitHub
parent 3dfb43e117
commit a61302f135
22 changed files with 649 additions and 90 deletions

View File

@@ -1,23 +1,12 @@
package api
import (
"context"
"errors"
"fmt"
"net/http"
"net/netip"
"strings"
"github.com/qdm12/gluetun/internal/models"
)
type API interface {
String() string
CanFetchAnyIP() bool
FetchInfo(ctx context.Context, ip netip.Addr) (
result models.PublicIP, err error)
}
type Provider string
const (
@@ -27,21 +16,34 @@ const (
IP2Location Provider = "ip2location"
)
func New(provider Provider, client *http.Client, token string) ( //nolint:ireturn
a API, err error,
type NameToken struct {
Name string
Token string
}
func New(nameTokenPairs []NameToken, client *http.Client) (
fetchers []Fetcher, err error,
) {
switch provider {
case Cloudflare:
return newCloudflare(client), nil
case IfConfigCo:
return newIfConfigCo(client), nil
case IPInfo:
return newIPInfo(client, token), nil
case IP2Location:
return newIP2Location(client, token), nil
default:
panic("provider not valid: " + provider)
fetchers = make([]Fetcher, len(nameTokenPairs))
for i, nameTokenPair := range nameTokenPairs {
provider, err := ParseProvider(nameTokenPair.Name)
if err != nil {
return nil, fmt.Errorf("parsing API name: %w", err)
}
switch provider {
case Cloudflare:
fetchers[i] = newCloudflare(client)
case IfConfigCo:
fetchers[i] = newIfConfigCo(client)
case IPInfo:
fetchers[i] = newIPInfo(client, nameTokenPair.Token)
case IP2Location:
fetchers[i] = newIP2Location(client, nameTokenPair.Token)
default:
panic("provider not valid: " + provider)
}
}
return fetchers, nil
}
var ErrProviderNotValid = errors.New("API name is not valid")