Feature: HTTP and DNS Public IP fetching options, fixes #136 (#187)

This commit is contained in:
Quentin McGaw
2021-03-22 17:49:58 -04:00
committed by GitHub
parent 106bcae966
commit 8b2e83a69e
5 changed files with 146 additions and 50 deletions

View File

@@ -79,9 +79,11 @@ ENV \
CONFIG= \
PERIOD=5m \
UPDATE_COOLDOWN_PERIOD=5m \
IP_METHOD=all \
IPV4_METHOD=all \
IPV6_METHOD=all \
PUBLICIP_FETCHERS=all \
PUBLICIP_HTTP_PROVIDERS=all \
PUBLICIPV4_HTTP_PROVIDERS=all \
PUBLICIPV6_HTTP_PROVIDERS=all \
PUBLICIP_DNS_PROVIDERS=all \
HTTP_TIMEOUT=10s \
# Web UI

View File

@@ -161,10 +161,12 @@ Note that:
| Environment variable | Default | Description |
| --- | --- | --- |
| `CONFIG` | | One line JSON object containing the entire config (takes precendence over config.json file) if specified |
| `PERIOD` | `5m` | Default period of IP address check, following [this format](https://golang.org/pkg/time/#ParseDuration) |
| `IP_METHOD` | `all` | Comma separated methods to obtain the public IP address (ipv4 or ipv6). See the [IP Methods section](#IP-methods) |
| `IPV4_METHOD` | `all` | Comma separated methods to obtain the public IPv4 address only. See the [IP Methods section](#IP-methods) |
| `IPV6_METHOD` | `all` | Comma separated methods to obtain the public IPv6 address only. See the [IP Methods section](#IP-methods) |
| `PERIOD` | `5m` | Default period of IP address check, following [this format](https://golang.org/pkg/time/#ParseDuration) |\
| `PUBLICIP_FETCHERS` | `all` | Comma separated fetcher types to obtain the public IP address from `http` and `dns` |
| `PUBLICIP_HTTP_PROVIDERS` | `all` | Comma separated providers to obtain the public IP address (ipv4 or ipv6). See the [Public IP section](#Public-IP) |
| `PUBLICIPV4_HTTP_PROVIDERS` | `all` | Comma separated providers to obtain the public IPv4 address only. See the [Public IP section](#Public-IP) |
| `PUBLICIPV6_HTTP_PROVIDERS` | `all` | Comma separated providers to obtain the public IPv6 address only. See the [Public IP section](#Public-IP) |
| `PUBLICIP_DNS_PROVIDERS` | `all` | Comma separated providers to obtain the public IP address (IPv4 and/or IPv6). See the [Public IP section](#Public-IP) |
| `UPDATE_COOLDOWN_PERIOD` | `5m` | Duration to cooldown between updates for each record. This is useful to avoid being rate limited or banned. |
| `HTTP_TIMEOUT` | `10s` | Timeout for all HTTP requests |
| `LISTENING_PORT` | `8000` | Internal TCP listening port for the web UI |
@@ -177,25 +179,34 @@ Note that:
| `GOTIFY_TOKEN` | | (optional) Token to access your Gotify server |
| `TZ` | | Timezone to have accurate times, i.e. `America/Montreal` |
#### IP methods
#### Public IP
By default, all ip methods are specified. The program will cycle between each. This allows you not to be blocked for making too many requests. You can otherwise pick one or more of the following, for each ip version:
By default, all public IP fetching types are used and cycled (over DNS and over HTTPs).
- IPv4 or IPv6 (for most cases)
On top of that, for each fetching method, all echo services available are cycled on each request.
This allows you not to be blocked for making too many requests.
You can otherwise customize it with the following:
- `PUBLICIP_HTTP_PROVIDERS` gets your public IPv4 or IPv6 address. It can be one or more of the following:
- `opendns` using [https://diagnostic.opendns.com/myip](https://diagnostic.opendns.com/myip)
- `ifconfig` using [https://ifconfig.io/ip](https://ifconfig.io/ip)
- `ipinfo` using [https://ipinfo.io/ip](https://ipinfo.io/ip)
- `ipify` using [https://api.ipify.org](https://api.ipify.org)
- `ddnss` using [https://ddnss.de/meineip.php](https://ddnss.de/meineip.php)
- `google` using [https://domains.google.com/checkip](https://domains.google.com/checkip)
- IPv4 only (useful for updating both ipv4 and ipv6)
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `PUBLICIPV4_HTTP_PROVIDERS` gets your public IPv4 address only. It can be one or more of the following:
- `ipify` using [https://api.ipify.org](https://api.ipify.org)
- `noip` using [http://ip1.dynupdate.no-ip.com](http://ip1.dynupdate.no-ip.com)
- IPv6 only
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `PUBLICIPV6_HTTP_PROVIDERS` gets your public IPv6 address only. It can be one or more of the following:
- `ipify` using [https://api6.ipify.org](https://api6.ipify.org)
- `noip` using [http://ip1.dynupdate6.no-ip.com](http://ip1.dynupdate6.no-ip.com)
You can also specify one or more HTTPS URL to obtain your public IP address (i.e. `-e IPV6_METHOD=https://ipinfo.io/ip`).
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `PUBLICIP_DNS_PROVIDERS` gets your public IPv4 address only or IPv6 address only or one of them (see #136). It can be one or more of the following:
- `google`
- `cloudflare`
### Host firewall

View File

@@ -22,6 +22,8 @@ import (
"github.com/qdm12/ddns-updater/internal/server"
"github.com/qdm12/ddns-updater/internal/splash"
"github.com/qdm12/ddns-updater/internal/update"
"github.com/qdm12/ddns-updater/pkg/publicip"
"github.com/qdm12/ddns-updater/pkg/publicip/dns"
pubiphttp "github.com/qdm12/ddns-updater/pkg/publicip/http"
"github.com/qdm12/golibs/admin"
"github.com/qdm12/golibs/logging"
@@ -45,7 +47,8 @@ func main() {
type allParams struct {
period time.Duration
cooldown time.Duration
httpIPOptions []pubiphttp.Option
httpSettings publicip.HTTPSettings
dnsSettings publicip.DNSSettings
dir string
dataDir string
listeningPort uint16
@@ -147,7 +150,9 @@ func _main(ctx context.Context, timeNow func() time.Time) int {
wg := &sync.WaitGroup{}
defer wg.Wait()
ipGetter, err := pubiphttp.New(client, p.httpIPOptions...)
p.httpSettings.Client = client
ipGetter, err := publicip.NewFetcher(p.dnsSettings, p.httpSettings)
if err != nil {
logger.Error(err)
return 1
@@ -235,24 +240,37 @@ func getParams(paramsReader params.Reader, logger logging.Logger) (p allParams,
return p, err
}
httpIPProviders, err := paramsReader.IPMethod()
p.httpSettings.Enabled, p.dnsSettings.Enabled, err = paramsReader.PublicIPFetchers()
if err != nil {
return p, err
}
httpIP4Providers, err := paramsReader.IPv4Method()
httpIPProviders, err := paramsReader.PublicIPHTTPProviders()
if err != nil {
return p, err
}
httpIP6Providers, err := paramsReader.IPv6Method()
httpIP4Providers, err := paramsReader.PublicIPv4HTTPProviders()
if err != nil {
return p, err
}
p.httpIPOptions = []pubiphttp.Option{
httpIP6Providers, err := paramsReader.PublicIPv6HTTPProviders()
if err != nil {
return p, err
}
p.httpSettings.Options = []pubiphttp.Option{
pubiphttp.SetProvidersIP(httpIPProviders[0], httpIPProviders[1:]...),
pubiphttp.SetProvidersIP4(httpIP4Providers[0], httpIP4Providers[1:]...),
pubiphttp.SetProvidersIP6(httpIP6Providers[0], httpIP6Providers[1:]...),
}
dnsIPProviders, err := paramsReader.PublicIPDNSProviders()
if err != nil {
return p, err
}
p.dnsSettings.Options = []dns.Option{
dns.SetProviders(dnsIPProviders[0], dnsIPProviders[1:]...),
}
p.dir, err = paramsReader.ExeDir()
if err != nil {
return p, err

View File

@@ -12,9 +12,11 @@ services:
- CONFIG=
- PERIOD=5m
- UPDATE_COOLDOWN_PERIOD=5m
- IP_METHOD=all
- IPV4_METHOD=all
- IPV6_METHOD=all
- PUBLICIP_FETCHERS=all
- PUBLICIP_HTTP_PROVIDERS=all
- PUBLICIPV4_HTTP_PROVIDERS=all
- PUBLICIPV6_HTTP_PROVIDERS=all
- PUBLICIP_DNS_PROVIDERS=all
- HTTP_TIMEOUT=10s
# Web UI

View File

@@ -9,21 +9,28 @@ import (
"time"
"github.com/qdm12/ddns-updater/internal/settings"
"github.com/qdm12/ddns-updater/pkg/publicip/dns"
"github.com/qdm12/ddns-updater/pkg/publicip/http"
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/params"
)
const (
all = "all"
)
type Reader interface {
// JSON
JSONSettings(filePath string) (allSettings []settings.Settings, warnings []string, err error)
// Core
Period() (period time.Duration, warnings []string, err error)
IPMethod() (providers []http.Provider, err error)
IPv4Method() (providers []http.Provider, err error)
IPv6Method() (providers []http.Provider, err error)
PublicIPFetchers() (http, dns bool, err error)
PublicIPHTTPProviders() (providers []http.Provider, err error)
PublicIPv4HTTPProviders() (providers []http.Provider, err error)
PublicIPv6HTTPProviders() (providers []http.Provider, err error)
PublicIPDNSProviders() (providers []dns.Provider, err error)
HTTPTimeout() (duration time.Duration, err error)
CooldownPeriod() (duration time.Duration, err error)
@@ -49,6 +56,7 @@ type reader struct {
env params.Env
os params.OS
readFile func(filename string) ([]byte, error)
retroFn func(oldKey, newKey string)
}
func NewReader(logger logging.Logger) Reader {
@@ -56,6 +64,9 @@ func NewReader(logger logging.Logger) Reader {
env: params.NewEnv(),
os: params.NewOS(),
readFile: ioutil.ReadFile,
retroFn: func(oldKey, newKey string) {
logger.Warn("You are using the old environment variable %s, please consider switching to %s instead", oldKey, newKey)
},
}
}
@@ -119,29 +130,81 @@ func (r *reader) Period() (period time.Duration, warnings []string, err error) {
return period, nil, err
}
var ErrInvalidFetcher = errors.New("invalid fetcher specified")
func (r *reader) PublicIPFetchers() (http, dns bool, err error) {
s, err := r.env.Get("PUBLICIP_FETCHERS", params.Default(all))
if err != nil {
return false, false, err
}
fields := strings.Split(s, ",")
for i, field := range fields {
switch strings.ToLower(field) {
case all:
return true, true, nil
case "http":
http = true
case "dns":
dns = true
default:
return false, false, fmt.Errorf(
"%w: %q at position %d of %d",
ErrInvalidFetcher, field, i+1, len(fields))
}
}
return http, dns, nil
}
// PublicIPHTTPProviders obtains the HTTP providers to obtain your public IPv4 and/or IPv6 address.
func (r *reader) PublicIPDNSProviders() (providers []dns.Provider, err error) {
s, err := r.env.Get("PUBLICIP_DNS_PROVIDERS", params.Default(all))
if err != nil {
return nil, err
}
availableProviders := dns.ListProviders()
fields := strings.Split(s, ",")
providers = make([]dns.Provider, len(fields))
for i, field := range fields {
if field == all {
return availableProviders, nil
}
providers[i] = dns.Provider(field)
if err := dns.ValidateProvider(providers[i]); err != nil {
return nil, err
}
}
return providers, nil
}
// PublicIPHTTPProviders obtains the HTTP providers to obtain your public IPv4 or IPv6 address.
func (r *reader) PublicIPHTTPProviders() (providers []http.Provider, err error) {
return r.httpIPMethod("PUBLICIP_HTTP_PROVIDERS", "IP_METHOD", ipversion.IP4or6)
}
// PublicIPv4HTTPProviders obtains the HTTP providers to obtain your public IPv4 address.
func (r *reader) PublicIPv4HTTPProviders() (providers []http.Provider, err error) {
return r.httpIPMethod("PUBLICIPV4_HTTP_PROVIDERS", "IPV4_METHOD", ipversion.IP4)
}
// PublicIPv6HTTPProviders obtains the HTTP providers to obtain your public IPv6 address.
func (r *reader) PublicIPv6HTTPProviders() (providers []http.Provider, err error) {
return r.httpIPMethod("PUBLICIPV6_HTTP_PROVIDERS", "IPV6_METHOD", ipversion.IP6)
}
var (
ErrIPMethodInvalid = errors.New("ip method is not valid")
ErrIPMethodVersion = errors.New("ip method not valid for IP version")
ErrInvalidPublicIPHTTPProvider = errors.New("invalid public IP HTTP provider")
)
// IPMethod obtains the HTTP method for IP v4 or v6 to obtain your public IP address.
func (r *reader) IPMethod() (providers []http.Provider, err error) {
return r.httpIPMethod("IP_METHOD", ipversion.IP4or6)
}
// IPMethod obtains the HTTP method for IP v4 to obtain your public IP address.
func (r *reader) IPv4Method() (providers []http.Provider, err error) {
return r.httpIPMethod("IPV4_METHOD", ipversion.IP4)
}
// IPMethod obtains the HTTP method for IP v6 to obtain your public IP address.
func (r *reader) IPv6Method() (providers []http.Provider, err error) {
return r.httpIPMethod("IPV6_METHOD", ipversion.IP6)
}
func (r *reader) httpIPMethod(envKey string, version ipversion.IPVersion) (
func (r *reader) httpIPMethod(envKey, retroKey string, version ipversion.IPVersion) (
providers []http.Provider, err error) {
s, err := r.env.Get(envKey, params.Default("cycle"))
retroKeyOption := params.RetroKeys([]string{retroKey}, r.retroFn)
s, err := r.env.Get(envKey, params.Default("cycle"), retroKeyOption)
if err != nil {
return nil, err
}
@@ -162,10 +225,10 @@ func (r *reader) httpIPMethod(envKey string, version ipversion.IPVersion) (
case "noip4", "noip6", "noip8245_4", "noip8245_6":
field = "noip"
case "cycle":
field = "all"
field = all
}
if field == "all" {
if field == all {
return availableProviders, nil
}
@@ -178,13 +241,13 @@ func (r *reader) httpIPMethod(envKey string, version ipversion.IPVersion) (
provider := http.Provider(field)
if _, ok := choices[provider]; !ok {
return nil, fmt.Errorf("%w: %s", ErrIPMethodInvalid, provider)
return nil, fmt.Errorf("%w: %s", ErrInvalidPublicIPHTTPProvider, provider)
}
providers = append(providers, provider)
}
if len(providers) == 0 {
return nil, fmt.Errorf("%w: %s", ErrIPMethodVersion, version)
return nil, fmt.Errorf("%w: for IP version %s", ErrInvalidPublicIPHTTPProvider, version)
}
return providers, nil