Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jhotmann authored and qdm12 committed Jun 15, 2024
1 parent a1a9fc0 commit 1378d95
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ Check the documentation for your DNS provider:
- [Ionos](docs/ionos.md)
- [Linode](docs/linode.md)
- [LuaDNS](docs/luadns.md)
- [Mikrotik](docs/mikrotik.md)
- [Name.com](docs/name.com.md)
- [Namecheap](docs/namecheap.md)
- [Netcup](docs/netcup.md)
Expand Down
33 changes: 33 additions & 0 deletions docs/mikrotik.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Mikrotik

## Configuration

### Example

```json
{
"settings": [
{
"provider": "mikrotik",
"router_ip": "192.168.0.1",
"address_list": "AddressListName",
"username": "user",
"password": "secret",
"ip_version": "ipv4"
}
]
}
```

### Parameters

- `"router_ip"` is the IP address of your router
- `"address_list"` is the name of the address list
- `"username"` is the username to authenticate with
- `"password"` is the user's password

## Domain setup

- Create a user with read, write, and api access
- Optionally create an entry in `/ip firewall address-list` to assign your public IP, an entry will be created for you otherwise
- You can then use this address list in your hairpin NAT firewall rules
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-routeros/routeros v0.0.0-20210123142807-2a44d57c6730 // indirect

Check failure on line 27 in go.mod

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./go.mod:27:15: "routeros" is a misspelling of "routers"

Check failure on line 27 in go.mod

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./go.mod:27:24: "routeros" is a misspelling of "routers"
github.com/golang/protobuf v1.5.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-routeros/routeros v0.0.0-20210123142807-2a44d57c6730 h1:EuqwWLv/LPPjhvFqkeD2bz+FOlvw2DjvDI7vK8GVeyY=

Check failure on line 18 in go.sum

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./go.sum:18:14: "routeros" is a misspelling of "routers"
github.com/go-routeros/routeros v0.0.0-20210123142807-2a44d57c6730/go.mod h1:em1mEqFKnoeQuQP9Sg7i26yaW8o05WwcNj7yLhrXxSQ=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
Expand Down
1 change: 1 addition & 0 deletions internal/provider/constants/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
Ionos models.Provider = "ionos"
Linode models.Provider = "linode"
LuaDNS models.Provider = "luadns"
Mikrotik models.Provider = "mikrotik"
Namecheap models.Provider = "namecheap"
NameCom models.Provider = "name.com"
Netcup models.Provider = "netcup"
Expand Down
3 changes: 3 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/qdm12/ddns-updater/internal/provider/providers/ionos"
"github.com/qdm12/ddns-updater/internal/provider/providers/linode"
"github.com/qdm12/ddns-updater/internal/provider/providers/luadns"
"github.com/qdm12/ddns-updater/internal/provider/providers/mikrotik"
"github.com/qdm12/ddns-updater/internal/provider/providers/namecheap"
"github.com/qdm12/ddns-updater/internal/provider/providers/namecom"
"github.com/qdm12/ddns-updater/internal/provider/providers/netcup"
Expand Down Expand Up @@ -139,6 +140,8 @@ func New(providerName models.Provider, data json.RawMessage, domain, host string
return linode.New(data, domain, host, ipVersion, ipv6Suffix)
case constants.LuaDNS:
return luadns.New(data, domain, host, ipVersion, ipv6Suffix)
case constants.Mikrotik:
return mikrotik.New(data, ipVersion, ipv6Suffix)
case constants.Namecheap:
return namecheap.New(data, domain, host)
case constants.NameCom:
Expand Down
34 changes: 34 additions & 0 deletions internal/provider/providers/mikrotik/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package mikrotik

import (
"github.com/go-routeros/routeros" //nolint:misspell

Check failure on line 4 in internal/provider/providers/mikrotik/api.go

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./internal/provider/providers/mikrotik/api.go:4:16: "routeros" is a misspelling of "routers"

Check failure on line 4 in internal/provider/providers/mikrotik/api.go

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./internal/provider/providers/mikrotik/api.go:4:25: "routeros" is a misspelling of "routers"
)

type addressListItem struct {
id string
list string
address string
}

func getAddressListItems(client *routeros.Client,

Check failure on line 13 in internal/provider/providers/mikrotik/api.go

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./internal/provider/providers/mikrotik/api.go:13:33: "routeros" is a misspelling of "routers"
addressList string) (items []addressListItem, err error) {
reply, err := client.Run("/ip/firewall/address-list/print",
"?disabled=false", "?list="+addressList)
if err != nil {
return nil, err
}

items = make([]addressListItem, 0, len(reply.Re))
for _, re := range reply.Re {
item := addressListItem{
id: re.Map[".id"],
list: re.Map["list"],
address: re.Map["address"],
}
if item.id == "" || item.address == "" {
continue
}
items = append(items, item)
}
return items, nil
}
143 changes: 143 additions & 0 deletions internal/provider/providers/mikrotik/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package mikrotik

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/netip"
"regexp"

"github.com/go-routeros/routeros" //nolint:misspell

Check failure on line 11 in internal/provider/providers/mikrotik/provider.go

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./internal/provider/providers/mikrotik/provider.go:11:16: "routeros" is a misspelling of "routers"

Check failure on line 11 in internal/provider/providers/mikrotik/provider.go

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./internal/provider/providers/mikrotik/provider.go:11:25: "routeros" is a misspelling of "routers"
"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/provider/constants"
"github.com/qdm12/ddns-updater/internal/provider/errors"
"github.com/qdm12/ddns-updater/internal/provider/utils"
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
)

type Provider struct {
ipVersion ipversion.IPVersion
ipv6Suffix netip.Prefix
routerIP netip.Addr
username string
password string
addressList string
}

type settings struct {
RouterIP netip.Addr `json:"router_ip"`
Username string `json:"username"`
Password string `json:"password"`
AddressList string `json:"address_list"`
}

func New(data json.RawMessage, ipVersion ipversion.IPVersion,
ipv6Suffix netip.Prefix) (p *Provider, err error) {
var providerSpecificSettings settings
err = json.Unmarshal(data, &providerSpecificSettings)
if err != nil {
return nil, fmt.Errorf("json decoding provider specific settings: %w", err)
}
err = validateSettings(providerSpecificSettings)
if err != nil {
return nil, fmt.Errorf("validating settings: %w", err)
}

return &Provider{
ipVersion: ipVersion,
ipv6Suffix: ipv6Suffix,
routerIP: providerSpecificSettings.RouterIP,
username: providerSpecificSettings.Username,
password: providerSpecificSettings.Password,
addressList: providerSpecificSettings.AddressList,
}, nil
}

var addressListRegex = regexp.MustCompile(`^[a-zA-Z]{2,}$`)

func validateSettings(settings settings) error {
switch {
case !addressListRegex.MatchString(settings.AddressList):
return fmt.Errorf("%w: host %q does not match regex %q",
errors.ErrKeyNotValid, settings.AddressList, addressListRegex)
case !settings.RouterIP.IsValid():
return fmt.Errorf("%w: router_ip cannot be empty", errors.ErrKeyNotSet)
}
return nil
}

func (p *Provider) String() string {
return utils.ToString(p.Domain(), p.addressList, constants.Mikrotik, p.ipVersion)
}

func (p *Provider) Domain() string {
return "N / A"
}

func (p *Provider) Host() string {
return "N / A"
}

func (p *Provider) IPVersion() ipversion.IPVersion {
return p.ipVersion
}

func (p *Provider) IPv6Suffix() netip.Prefix {
return p.ipv6Suffix
}

func (p *Provider) Proxied() bool {
return false
}

func (p *Provider) BuildDomainName() string {
return ""
}

func (p *Provider) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: p.Domain(),
Host: p.addressList,
Provider: fmt.Sprintf("<a href=\"http://%s\">Mikrotik</a>", p.routerIP),
IPVersion: p.ipVersion.String(),
}
}

func (p *Provider) Update(_ context.Context, _ *http.Client, ip netip.Addr) (
newIP netip.Addr, err error) {
client, err := routeros.Dial(p.routerIP.String()+":8728", p.username, p.password)

Check failure on line 109 in internal/provider/providers/mikrotik/provider.go

View workflow job for this annotation

GitHub Actions / verify

[misspell] reported by reviewdog 🐶 "routeros" is a misspelling of "routers" Raw Output: ./internal/provider/providers/mikrotik/provider.go:109:16: "routeros" is a misspelling of "routers"
if err != nil {
return netip.Addr{}, fmt.Errorf("authenticating with router: %w", err)
}
defer client.Close()

addressListItems, err := getAddressListItems(client, p.addressList)
if err != nil {
return netip.Addr{}, fmt.Errorf("getting address list items: %w", err)
}

if len(addressListItems) == 0 {
_, err = client.Run("/ip/firewall/address-list/add",
"=list="+p.addressList, "=address="+ip.String())
if err != nil {
return netip.Addr{}, fmt.Errorf("adding address list %q: %w",
p.addressList, err)
}
return ip, nil
}

for _, addressListItem := range addressListItems {
if addressListItem.address == ip.String() {
continue // already up to date
}
_, err = client.Run("/ip/firewall/address-list/set",
"=.id="+addressListItem.id, "=address="+ip.String())
if err != nil {
return netip.Addr{}, fmt.Errorf("setting address in address list id %q: %w",
addressListItem.id, err)
}
}

return ip, nil
}

0 comments on commit 1378d95

Please sign in to comment.