You've already forked ddns-updater
Refactor: Moved all under internal.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
FEAT: Directadmin provider is now working
This commit is contained in:
55
internal/public_resolvers/base_resolver.go
Normal file
55
internal/public_resolvers/base_resolver.go
Normal file
@@ -0,0 +1,55 @@
|
||||
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
|
||||
ipParser ipParserFunc
|
||||
}
|
||||
|
||||
func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, i.url, nil)
|
||||
if err != nil {
|
||||
return net.IP{}, fmt.Errorf("error creating ifconfig request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := i.client.Do(req)
|
||||
if err != nil {
|
||||
return net.IP{}, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
ipText, err := i.ipParser(resp.Body)
|
||||
if err != nil {
|
||||
return net.IP{}, fmt.Errorf("error reading body: %w", err)
|
||||
}
|
||||
|
||||
return net.ParseIP(ipText), nil
|
||||
}
|
||||
100
internal/public_resolvers/base_resolver_test.go
Normal file
100
internal/public_resolvers/base_resolver_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package public_resolvers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// RoundTripFunc .
|
||||
type RoundTripFunc func(req *http.Request) *http.Response
|
||||
|
||||
// RoundTrip .
|
||||
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return f(req), nil
|
||||
}
|
||||
|
||||
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
|
||||
func NewTestClient(fn RoundTripFunc) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: fn,
|
||||
}
|
||||
}
|
||||
|
||||
func Test_baseResolver_ResolvePublicIp(t *testing.T) {
|
||||
|
||||
testUrl := "http://my-test-url.url"
|
||||
testIp := `192.168.0.100`
|
||||
|
||||
client := NewTestClient(func(req *http.Request) *http.Response {
|
||||
|
||||
if req.URL.String() != testUrl {
|
||||
return &http.Response{
|
||||
StatusCode: 500,
|
||||
// Send response to be tested
|
||||
Body: io.NopCloser(bytes.NewBufferString(`invalid url`)),
|
||||
// Must be set to non-nil value or it panics
|
||||
Header: make(http.Header),
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
// Send response to be tested
|
||||
Body: io.NopCloser(bytes.NewBufferString(testIp)),
|
||||
// Must be set to non-nil value or it panics
|
||||
Header: make(http.Header),
|
||||
}
|
||||
})
|
||||
|
||||
type fields struct {
|
||||
client Doer
|
||||
url string
|
||||
fn ipParserFunc
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want net.IP
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "check parse ip4",
|
||||
fields: fields{
|
||||
client: client,
|
||||
url: testUrl,
|
||||
fn: defaultIpParser,
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
},
|
||||
want: net.ParseIP(testIp),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
i := baseResolver{
|
||||
client: tt.fields.client,
|
||||
url: tt.fields.url,
|
||||
ipParser: tt.fields.fn,
|
||||
}
|
||||
got, err := i.ResolvePublicIp(tt.args.ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ResolvePublicIp() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ResolvePublicIp() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
49
internal/public_resolvers/cloudflare_trace.go
Normal file
49
internal/public_resolvers/cloudflare_trace.go
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
78
internal/public_resolvers/cloudflare_trace_test.go
Normal file
78
internal/public_resolvers/cloudflare_trace_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
31
internal/public_resolvers/icanhazip.go
Normal file
31
internal/public_resolvers/icanhazip.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package public_resolvers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
IcanhazipTag = "icanhazip"
|
||||
IcanhazipUrl = "https://v4.icanhazip.com/"
|
||||
)
|
||||
|
||||
type Icanhazip struct {
|
||||
baseResolver
|
||||
}
|
||||
|
||||
func NewIcanhazipDefault() *Icanhazip {
|
||||
return NewIcanhazip(&http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
func NewIcanhazip(client Doer) *Icanhazip {
|
||||
return &Icanhazip{
|
||||
baseResolver: baseResolver{
|
||||
client: client,
|
||||
url: v4IdentMeUrl,
|
||||
ipParser: defaultIpParser,
|
||||
},
|
||||
}
|
||||
}
|
||||
31
internal/public_resolvers/ifconfigme.go
Normal file
31
internal/public_resolvers/ifconfigme.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package public_resolvers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
IfConfigMeTag = "ifconfig.me"
|
||||
ifConfigMeUrl = "https://ifconfig.me"
|
||||
)
|
||||
|
||||
type IfConfigMe struct {
|
||||
baseResolver
|
||||
}
|
||||
|
||||
func NewDefaultIfConfigMe() *IfConfigMe {
|
||||
return NewIfConfigMe(&http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
func NewIfConfigMe(client Doer) *IfConfigMe {
|
||||
return &IfConfigMe{
|
||||
baseResolver: baseResolver{
|
||||
client: client,
|
||||
url: ifConfigMeUrl,
|
||||
ipParser: defaultIpParser,
|
||||
},
|
||||
}
|
||||
}
|
||||
31
internal/public_resolvers/v4identme.go
Normal file
31
internal/public_resolvers/v4identme.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package public_resolvers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
V4IdentMeTag = "v4.ident.me"
|
||||
v4IdentMeUrl = "https://v4.ident.me/"
|
||||
)
|
||||
|
||||
type V4IdentMe struct {
|
||||
baseResolver
|
||||
}
|
||||
|
||||
func NewV4IdentMeDefault() *V4IdentMe {
|
||||
return NewV4IdentMe(&http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
func NewV4IdentMe(client Doer) *V4IdentMe {
|
||||
return &V4IdentMe{
|
||||
baseResolver: baseResolver{
|
||||
client: client,
|
||||
url: v4IdentMeUrl,
|
||||
ipParser: defaultIpParser,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user