Merge pull request #8 from mkelcik/new-1_1_1_1_resolver

Add 1.1.1.1 resolver
This commit is contained in:
mkelcik
2023-05-03 23:17:20 +02:00
committed by GitHub
9 changed files with 160 additions and 13 deletions

2
Makefile Normal file
View File

@@ -0,0 +1,2 @@
test:
go test --cover -covermode count -v ./...

View File

@@ -17,7 +17,7 @@ Before run, you need configure this environment variables.
- `CLOUDFLARE_ZONE` - (required) zone name with domain you want to check. See: [https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones](https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones)
- `ON_CHANGE_COMMENT` - (optional) in the event that the ip address of the dns record changes, this comment will be added to the record
- `CHECK_INTERVAL_SECONDS` - (optional) how often will the ip address of the records be checked (default: `300`)
- `PUBLIC_IP_RESOLVER` - (optional) public ip address resolver. (default: `ifconfig.me`) Available: `ifconfig.me`, `v4.ident.me`
- `PUBLIC_IP_RESOLVER` - (optional) public ip address resolver. (default: `ifconfig.me`) Available: `ifconfig.me`, `v4.ident.me`, `1.1.1.1`
### Building from source

View File

@@ -20,6 +20,8 @@ type PublicIpResolver interface {
func getResolver(resolverName string) (PublicIpResolver, string) {
switch resolverName {
// HERE add another resolver if needed
case public_resolvers.CloudflareTraceTag:
return public_resolvers.NewDefaultCloudflareTrace(), public_resolvers.CloudflareTraceTag
case public_resolvers.V4IdentMeTag:
return public_resolvers.NewV4IdentMeDefault(), public_resolvers.V4IdentMeTag
case public_resolvers.IfConfigMeTag:
@@ -57,7 +59,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
log.Printf("Current public ip `%s` (%s)", currentPublicIP, resolverTag)
log.Printf("Current public ip `%s` (resolver: %s)", currentPublicIP, resolverTag)
dns, err := allDNSRecords(ctx, api, cloudflare.ZoneIdentifier(zoneID))
if err != nil {

View File

@@ -2,19 +2,30 @@ package public_resolvers
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
)
var NoIPInResponseError = errors.New("no ip found in response")
type Doer interface {
Do(*http.Request) (*http.Response, error)
}
type ipParserFunc func(reader io.Reader) (string, error)
func defaultIpParser(reader io.Reader) (string, error) {
out, err := io.ReadAll(reader)
return string(out), err
}
type baseResolver struct {
client Doer
url string
client Doer
url string
ipParser ipParserFunc
}
func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
@@ -35,10 +46,10 @@ func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
}
ipText, err := io.ReadAll(resp.Body)
ipText, err := i.ipParser(resp.Body)
if err != nil {
return net.IP{}, fmt.Errorf("error reading body: %w", err)
}
return net.ParseIP(string(ipText)), nil
return net.ParseIP(ipText), nil
}

View File

@@ -21,7 +21,7 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
func NewTestClient(fn RoundTripFunc) *http.Client {
return &http.Client{
Transport: RoundTripFunc(fn),
Transport: fn,
}
}
@@ -54,6 +54,7 @@ func Test_baseResolver_ResolvePublicIp(t *testing.T) {
type fields struct {
client Doer
url string
fn ipParserFunc
}
type args struct {
ctx context.Context
@@ -70,6 +71,7 @@ func Test_baseResolver_ResolvePublicIp(t *testing.T) {
fields: fields{
client: client,
url: testUrl,
fn: defaultIpParser,
},
args: args{
ctx: context.Background(),
@@ -81,8 +83,9 @@ func Test_baseResolver_ResolvePublicIp(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := baseResolver{
client: tt.fields.client,
url: tt.fields.url,
client: tt.fields.client,
url: tt.fields.url,
ipParser: tt.fields.fn,
}
got, err := i.ResolvePublicIp(tt.args.ctx)
if (err != nil) != tt.wantErr {

View File

@@ -0,0 +1,49 @@
package public_resolvers
import (
"io"
"net/http"
"strings"
"time"
)
const (
CloudflareTraceTag = "1.1.1.1"
CloudflareTraceUrl = "https://1.1.1.1/cdn-cgi/trace"
ipPrefix = "ip="
)
type CloudflareTrace struct {
baseResolver
}
func NewDefaultCloudflareTrace() *CloudflareTrace {
return NewCloudflareTrace(&http.Client{
Timeout: 10 * time.Second,
})
}
func cloudflareTraceResponseParser(reader io.Reader) (string, error) {
data, err := io.ReadAll(reader)
if err != nil {
return "", err
}
for _, row := range strings.Split(string(data), "\n") {
if strings.Index(row, ipPrefix) == 0 {
return strings.TrimSpace(strings.ReplaceAll(row, ipPrefix, "")), nil
}
}
return "", NoIPInResponseError
}
func NewCloudflareTrace(client Doer) *CloudflareTrace {
return &CloudflareTrace{
baseResolver: baseResolver{
client: client,
url: CloudflareTraceUrl,
ipParser: cloudflareTraceResponseParser,
},
}
}

View File

@@ -0,0 +1,78 @@
package public_resolvers
import (
"bytes"
"io"
"testing"
)
func Test_cloudflareTraceResponseParser(t *testing.T) {
type args struct {
reader io.Reader
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "ok",
args: args{
reader: bytes.NewBuffer([]byte(`fl=31f118
h=1.1.1.1
ip=94.113.142.206
ts=1683145336.383
visit_scheme=https
uag=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
colo=PRG
sliver=none
http=http/2
loc=CZ
tls=TLSv1.3
sni=off
warp=off
gateway=off
rbi=off
kex=X25519`)),
},
want: "94.113.142.206",
wantErr: false,
},
{
name: "no ip in response",
args: args{
reader: bytes.NewBuffer([]byte(`fl=31f118
h=1.1.1.1
ts=1683145336.383
visit_scheme=https
uag=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
colo=PRG
sliver=none
http=http/2
loc=CZ
tls=TLSv1.3
sni=off
warp=off
gateway=off
rbi=off
kex=X25519`)),
},
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := cloudflareTraceResponseParser(tt.args.reader)
if (err != nil) != tt.wantErr {
t.Errorf("cloudflareTraceResponseParser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("cloudflareTraceResponseParser() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -23,8 +23,9 @@ func NewDefaultIfConfigMe() *IfConfigMe {
func NewIfConfigMe(client Doer) *IfConfigMe {
return &IfConfigMe{
baseResolver: baseResolver{
client: client,
url: ifConfigMeUrl,
client: client,
url: ifConfigMeUrl,
ipParser: defaultIpParser,
},
}
}

View File

@@ -23,8 +23,9 @@ func NewV4IdentMeDefault() *V4IdentMe {
func NewV4IdentMe(client Doer) *V4IdentMe {
return &V4IdentMe{
baseResolver: baseResolver{
client: client,
url: v4IdentMeUrl,
client: client,
url: v4IdentMeUrl,
ipParser: defaultIpParser,
},
}
}