From a0bc0df32a8cc28c86e30f022fe9aac12549d04d Mon Sep 17 00:00:00 2001 From: maze Date: Wed, 24 Jul 2024 15:38:56 +0200 Subject: [PATCH] Initial import --- .gitignore | 2 + cmd/update-ip/main.go | 51 ++++++++++++++++++++++++ config.go | 93 +++++++++++++++++++++++++++++++++++++++++++ fqdn.go | 39 ++++++++++++++++++ fqdn_test.go | 64 +++++++++++++++++++++++++++++ go.mod | 27 +++++++++++++ go.sum | 51 ++++++++++++++++++++++++ label.go | 66 ++++++++++++++++++++++++++++++ label_test.go | 91 ++++++++++++++++++++++++++++++++++++++++++ provider.go | 39 ++++++++++++++++++ provider_transip.go | 91 ++++++++++++++++++++++++++++++++++++++++++ resolve.go | 33 +++++++++++++++ resolve_test.go | 13 ++++++ update-my-ip.hcl | 76 +++++++++++++++++++++++++++++++++++ 14 files changed, 736 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/update-ip/main.go create mode 100644 config.go create mode 100644 fqdn.go create mode 100644 fqdn_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 label.go create mode 100644 label_test.go create mode 100644 provider.go create mode 100644 provider_transip.go create mode 100644 resolve.go create mode 100644 resolve_test.go create mode 100644 update-my-ip.hcl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dab5090 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Don't check in private key files +*.key \ No newline at end of file diff --git a/cmd/update-ip/main.go b/cmd/update-ip/main.go new file mode 100644 index 0000000..0995604 --- /dev/null +++ b/cmd/update-ip/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "flag" + + updateip "git.maze.io/wijnand/update-ip" + log "github.com/sirupsen/logrus" +) + +func main() { + configFile := flag.String("config", "", "configuration file") + debugFlag := flag.Bool("debug", false, "enable debug messages") + traceFlag := flag.Bool("trace", false, "enable trace messages") + flag.Parse() + + if *traceFlag { + log.SetLevel(log.TraceLevel) + } else if *debugFlag { + log.SetLevel(log.DebugLevel) + } + + c, err := updateip.Load(*configFile) + if err != nil { + log.WithError(err).Fatal("failed to configure") + } + + ip, err := c.Resolver.CurrentIP() + if err != nil { + log.WithError(err).Fatal("failed to resolve current IP") + } + log.WithField("ip", ip).Info("resolved current IP") + + if !c.Resolver.IsAllowed(ip) { + log.WithField("ip", ip).Fatal("not allowed by ACL") + } + + for _, domain := range c.Domain { + log := log.WithField("domain", domain.Name) + log.Info("checking domain") + if len(domain.Update) == 0 { + log.Warn("no records to update!") + continue + } + + for _, record := range domain.Update { + if err := domain.Provider.UpdateIP(domain.Name, record, ip); err != nil { + log.WithError(err).Error("update failed!") + } + } + } +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..29a42a3 --- /dev/null +++ b/config.go @@ -0,0 +1,93 @@ +package updateip + +import ( + "fmt" + "net" + + "github.com/hashicorp/hcl/v2/hclsimple" + log "github.com/sirupsen/logrus" +) + +func Load(name string) (*Config, error) { + var ( + config Config + err error + ) + + log.WithField("config", name).Debug("loading configuration file") + if err = hclsimple.DecodeFile(name, nil, &config); err != nil { + return nil, err + } + + providers := make(map[string]Provider) + for _, v := range config.Provider { + if providers[v.Name], err = NewProvider(v); err != nil { + return nil, fmt.Errorf("error configuring provider %q: %w", v.Name, err) + } + } + + for _, v := range config.Domain { + if p, ok := providers[v.ProviderName]; ok { + v.Provider = p + } else { + return nil, fmt.Errorf("provider %q in domain %q is not configured", v.ProviderName, v.Name) + } + + if len(v.Update) == 0 { + v.Update = append(v.Update, "@") + } + } + + return &config, nil +} + +type Config struct { + Resolver ResolverConfig `hcl:"resolver,block"` + Domain []*DomainConfig `hcl:"domain,block"` + Provider []*ProviderConfig `hcl:"provider,block"` +} + +type ResolverConfig struct { + Permit []string `hcl:"permit"` +} + +func (r ResolverConfig) CurrentIP() (net.IP, error) { + return NewResolver().CurrentIP() +} + +func (r ResolverConfig) IsAllowed(ip net.IP) bool { + if len(r.Permit) == 0 { + // Fail open if no ACL is configured. + log.WithField("ip", ip.String()).Debug("IP allowed by resolver; no ACL") + return true + } + + for _, v := range r.Permit { + if _, ipnet, err := net.ParseCIDR(v); err == nil { + if ipnet.Contains(ip) { + log.WithFields(log.Fields{ + "ip": ip.String(), + "range": ipnet.String(), + }).Debug("IP allowed by resolver ACL") + return true + } + } + } + + log.WithField("ip", ip.String()).Warn("IP not allowed by resolver ACL") + return false +} + +type DomainConfig struct { + Name string `hcl:"name,label"` + ProviderName string `hcl:"provider"` + Provider Provider `` + Update []string `hcl:"update,optional"` +} + +type ProviderConfig struct { + Name string `hcl:"name,label"` + Type string + Username string `hcl:"username"` + PrivateKey string `hcl:"private_key"` +} diff --git a/fqdn.go b/fqdn.go new file mode 100644 index 0000000..1194043 --- /dev/null +++ b/fqdn.go @@ -0,0 +1,39 @@ +package updateip + +import "strings" + +// Canonicalize a DNS record. +func Canonicalize(domainName, recordName string) string { + if recordName == "@" { + return ToFqdn(domainName) + } else if strings.HasSuffix(recordName, ".@") { + return ToFqdn(strings.TrimSuffix(recordName, "@") + domainName) + } + return ToFqdn(recordName) +} + +// IsFqdn checks if the label is a full-qualified name. +func IsFqdn(name string) bool { + return strings.HasSuffix(name, ".") +} + +// ToFqdn converts the name into a fqdn appending a trailing dot. +func ToFqdn(name string) string { + if name == "@" { + return name + } + n := len(name) + if n == 0 || name[n-1] == '.' { + return name + } + return name + "." +} + +// UnFqdn converts the fqdn into a name removing the trailing dot. +func UnFqdn(name string) string { + n := len(name) + if n != 0 && name[n-1] == '.' { + return name[:n-1] + } + return name +} diff --git a/fqdn_test.go b/fqdn_test.go new file mode 100644 index 0000000..86f2483 --- /dev/null +++ b/fqdn_test.go @@ -0,0 +1,64 @@ +package updateip + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToFqdn(t *testing.T) { + testCases := []struct { + desc string + domain string + expected string + }{ + { + desc: "simple", + domain: "foo.example.com", + expected: "foo.example.com.", + }, + { + desc: "already FQDN", + domain: "foo.example.com.", + expected: "foo.example.com.", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + fqdn := ToFqdn(test.domain) + assert.Equal(t, test.expected, fqdn) + }) + } +} + +func TestUnFqdn(t *testing.T) { + testCases := []struct { + desc string + fqdn string + expected string + }{ + { + desc: "simple", + fqdn: "foo.example.", + expected: "foo.example", + }, + { + desc: "already domain", + fqdn: "foo.example", + expected: "foo.example", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + domain := UnFqdn(test.fqdn) + + assert.Equal(t, test.expected, domain) + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bc09ce1 --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module git.maze.io/wijnand/update-ip + +go 1.22.3 + +require ( + github.com/hashicorp/hcl/v2 v2.21.0 + github.com/sirupsen/logrus v1.9.3 + github.com/transip/gotransip/v6 v6.25.0 +) + +require ( + github.com/agext/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/transip/gotransip v5.8.2+incompatible // indirect + github.com/zclconf/go-cty v1.13.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.11.0 // indirect + golang.org/x/tools v0.6.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..03c4c16 --- /dev/null +++ b/go.sum @@ -0,0 +1,51 @@ +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= +github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/transip/gotransip v5.8.2+incompatible h1:aNJhw/w/3QBqFcHAIPz1ytoK5FexeMzbUCGrrhWr3H0= +github.com/transip/gotransip v5.8.2+incompatible/go.mod h1:uacMoJVmrfOcscM4Bi5NVg708b7c6rz2oDTWqa7i2Ic= +github.com/transip/gotransip/v6 v6.25.0 h1:/H+SjMq/9HNZ0/maE1OLhJpxLaCGHsxq0PWaMPJHxK4= +github.com/transip/gotransip/v6 v6.25.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= +github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= +github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/label.go b/label.go new file mode 100644 index 0000000..d185417 --- /dev/null +++ b/label.go @@ -0,0 +1,66 @@ +package updateip + +import ( + "strings" + "unicode/utf8" +) + +// CompressLabel returns the smallest possible subdomain label for the provided record against the domain. +func CompressLabel(domainName, recordName string) string { + domainName = UnFqdn(domainName) + recordName = UnFqdn(recordName) + if strings.EqualFold(domainName, recordName) { + return "@" + } else if i := indexFold(recordName, "."+domainName); i > -1 { + return recordName[:i] + } + return recordName +} + +// ExapandLabel gives the full DNS label for the (abbreviated) record. +func ExapandLabel(domainName, recordName string) string { + if strings.EqualFold(domainName, recordName) { + return recordName + } else if IsFqdn(recordName) { + return recordName + } else if recordName == "@" { + return domainName + } else if i := strings.LastIndex(recordName, ".@"); i > -1 { + return recordName[:i] + "." + domainName + } + return recordName + "." + domainName +} + +// indexFold returns the index of the first instance of substr in s (case insensitive), or -1 if substr is not present in s. +func indexFold(s, substr string) int { + ns := len(s) + nb := len(substr) + if ns < nb { + return -1 + } + if nb == 0 { + return 0 + } + if ns == nb { + if strings.EqualFold(s, substr) { + return 0 + } + return -1 + } + + l := ns - nb + for i := 0; i <= l; { + src := s[i : i+nb] + if strings.EqualFold(src, substr) { + return i + } + _, z := utf8.DecodeRuneInString(src) + i += z + } + return -1 +} + +// hasSuffixFold Tests if the string s ends with the specified suffix (case insensitive). +func hasSuffixFold(s, suffix string) bool { + return len(s) >= len(suffix) && strings.EqualFold(s[len(s)-len(suffix):], suffix) +} diff --git a/label_test.go b/label_test.go new file mode 100644 index 0000000..d788c07 --- /dev/null +++ b/label_test.go @@ -0,0 +1,91 @@ +package updateip + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCompressLabel(t *testing.T) { + testCases := []struct { + desc string + domain, record string + expected string + }{ + { + "same", + "example.org", + "example.org", + "@", + }, + { + "same_fqdn", + "example.org.", + "example.org", + "@", + }, + { + "simple", + "example.org", + "www.example.org", + "www", + }, + { + "unrelated", + "example.org", + "example.net", + "example.net", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(it *testing.T) { + it.Parallel() + + result := CompressLabel(test.domain, test.record) + assert.Equal(it, test.expected, result) + }) + } +} + +func TestExpandLabel(t *testing.T) { + testCases := []struct { + desc string + domain, record string + expected string + }{ + { + "same", + "example.org", + "@", + "example.org", + }, + { + "simple", + "example.org", + "www", + "www.example.org", + }, + { + "unrelated", + "example.org", + "example.net", + "example.net.example.org", + }, + { + "using_at", + "example.org", + "www.@", + "www.example.org", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(it *testing.T) { + it.Parallel() + + result := ExapandLabel(test.domain, test.record) + assert.Equal(it, test.expected, result) + }) + } +} diff --git a/provider.go b/provider.go new file mode 100644 index 0000000..b3e48e3 --- /dev/null +++ b/provider.go @@ -0,0 +1,39 @@ +package updateip + +import ( + "errors" + "fmt" + "net" + "strings" + "time" + + log "github.com/sirupsen/logrus" +) + +const DefaultTTL = 5 * time.Minute + +type Provider interface { + UpdateIP(domain, name string, ip net.IP) error +} + +func NewProvider(config *ProviderConfig) (Provider, error) { + if config == nil { + return nil, errors.New("provider: config is nil") + } + + if config.Type == "" { + config.Type = config.Name + } + + log.WithFields(log.Fields{ + "name": config.Name, + "type": config.Type, + }).Debug("configuring provider") + + switch strings.ToLower(config.Type) { + case "transip": + return NewTransIP(config) + default: + return nil, fmt.Errorf("provider %s: unsupported type %q", config.Name, config.Type) + } +} diff --git a/provider_transip.go b/provider_transip.go new file mode 100644 index 0000000..df35d85 --- /dev/null +++ b/provider_transip.go @@ -0,0 +1,91 @@ +package updateip + +import ( + "net" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/transip/gotransip/v6" + transipdomain "github.com/transip/gotransip/v6/domain" +) + +type transipProvider struct { + repo transipdomain.Repository +} + +func NewTransIP(config *ProviderConfig) (Provider, error) { + client, err := gotransip.NewClient(gotransip.ClientConfiguration{ + AccountName: config.Username, + PrivateKeyPath: config.PrivateKey, + }) + if err != nil { + return nil, err + } + + return &transipProvider{ + repo: transipdomain.Repository{Client: client}, + }, nil +} + +func (p *transipProvider) UpdateIP(domain, name string, ip net.IP) error { + domainName := UnFqdn(domain) + entries, err := p.repo.GetDNSEntries(domainName) + if err != nil { + return err + } + + entryName := CompressLabel(domainName, UnFqdn(name)) + for _, entry := range entries { + log := log.WithFields(log.Fields{ + "domain": domainName, + "name": entry.Name, + "type": entry.Type, + "ttl": entry.Expire, + "content": entry.Content, + }) + log.Trace("checking DNS record") + + if strings.EqualFold(ExapandLabel(domainName, entryName), ExapandLabel(domainName, entry.Name)) { + switch { + case IsLabel(entry.Type): + if entry.Content != ip.String() { + log.WithField("content", ip.String()).Info("updating DNS record content") + return p.repo.UpdateDNSEntry(domainName, entry) + } + log.Debug("not updating DNS record; already up-to-date!") + return nil + + case IsCanonicalName(entry.Type): + log.Info("removing DNS record") + if err = p.repo.RemoveDNSEntry(domainName, entry); err != nil { + return err + } + } + } + } + + // No entry found, create one + entry := transipdomain.DNSEntry{ + Name: entryName, + Type: "A", + Content: ip.String(), + Expire: int(DefaultTTL.Seconds()), + } + log.WithFields(log.Fields{ + "name": entry.Name, + "type": "A", + "new": ip.String(), + "expire": entry.Expire, + }).Info("creating DNS record") + return p.repo.AddDNSEntry(domainName, entry) +} + +// IsLabel check if the DNS record type is a DNS label (A-record). +func IsLabel(recordType string) bool { + return strings.EqualFold(recordType, "A") +} + +// IsCanonicalName checks if the DNS record type is a DNS canonical name (CNAME record). +func IsCanonicalName(recordType string) bool { + return strings.EqualFold(recordType, "CNAME") +} diff --git a/resolve.go b/resolve.go new file mode 100644 index 0000000..9dce53f --- /dev/null +++ b/resolve.go @@ -0,0 +1,33 @@ +package updateip + +import ( + "io" + "net" + "net/http" +) + +type Resolver interface { + CurrentIP() (net.IP, error) +} + +func NewResolver() Resolver { + return new(Ipify) +} + +type Ipify struct{} + +func (Ipify) CurrentIP() (net.IP, error) { + r, err := http.Get("https://api.ipify.org") + if err != nil { + return nil, err + } + + defer func() { _ = r.Body.Close() }() + + b, err := io.ReadAll(r.Body) + if err != nil { + return nil, err + } + + return net.ParseIP(string(b)), nil +} diff --git a/resolve_test.go b/resolve_test.go new file mode 100644 index 0000000..762ea37 --- /dev/null +++ b/resolve_test.go @@ -0,0 +1,13 @@ +package updateip + +import "testing" + +func TestResolveCurrentIP(t *testing.T) { + r := new(Ipify) + + v, err := r.CurrentIP() + if err != nil { + t.Error(err) + } + t.Log("current IP:", v) +} diff --git a/update-my-ip.hcl b/update-my-ip.hcl new file mode 100644 index 0000000..59495e1 --- /dev/null +++ b/update-my-ip.hcl @@ -0,0 +1,76 @@ +resolver { + permit = [ + # AS50266 - Odido Netherlands B.V. + "143.177.0.0/16", # Pool for fixed WBA users 65,536 + "143.178.0.0/17", # Pool for fixed WBA users 32,768 + "143.178.128.0/17", # Pool for fixed WBA users 32,768 + "143.178.232.0/24", # Pool for fixed WBA users 256 + "143.179.0.0/16", # Tele2 Nederland B.V. 65,536 + "185.180.148.0/22", # Odido Netherlands B.V. 1,024 + "185.35.112.0/22", # Odido Netherlands B.V. 1,024 + "188.88.0.0/14", # Odido Netherlands B.V. 262,144 + "188.88.0.0/16", # Pool for mobile internet users 65,536 + "188.89.0.0/16", # Pool for mobile internet users 65,536 + "188.90.0.0/16", # Pool for mobile internet users 65,536 + "188.91.0.0/16", # Pool for mobile internet users 65,536 + "195.191.16.0/23", # Odido Netherlands B.V. 512 + "31.184.64.0/18", # Odido Netherlands 16,384 + "31.187.128.0/17", # Pool for fixed WBA, ODF and VULA users 32,768 + "31.20.0.0/16", # Pool for fixed WBA users 65,536 + "31.201.0.0/16", # Pool for fixed WBA, ODF and VULA users 65,536 + "31.21.0.0/16", # Pool for fixed WBA users 65,536 + "37.143.80.0/21", # Odido Netherlands B.V. 2,048 + "5.132.0.0/17", # Odido Netherlands B.V. 32,768 + "62.166.128.0/17", # Pool for fixed WBA users 32,768 + "62.250.0.0/17", # Odido Netherlands 32,768 + "62.250.128.0/17", # Pool for fixed WBA users 32,768 + "81.59.0.0/17", # Zon internet is one of the largest free ISP in the Netherlands 32,768 + "82.172.0.0/17", # Tele2 Consumer is one of the largest ISP\'s in the Netherlands 32,768 + "82.174.0.0/16", # Pool for fixed WBA users 65,536 + "85.144.0.0/15", # Odido Netherlands B.V. 131,072 + "85.146.0.0/17", # Odido Netherlands B.V. 32,768 + "85.146.128.0/18", # Odido Netherlands B.V. 16,384 + "85.223.0.0/17", # Odido Netherlands 32,768 + "87.208.0.0/16", # Pool for fixed WBA users 65,536 + "87.209.0.0/16", # Pool for fixed WBA users 65,536 + "87.209.180.0/24", # Pool for fixed WBA users 256 + "87.210.0.0/16", # Pool for fixed WBA users 65,536 + "87.212.0.0/16", # Pool for fixed WBA users 65,536 + "92.254.0.0/17", # Odido Netherlands 32,768 + "94.157.0.0/16", # Pool for fixed WBA, ODF and VULA users 65,536 + "95.98.0.0/15", # Odido Netherlands B.V. 131,072 + "95.98.0.0/16", # Pool for mobile internet users 65,536 + "95.99.0.0/16", # Pool for mobile internet users 65,536 + ] +} + +#domain "maze.io" { +# provider = "transip" +# update = ["maze.io", "www.maze.io"] +#} + +domain "duzzdus.nl" { + provider = "transip" +} + +domain "modderman-lenstra.nl" { + provider = "transip" +} + +domain "maze.casa" { + provider = "transip" +} + +domain "maze.network" { + provider = "transip" +} + +domain "maze.io" { + provider = "transip" + update = ["@", "lab.maze.io"] +} + +provider "transip" { + username = "tehmaze" + private_key = "testdata/transip.key" +} \ No newline at end of file