1
0
mirror of https://github.com/taigrr/godns synced 2025-01-18 04:03:25 -08:00

Compare commits

...

96 Commits

Author SHA1 Message Date
Timothy
5ad83edfe8
Merge pull request #99 from iskrisis/patch-1
Update README.md
2021-03-01 10:17:21 +08:00
iskrisis
069fd1dcb5
Update README.md 2021-02-28 18:09:15 +01:00
Timothy
fa03a1b959
Merge pull request #98 from jlsalvador/patch-1
Clarifies the status of DDNS for root domains
2021-03-01 00:53:19 +08:00
José Luis Salvador Rufo
17c9b6fa17
Clarifies the status of DDNS for root domains
Proposed by https://github.com/TimothyYe/godns/issues/76
2021-02-28 17:28:33 +01:00
Timothy
6dfbb60dfd
Merge pull request #96 from shaworth/patch-1
Location of config in container has changed
2021-02-24 10:37:46 +08:00
Timothy
07e5d7f99e
Update README.md 2021-02-24 10:30:28 +08:00
Shannon Haworth
10c67fa23a
Location of config in container has changed
Changed the documentation so that the binary will find the configuration file.
2021-02-23 02:57:15 -05:00
Timothy
afc6ba8241
update Makefile 2021-02-20 11:47:48 +08:00
Timothy
7ac3eed730
Merge branch 'master' of github.com:TimothyYe/godns 2021-02-20 11:40:59 +08:00
Timothy
ac7dc021eb
update Dockerfile 2021-02-20 11:40:35 +08:00
Timothy Ye
2154fcc762 update Makefile 2021-02-05 17:52:38 +00:00
Timothy
99af25e496
Merge pull request #94 from jlsalvador/patch-1
Update domain for Cloudflare
2021-01-31 00:37:43 +08:00
Timothy
dd4805df92
Update go.yml 2021-01-30 23:49:07 +08:00
José Luis Salvador Rufo
cdf32c509d
Update domain for Cloudflare
Fix #33
2021-01-29 14:24:06 +01:00
Timothy
fd4c30eec7
Merge pull request #84 from yoannchaudet/master
Revamp the README file a bit
2020-12-29 18:40:52 +08:00
Yoann Chaudet
212ce47ea7 Revamp the README file a bit
- Add a table of contents
- Re-organize sections a bit
- Reword minor things
2020-12-28 22:17:32 -08:00
Timothy
cefae90569
Merge pull request #83 from GiantTreeLP/fix/cloudflare-api-token-permissions
Fix API token permission issue with CloudFlare
2020-11-22 10:51:22 +08:00
Marvin
c720f7bca1
Fix API token permission issue with CloudFlare
When querying CloudFlare for all zones via the API using a restricted API token, CloudFlare responds, that that particular token needs the "com.cloudflare.api.account.zone.list" permission, in order to successfully retrieve all zones.
Adding the parameter "name" with the value of the given domain works around that limitation, as then the API token only needs access to that particular domain.
2020-11-06 20:35:40 +01:00
Timothy
7a60d3415f
fix: DNSPod handler issue for checking IPv4/IPv6 2020-10-01 18:28:31 +08:00
Timothy
e33cd6e1cb
Merge pull request #81 from 6543-forks/fix-license-badge
Fix License Badge
2020-10-01 14:25:08 +08:00
6543
f62c20a145
Fix License Badge 2020-10-01 06:50:38 +02:00
Timothy
ad6e17dc06
Merge pull request #80 from tinysnake/master
Let docker can change to correct timezone
2020-09-25 22:50:52 +08:00
Feifan Tang
f807baa3ce
Merge pull request #1 from tinysnake/timezone-patch
Let docker can change to correct timezone
2020-09-25 16:50:53 +08:00
Feifan Tang
2d546702ff
Let docker can change to correct timezone 2020-09-25 16:32:52 +08:00
Timothy
f6a27491f3
Update README.md 2020-08-12 00:38:55 +08:00
Timothy
3ba95289e9
Update README.md 2020-08-12 00:10:32 +08:00
Timothy
d7755ab15e
Merge branch 'master' of github.com:TimothyYe/godns into master 2020-08-11 00:07:14 +08:00
Timothy
e070900b3b
refactor: fix code lint warnings 2020-08-11 00:07:05 +08:00
Timothy
21590d4c64
Merge pull request #78 from TimothyYe/noip
feat: add No-IP provider
2020-08-10 23:50:18 +08:00
Timothy
a7c2b0a56e
feat: add No-IP provider 2020-08-10 23:49:12 +08:00
Timothy
1ddd1dfb8b
refactor: fix code lint warnings 2020-08-10 14:39:56 +08:00
Timothy
e94d99e25c
docs: update README for IPv6 support 2020-08-10 13:21:25 +08:00
Timothy
f70f3bc0a3
Merge pull request #77 from wi1dcard/bugfix/alidns-subdomain-contains-another-subdomain-string
Fix update incorrect subdomain record of AliDNS.
2020-07-30 17:57:48 +08:00
Timothy
3eaed255d9
Merge pull request #75 from x-liao/master
Fix: lastip changes when update request fails
2020-07-30 17:54:01 +08:00
wi1dcard
633e1187d0 Fix update incorrect subdomain record of AliDNS. 2020-07-30 12:14:45 +08:00
xliao
0f670c66f9 Fix: the results returned may be limited 2020-07-28 20:26:30 +08:00
xliao
0f9ea20f10 Fix: lastip changes when update request fails 2020-07-28 16:35:03 +08:00
Timothy
81ecf1d096
fix lint error 2020-07-22 23:55:03 +08:00
Timothy.Ye
fa1432be99
refactor: update error messages with lowercase 2020-07-02 21:55:12 +08:00
Timothy
045cb692a5
Create CODE_OF_CONDUCT.md 2020-07-02 20:58:21 +08:00
Timothy
2021a2404c
Merge pull request #73 from kenthua/add-slack
add slack support for notification
2020-07-02 17:06:25 +08:00
Kent Hua
eec9e9a881 add slack support for notification 2020-07-02 06:14:57 +00:00
Timothy
b1e94fda52
update resolver name 2020-06-19 21:43:01 +08:00
Timothy
de42aa6073
update resolver package 2020-06-18 21:58:03 +08:00
Timothy
31ebe517e5
Merge branch 'master' of github.com:TimothyYe/godns 2020-06-18 21:53:05 +08:00
Timothy
3c2e9a805e
add resolver package 2020-06-18 21:52:52 +08:00
Timothy
cf0a80a729
Merge pull request #67 from jemyzhang/pr
fix bug of get ip from interface
2020-05-05 19:20:37 +08:00
Timothy
49de482a53
Merge pull request #66 from jemyzhang/master
enable socks for domain update and telegram notification seperately
2020-05-05 19:19:49 +08:00
Jemy Zhang
20c2b96765 fix bug of get ip from interface 2020-05-05 16:15:36 +08:00
Jemy Zhang
97195eccce fix bug of get ip from interface 2020-05-05 16:13:23 +08:00
Jemy Zhang
9186772d50 enable socks for domain update and telegram notification seperately
- add configuration to enable socks for domain update
  and telegram notification seperately.
- sleep to reduce cpu usage while connection error occurred.
2020-05-05 15:32:10 +08:00
Timothy
09503d0c97
Update README.md 2020-05-03 23:04:40 +08:00
Timothy
5f405ba486
Update README.md 2020-05-03 23:02:20 +08:00
Timothy
8a3471ad1c
add DNS resolver, update all the handlers 2020-05-03 21:32:06 +08:00
Timothy
e91015f051
update test case 2020-05-03 20:08:32 +08:00
Timothy
d923cbde61
merge from release branch 2020-05-03 19:50:57 +08:00
Timothy
4f72668e2b
update Makefile 2020-05-01 02:43:43 +08:00
Timothy
b7b08efd71
Merge pull request #64 from TimothyYe/google-domain
fix Google Domain handler issue #46
2020-05-01 02:29:13 +08:00
Timothy
fcf819bb70
fix Google Domain handler issue #46 2020-05-01 02:26:12 +08:00
Timothy
6a164f8b0b
Merge pull request #62 from ebastos/dreamhost_dns
Dreamhost dns
2020-04-29 10:09:41 +08:00
Eri Bastos
e8ce3434ee Addressed code review plus missed updates 2020-04-28 18:40:04 -04:00
Eri Bastos
690e138164 Updated README file to include Dreamhost 2020-04-27 14:39:58 -04:00
Eri Bastos
73cd0517a0 Updated local 2020-04-27 14:31:47 -04:00
Eri Bastos
f2e9c0ab00 Add dreamhost handler 2020-04-27 14:28:13 -04:00
Eri Bastos
71077db79b Add dreamhost handler 2020-04-27 14:05:37 -04:00
Timothy
10e125d06d
Merge pull request #61 from ebastos/update_handlers
Update handlers
2020-04-20 09:35:00 +08:00
Eri Bastos
1c1aa6c420 HE Handler - Use Resolver 2020-04-19 15:34:25 -04:00
Eri Bastos
57ecf1f7bb Duck Handler - Use Resolver 2020-04-19 15:31:49 -04:00
Eri Bastos
9397f6b272 DNSPod Handler - Use Resolver 2020-04-19 15:26:10 -04:00
Eri Bastos
68b729e635 AliDNS Handler - Use Resolver 2020-04-19 15:20:22 -04:00
Timothy
9a43df7906
Merge pull request #60 from ebastos/remote_resolver
Replaced cached IP with remote resolver
2020-04-19 13:03:22 +08:00
Eri Bastos
61786fdfb5 Added mod files 2020-04-18 12:57:20 -04:00
Eri Bastos
f0a6291406 Replaced cached IP with remote resolver 2020-04-18 12:51:50 -04:00
Timothy
41d54f1354
Merge pull request #59 from ebastos/use_switch_case
Use switch/case to make code easier to follow
2020-04-17 10:21:31 +08:00
Eri Bastos
019388f7b5 Use switch/case to make code easier to follow 2020-04-16 19:26:58 -04:00
Timothy
4ca167ac43
Merge pull request #57 from jemyzhang/master
proxy should not be used while getting public ip
2020-03-21 23:09:08 +08:00
Jemy Zhang
df549ddc33 remove proxy while getting the public ip
remove proxy while getting the public ip,
otherwise ip of socks server would be returned
2020-03-21 18:18:48 +08:00
Timothy
79134b3315
update Makefile 2020-03-21 16:14:56 +08:00
Timothy
1a417c87b3
Merge pull request #56 from jemyzhang/master
Add support to telegram notification
2020-03-21 14:43:10 +08:00
Jemy Zhang
f5a793a907 Add support to telegram notification 2020-03-21 13:34:54 +08:00
Timothy
e5f9076259
Merge branch 'master' of github.com:TimothyYe/godns 2020-03-09 14:18:08 +08:00
Timothy
9dfc40da19
use docker buildx to build docker images 2020-03-09 14:17:47 +08:00
Timothy
13fbb7275d Merge pull request #53 from rlei/master
Better DNSPod error message handling and ip_type checking
2020-02-09 15:55:53 +08:00
Rick Lei
06a7d2384a Better DNSPod error message handling and ip_type checking
DNSPod API now requires record type to be specified, so the "ip_type"
setting becomes a must in config.json.
2020-02-08 20:30:51 +01:00
Timothy
acbbe3158d
update README 2020-02-06 22:48:05 +08:00
Timothy
a65c38ddb7
add IPv6 support for DuckDNS 2020-02-06 22:34:39 +08:00
Timothy
162b639019
add IPv6 support for DuckDNS 2020-02-06 22:33:55 +08:00
Timothy
456e01df61
add IPv6 support for DNSPod 2020-02-06 22:09:44 +08:00
Timothy
2d4d414417
add IPv6 support for DNSPod 2020-02-06 22:07:31 +08:00
Timothy
13f3d940a2
add IPv6 support for HE.net 2020-02-06 21:38:52 +08:00
Timothy
168ef4585a
fix issue 2020-02-06 15:21:13 +08:00
Timothy
5ed9f8ce68
Merge pull request #52 from TimothyYe/ipv6
add IPv6 support for Cloudflare
2020-02-06 10:38:04 +08:00
Timothy
5bcad54901
update README 2020-02-06 10:34:26 +08:00
Timothy
ad7fb64aa1
add IPv6 support for Cloudflare 2020-02-06 10:32:45 +08:00
Timothy.Ye
290f6faf60
Merge branch 'master' into ipv6 2020-02-06 09:52:14 +08:00
Timothy
6ec63e4499
add IPV6 support 2020-01-30 23:54:47 +08:00
26 changed files with 1407 additions and 368 deletions

View File

@ -7,10 +7,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13.6
- name: Set up Go 1.15.7
uses: actions/setup-go@v1
with:
go-version: 1.13.6
go-version: 1.15.7
id: go
- name: Check out code into the Go module directory

4
.gitignore vendored
View File

@ -28,7 +28,9 @@ config.json
*.log
*.swp
*.gz
cmd/godns/godns
godns
godns.exe
config.json
cmd/godns/config.json
/.idea

76
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at i@xiaozhou.net. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

9
Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM golang:alpine AS builder
RUN mkdir /godns
ADD . /godns/
WORKDIR /godns
RUN CGO_ENABLED=0 go build -o godns cmd/godns/godns.go
FROM gcr.io/distroless/base
COPY --from=builder /godns/godns /godns
ENTRYPOINT ["/godns"]

View File

@ -2,44 +2,43 @@
BINARY=godns
# Builds the project
build:
GO111MODULE=on go build -o ${BINARY} -ldflags "-X main.Version=${VERSION}"
GO111MODULE=on go build -ldflags "-X main.Version=${VERSION}" -o ${BINARY} cmd/godns/godns.go
# Installs our project: copies binaries
install:
GO111MODULE=on go install
image:
# Build docker image
go clean
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o ${BINARY} -ldflags "-s -w -X main.Version=${VERSION}"
docker build -t timothyye/godns:${VERSION} .
docker tag timothyye/godns:${VERSION} timothyye/godns:latest
docker push timothyye/godns:${VERSION}
docker push timothyye/godns:latest
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t timothyye/godns:${VERSION} . --push
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t timothyye/godns:latest . --push
release:
# Clean
go clean
rm -rf *.gz
# Build for mac
GO111MODULE=on go build -o ${BINARY} -ldflags "-s -w -X main.Version=${VERSION}"
GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
tar czvf ${BINARY}-mac64-${VERSION}.tar.gz ./${BINARY}
# Build for linux
go clean
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o ${BINARY} -ldflags "-s -w -X main.Version=${VERSION}"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
tar czvf ${BINARY}-linux64-${VERSION}.tar.gz ./${BINARY}
# Build for arm
go clean
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -o ${BINARY} -ldflags "-s -w -X main.Version=${VERSION}"
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
tar czvf ${BINARY}-arm64-${VERSION}.tar.gz ./${BINARY}
go clean
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -o ${BINARY} -ldflags "-s -w -X main.Version=${VERSION}"
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
tar czvf ${BINARY}-arm-${VERSION}.tar.gz ./${BINARY}
# Build for win
go clean
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 GO111MODULE=on go build -o ${BINARY}.exe -ldflags "-s -w -X main.Version=${VERSION}"
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
tar czvf ${BINARY}-win64-${VERSION}.tar.gz ./${BINARY}.exe
make image
# Cleans our projects: deletes binaries
clean:
go clean
rm -rf ./godns
rm -rf ./godns.exe
rm -rf *.gz
.PHONY: clean build

510
README.md
View File

@ -7,7 +7,7 @@
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
```
[![MIT licensed][9]][10] [![LICENSE](https://img.shields.io/badge/license-NPL%20(The%20996%20Prohibited%20License)-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) [![Docker][3]][4] [![Go Report Card][11]][12] [![Cover.Run][15]][16] [![GoDoc][13]][14]
[![Apache licensed][9]][10] [![Docker][3]][4] [![Go Report Card][11]][12] [![Cover.Run][15]][16] [![GoDoc][13]][14]
[3]: https://images.microbadger.com/badges/image/timothyye/godns.svg
[4]: https://microbadger.com/images/timothyye/godns
@ -20,68 +20,107 @@
[15]: https://img.shields.io/badge/cover.run-88.2%25-green.svg
[16]: https://cover.run/go/github.com/timothyye/godns
GoDNS is a dynamic DNS (DDNS) client tool, it is based on my early open source project: [DynDNS](https://github.com/TimothyYe/DynDNS).
[GoDNS](https://github.com/TimothyYe/godns) is a dynamic DNS (DDNS) client tool. It is a rewrite in [Go](https://golang.org) of my early [DynDNS](https://github.com/TimothyYe/DynDNS) open source project.
Now I rewrite [DynDNS](https://github.com/TimothyYe/DynDNS) by Golang and call it [GoDNS](https://github.com/TimothyYe/godns).
Currently supports updating A records for subdomains. Doesn't support updating of root domains.
---
- [Supported DNS Providers](#supported-dns-providers)
- [Supported Platforms](#supported-platforms)
- [Pre-conditions](#pre-conditions)
- [Installation](#installation)
- [Usage](#usage)
- [Configuration](#configuration)
- [Overview](#overview)
- [Configuration properties](#configuration-properties)
- [Configuration examples](#configuration-examples)
- [Cloudflare](#cloudflare)
- [DNSPod](#dnspod)
- [Dreamhost](#dreamhost)
- [Google Domains](#google-domains)
- [AliDNS](#alidns)
- [DuckDNS](#duckdns)
- [No-IP](#no-ip)
- [HE.net](#henet)
- [Notifications](#notifications)
- [Email](#email)
- [Telegram](#telegram)
- [Slack](#slack)
- [Miscellaneous topics](#miscellaneous-topics)
- [IPv6 support](#ipv6-support)
- [Network interface IP address](#network-interface-ip-address)
- [SOCKS5 proxy support](#socks5-proxy-support)
- [Running GoDNS](#running-godns)
- [As a manual daemon](#as-a-manual-daemon)
- [As a managed daemon (with upstart)](#as-a-managed-daemon-with-upstart)
- [As a managed daemon (with systemd)](#as-a-managed-daemon-with-systemd)
- [As a Docker container](#as-a-docker-container)
- [As a Windows service](#as-a-windows-service)
- [Special Thanks](#special-thanks)
---
## Supported DNS Providers
* Cloudflare ([https://cloudflare.com](https://cloudflare.com))
* Google Domains ([https://domains.google](https://domains.google))
* DNSPod ([https://www.dnspod.cn/](https://www.dnspod.cn/))
* HE.net (Hurricane Electric) ([https://dns.he.net/](https://dns.he.net/))
* AliDNS ([https://help.aliyun.com/product/29697.html](https://help.aliyun.com/product/29697.html))
* DuckDNS ([https://www.duckdns.org](https://www.duckdns.org))
| Provider | IPv4 support | IPv6 support | Root Domain | Subdomains |
| ------------------------------------- | :----------------: | :----------------: | :----------------: | :----------------: |
| [Cloudflare][cloudflare] | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| [Google Domains][google.domains] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
| [DNSPod][dnspod] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
| [HE.net (Hurricane Electric)][he.net] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
| [AliDNS][alidns] | :white_check_mark: | :x: | :x: | :white_check_mark: |
| [DuckDNS][duckdns] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
| [Dreamhost][dreamhost] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
| [No-IP][no-ip] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
[cloudflare]: https://cloudflare.com
[google.domains]: https://domains.google
[dnspod]: https://www.dnspod.cn
[he.net]: https://dns.he.net
[alidns]: https://help.aliyun.com/product/29697.html
[duckdns]: https://www.duckdns.org
[dreamhost]: https://www.dreamhost.com
[no-ip]: https://www.noip.com
Tip: You can follow this [issue](https://github.com/TimothyYe/godns/issues/76) to view the current status of DDNS for root domains.
## Supported Platforms
* Linux
* MacOS
* ARM Linux (Raspberry Pi, etc...)
* ARM Linux (Raspberry Pi, etc.)
* Windows
* MIPS32 platform
## MIPS32 platform
To compile binaries for MIPS (mips or mipsle), run:
To compile binaries for MIPS (mips or mipsle):
```bash
GOOS=linux GOARCH=mips/mipsle GOMIPS=softfloat go build -a
```
The binary can run on routers as well.
## Pre-conditions
To use GoDNS, it is assumed:
* You registered (now own) a domain
* Domain was delegated to a supported [DNS provider](#supported-dns-providers) (i.e. it has nameserver `NS` records pointing at a supported provider)
Alternatively, you can sign in to [DuckDNS](https://www.duckdns.org) (with a social account) and get a subdomain on the duckdns.org domain for free.
## Installation
Build GoDNS by running (from the root of the repository):
```bash
GOOS=linux GOARCH=mips/mipsle GOMIPS=softfloat go build -a
cd cmd/godns # go to the GoDNS directory
go get -v # get dependencies
go build # build
```
And the binary can run well on routers.
You can also download a compiled binary from the [releases](https://github.com/TimothyYe/godns/releases).
## Usage
## Pre-condition
* Register and own a domain.
* Domain's nameserver points to [DNSPod](https://www.dnspod.cn/) or [HE.net](https://dns.he.net/) or [Cloudflare](https://www.cloudflare.com/) or [Google Domains](https://domains.google) or [AliDNS](https://dc.console.aliyun.com).
* Or just register an account from [DuckDNS](https://www.duckdns.org/).
## Get it
### Build it from source code
* Get source code from Github:
```bash
git clone https://github.com/timothyye/godns.git
```
* Go into the godns directory, get related library and then build it:
```bash
cd cmd/godns
go get -v
go build
```
### Download from releases
Download compiled binaries from [releases](https://github.com/TimothyYe/godns/releases)
## Get help
Print usage/help by running:
```bash
$ ./godns -h
@ -91,30 +130,37 @@ Usage of ./godns:
-h Show help
```
## Config it
## Configuration
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
* Rename it to **config.json**.
* Configure your provider, domain/subdomain info, username and password, etc.
* Configure the SMTP options if you want, a mail notification will sent to your mailbox once the IP is changed.
* Save it in the same directory of GoDNS, or use -c=your_conf_path command.
### Overview
## Config fields
* Make a copy of [config_sample.json](./config_sample.json) and name it `config.json`
* Configure your provider, domain/subdomain info, credentials, etc.
* Configure a notification medium (e.g. SMTP to receive emails) to get notified when your IP address changes
* Place the file in the same directory of GoDNS or use the `-c=path/to/your/file.json` option
* provider: The providers that GoDNS supports, available values are: `Cloudflare`, `Google`, `DNSPod`, `AliDNS`, `HE`, `DuckDNS`.
* email: Email or account name of your DNS provider.
* password: Password of your account.
* login_token: API token of your account.
* domains: Domains list, with your sub domains.
* ip_url: A site helps you to get your public IP address.
* interval: The interval `seconds` that GoDNS check your public IP.
* socks5_proxy: Socks5 proxy server.
### Configuration properties
### Config example for Cloudflare
* `provider` — One of the [supported provider to use](#supported-dns-providers): `Cloudflare`, `Google`, `DNSPod`, `AliDNS`, `HE`, `DuckDNS` or `Dreamhost`.
* `email` — Email or account name of the DNS provider.
* `password` — Password of the DNS provider.
* `login_token` — API token of the DNS provider.
* `domains` — Domains list, with your sub domains.
* `ip_url` — A URL for fetching one's public IPv4 address.
* `ipv6_url` — A URL for fetching one's public IPv6 address.
* `ip_type` — Switch deciding if IPv4 or IPv6 should be used (when [supported](#supported-dns-providers)). Available values: `IPv4` or `IPv6`.
* `interval` — How often (in seconds) the public IP should be updated.
* `socks5_proxy` — Socks5 proxy server.
* `resolver` — Address of a public DNS server to use. For instance to use [Google's public DNS](https://developers.google.com/speed/public-dns/docs/using), you can set `8.8.8.8` when using GoDNS in IPv4 mode or `2001:4860:4860::8888` in IPv6 mode.
### Configuration examples
#### Cloudflare
For Cloudflare, you need to provide the email & Global API Key as password (or to use the API token) and config all the domains & subdomains.
* Using email & Global API Key
<details>
<summary>Using email & Global API Key</summary>
```json
{
@ -129,13 +175,16 @@ For Cloudflare, you need to provide the email & Global API Key as password (or t
"sub_domains": ["www","test"]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
* Using the API Token
<details>
<summary>Using the API Token</summary>
```json
{
@ -149,16 +198,21 @@ For Cloudflare, you need to provide the email & Global API Key as password (or t
"sub_domains": ["www","test"]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
### Config example for DNSPod
#### DNSPod
For DNSPod, you need to provide your API Token(you can create it [here](https://www.dnspod.cn/console/user/security)), and config all the domains & subdomains.
<details>
<summary>Example</summary>
```json
{
"provider": "DNSPod",
@ -171,16 +225,51 @@ For DNSPod, you need to provide your API Token(you can create it [here](https://
"sub_domains": ["www","test"]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"ip_type": "IPV4",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
### Config example for Google Domains
#### Dreamhost
For Dreamhost, you need to provide your API Token(you can create it [here](https://panel.dreamhost.com/?tree=home.api)), and config all the domains & subdomains.
<details>
<summary>Example</summary>
```json
{
"provider": "Dreamhost",
"login_token": "your_api_key",
"domains": [{
"domain_name": "example.com",
"sub_domains": ["www","test"]
},{
"domain_name": "example2.com",
"sub_domains": ["www","test"]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"ip_type": "IPV4",
"interval": 300,
"resolver": "ns1.dreamhost.com",
"socks5_proxy": ""
}
```
</details>
#### Google Domains
For Google Domains, you need to provide email & password, and config all the domains & subdomains.
<details>
<summary>Example</summary>
```json
{
"provider": "Google",
@ -194,16 +283,21 @@ For Google Domains, you need to provide email & password, and config all the dom
"sub_domains": ["www","test"]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
### Config example for AliDNS
#### AliDNS
For AliDNS, you need to provide `AccessKeyID` & `AccessKeySecret` as `email` & `password`, and config all the domains & subdomains.
<details>
<summary>Example</summary>
```json
{
"provider": "AliDNS",
@ -218,16 +312,21 @@ For AliDNS, you need to provide `AccessKeyID` & `AccessKeySecret` as `email` & `
"sub_domains": ["www","test"]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
### Config example for DuckDNS
#### DuckDNS
For DuckDNS, only need to provide the `token`, config 1 default domain & subdomains.
<details>
<summary>Example</summary>
```json
{
"provider": "DuckDNS",
@ -241,16 +340,46 @@ For DuckDNS, only need to provide the `token`, config 1 default domain & subdoma
]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
### Config example for HE.net
#### No-IP
<details>
<summary>Example</summary>
```json
{
"provider": "NoIP",
"email": "mail@example.com",
"password": "YourPassword",
"domains": [
{
"domain_name": "ddns.net",
"sub_domains": ["timothyye6"]
}
],
"ip_type": "IPv4",
"ip_url": "https://myip.biturl.top",
"resolver": "8.8.8.8",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
#### HE.net
For HE, email is not needed, just fill DDNS key to password, and config all the domains & subdomains.
<details>
<summary>Example</summary>
```json
{
"provider": "HE",
@ -264,128 +393,227 @@ For HE, email is not needed, just fill DDNS key to password, and config all the
"sub_domains": ["www","test"]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
### HE.net DDNS configuration
<details>
<summary>Provider configuration</summary>
Add a new "A record", make sure that "Enable entry for dynamic dns" is checked:
Add a new "A record" and make sure that "Enable entry for dynamic dns" is checked:
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/he1.png?raw=true" width="640" />
<img src="./snapshots/he1.png" width="640" />
Fill your own DDNS key or generate a random DDNS key for this new created "A record":
Fill in your own DDNS key or generate a random DDNS key for this new created "A record":
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/he2.png?raw=true" width="640" />
<img src="./snapshots/he2.png" width="640" />
Remember the DDNS key and fill it as password to the config.json.
Remember the DDNS key and set it in the `password` property in the configuration file.
__NOTICE__: If you have multiple domains or subdomains, make sure their DDNS key are the same.
</details>
### Get an IP address from the interface
### Notifications
For some reasons if you want to get an IP directly from the interface, say `eth0` for Linux or `Local Area Connection` for Windows, update config file like this:
GoDNS can send a notification each time the IP changes.
```json
"ip_url": "",
"ip_interface": "eth0",
```
#### Email
If you set both `ip_url` and `ip_interface`, it first tries to get an IP address online, and if not succeed, gets
an IP address from the interface as a fallback.
Note that IPv6 address will be ignored currently.
### Email notification support
Update config file and provide your SMTP options, a notification mail will be sent to your mailbox once the IP is changed and updated.
Emails are sent over [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol). Update your configuration with the following snippet:
```json
"notify": {
"enabled": true,
"smtp_server": "smtp.example.com",
"smtp_username": "user",
"smtp_password": "password",
"smtp_port": 25,
"send_to": "my_mail@example.com"
"mail": {
"enabled": true,
"smtp_server": "smtp.example.com",
"smtp_username": "user",
"smtp_password": "password",
"smtp_port": 25,
"send_to": "my_mail@example.com"
}
}
```
Notification mail example:
Each time the IP changes, you will receive an email like that:
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/mail.png?raw=true" />
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/mail.png?raw=true" />
### SOCKS5 proxy support
#### Telegram
You can also use SOCKS5 proxy, just fill SOCKS5 address to the ```socks5_proxy``` item:
To receive a [Telegram](https://telegram.org/) message each time the IP changes, update your configuration with the following snippet:
```json
"socks5_proxy": "127.0.0.1:7070"
"notify": {
"telegram": {
"enabled": true,
"bot_api_key": "11111:aaaa-bbbb",
"chat_id": "-123456",
"message_template": "Domain *{{ .Domain }}* is updated to %0A{{ .CurrentIP }}",
"use_proxy": false
},
}
```
Now all the queries will go through the specified SOCKS5 proxy.
The `message_template` property supports [markdown](https://www.markdownguide.org). New lines needs to be escaped with `%0A`.
## Run it as a daemon manually
#### Slack
To receive a [Slack](https://slack.com) message each time the IP changes, update your configuration with the following snippet:
```json
"notify": {
"slack": {
"enabled": true,
"bot_api_token": "xoxb-xxx",
"channel": "your_channel",
"message_template": "Domain *{{ .Domain }}* is updated to \n{{ .CurrentIP }}",
"use_proxy": false
},
}
```
The `message_template` property supports [markdown](https://www.markdownguide.org). New lines needs to be escaped with `\n`.
### Miscellaneous topics
#### IPv6 support
Most of the [providers](#supported-dns-providers) support IPv6.
To enable the `IPv6` support of GoDNS, there are two solutions to choose from:
1. Use an online service to lookup the external IPv6
For that:
- Set the `ip_type` as `IPv6`, and make sure the `ipv6_url` is configured
- Create an `AAAA` record instead of an `A` record in your DNS provider
<details>
<summary>Configuration example</summary>
```json
{
"domains": [
{
"domain_name": "example.com",
"sub_domains": [
"ipv6"
]
}
],
"resolver": "2001:4860:4860::8888",
"ipv6_url": "https://api-ipv6.ip.sb/ip",
"ip_type": "IPv6"
}
```
</details>
2. Let GoDNS find the IPv6 of the network interface of the machine it is running on (more on that [later](#network-interface-ip-address)).
For this to happen, just leave `ip_url` and `ipv6_url` empty.
Note that the network interface must be configured with an IPv6 for this to work.
#### Network interface IP address
For some reasons if you want to get the IP address associated to a network interface (instead of performing an online lookup), you can specify it in the configuration file this way:
```json
...
"ip_url": "",
"ip_interface": "interface-name",
...
```
With `interface-name` replaced by the name of the network interface, e.g. `eth0` on Linux or `Local Area Connection` on Windows.
Note: If `ip_url` is also specified, it will be used to perform an online lookup first and the network interface IP will be used as a fallback in case of failure.
#### SOCKS5 proxy support
You can make all remote calls go through a [SOCKS5 proxy](https://en.wikipedia.org/wiki/SOCKS#SOCKS5) by specifying it in the configuration file this way:
```json
...
"socks5_proxy": "127.0.0.1:7070"
"use_proxy": true
...
```
## Running GoDNS
There are few ways to run GoDNS.
### As a manual daemon
```bash
nohup ./godns &
```
## Run it as a daemon, manage it via Upstart
Note: when the program stops, it will not be restarted.
* Install `upstart` first
* Copy `./upstart/godns.conf` to `/etc/init`
* Start it as a system service:
### As a managed daemon (with upstart)
1. Install `upstart` first (if not available already)
2. Copy `./upstart/godns.conf` to `/etc/init` (and tweak it to your needs)
3. Start the service:
```bash
sudo start godns
```
### As a managed daemon (with systemd)
1. Install `systemd` first (it not available already)
2. Copy `./systemd/godns.service` to `/lib/systemd/system` (and tweak it to your needs)
3. Start the service:
```bash
sudo systemctl enable godns
sudo systemctl start godns
```
### As a Docker container
With `/path/to/config.json` your local configuration file, run:
```bash
sudo start godns
docker run \
-d --name godns --restart=always \
-v /path/to/config.json:/config.json \
timothyye/godns:latest
```
## Run it as a daemon, manage it via Systemd
### As a Windows service
* Modify `./systemd/godns.service` and config it.
* Copy `./systemd/godns.service` to `/lib/systemd/system`
* Start it as a systemd service:
1. Download the latest version of [NSSM](https://nssm.cc/download)
```bash
sudo systemctl enable godns
sudo systemctl start godns
```
2. In an administrative prompt, from the folder where NSSM was downloaded, e.g. `C:\Downloads\nssm\` **win64**, run:
## Run it with docker
```
nssm install YOURSERVICENAME
```
Now godns supports to run in docker.
3. Follow the interface to configure the service. In the "Application" tab just indicate where the `godns.exe` file is. Optionally you can also define a description on the "Details" tab and define a log file on the "I/O" tab. Finish by clicking on the "Install service" button.
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
* Rename it to **config.json**.
* Run GoDNS with docker:
4. The service will now start along Windows.
```bash
docker run -d --name godns --restart=always \
-v /path/to/config.json:/usr/local/godns/config.json timothyye/godns:latest
```
## Run it as a Windows service
After creating your config.json file:
* Get the latest [NSSM](https://nssm.cc/download) for create a windows service.
* Open the command prompt (as administrator) in the folder where you downloaded NSSM (e.g. C:\Downloads\nssm\ **win64**) and run:
```
nssm install YOURSERVICENAME
```
You will have an interface to configure your service, it is very simple in the "Application" tab just indicate where your `godns.exe` file is. Optionally you can also define a description on the "Details" tab and define a log file on the "I/O" tab.
* Finish using the "Install service" button.
* Done. Now whenever windows start your service will be loaded.
* To uninstall:
Note: you can uninstall the service by running:
```
nssm remove YOURSERVICENAME
```
## Enjoy it!
## Special Thanks
<img src="https://i.imgur.com/xhe5RLZ.jpg" width="80px" align="right" />
Thanks JetBrains for sponsoring this project with [free open source license](https://www.jetbrains.com/community/opensource/).
> I like GoLand, it is an amazing and productive tool.

View File

@ -1,9 +0,0 @@
FROM timothyye/alpine:3.6-glibc
MAINTAINER Timothy
RUN apk add --update ca-certificates
RUN mkdir -p /usr/local/godns
COPY godns /usr/local/godns
RUN chmod +x /usr/local/godns/godns
RUN rm -rf /var/cache/apk/*
WORKDIR /usr/local/godns
ENTRYPOINT ["./godns", "-c", "/usr/local/godns/config.json"]

View File

@ -50,17 +50,17 @@ func dnsLoop() {
panicChan := make(chan godns.Domain)
log.Println("Creating DNS handler with provider:", configuration.Provider)
handler := handler.CreateHandler(configuration.Provider)
handler.SetConfiguration(&configuration)
h := handler.CreateHandler(configuration.Provider)
h.SetConfiguration(&configuration)
for i := range configuration.Domains {
go handler.DomainLoop(&configuration.Domains[i], panicChan)
go h.DomainLoop(&configuration.Domains[i], panicChan)
}
panicCount := 0
for {
failDomain := <-panicChan
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
go handler.DomainLoop(&failDomain, panicChan)
go h.DomainLoop(&failDomain, panicChan)
panicCount++
if panicCount >= godns.PanicMax {

View File

@ -19,16 +19,29 @@
}
],
"ip_url": "https://myip.biturl.top",
"ipv6_url": "https://api-ipv6.ip.sb/ip",
"ip_type": "IPv4",
"interval": 300,
"resolver": "8.8.8.8",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
"ip_interface": "eth0",
"socks5_proxy": "",
"use_proxy": false,
"notify": {
"enabled": false,
"smtp_server": "",
"smtp_username": "",
"smtp_password": "",
"smtp_port": 25,
"send_to": ""
"telegram": {
"enabled": false,
"bot_api_key": "",
"chat_id": "",
"message_template": "",
"use_proxy": false
},
"mail": {
"enabled": false,
"smtp_server": "",
"smtp_username": "",
"smtp_password": "",
"smtp_port": 25,
"send_to": ""
}
}
}

6
go.mod
View File

@ -3,12 +3,14 @@ module github.com/TimothyYe/godns
require (
github.com/bitly/go-simplejson v0.5.0
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6
github.com/fatih/color v1.7.0
github.com/google/uuid v1.1.1
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/go-colorable v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.4 // indirect
golang.org/x/net v0.0.0-20190110200230-915654e7eabc
golang.org/x/sys v0.0.0-20190226215855-775f8194d0f9 // indirect
github.com/miekg/dns v1.1.29
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
)

23
go.sum
View File

@ -2,8 +2,12 @@ github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkN
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6 h1:oV1V+uwP+sjmdSkvMxsl/l+HE+N8wbL49wCXZPel25M=
github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6/go.mod h1:txOV61Nn+21z77KUMkNsp8lTHoOFTtqotltQAFenS9I=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -13,10 +17,29 @@ github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRU
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190226215855-775f8194d0f9 h1:N26gncmS+iqc/W/SKhX3ElI5pkt72XYoRLgi5Z70LSc=
golang.org/x/sys v0.0.0-20190226215855-775f8194d0f9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=

View File

@ -74,7 +74,7 @@ func getHTTPBody(url string) ([]byte, error) {
if resp.StatusCode == http.StatusOK {
return body, err
}
return nil, fmt.Errorf("Status %d, Error:%s", resp.StatusCode, body)
return nil, fmt.Errorf("status %d, Error:%s", resp.StatusCode, body)
}
// NewAliDNS function creates instance of AliDNS and return
@ -92,9 +92,8 @@ func NewAliDNS(key, secret string) *AliDNS {
func (d *AliDNS) GetDomainRecords(domain, rr string) []DomainRecord {
resp := &domainRecordsResp{}
parms := map[string]string{
"Action": "DescribeDomainRecords",
"DomainName": domain,
"RRKeyWord": rr,
"Action": "DescribeSubDomainRecords",
"SubDomain": fmt.Sprintf("%s.%s", rr, domain),
}
urlPath := d.genRequestURL(parms)
body, err := getHTTPBody(urlPath)
@ -124,7 +123,7 @@ func (d *AliDNS) UpdateDomainRecord(r DomainRecord) error {
urlPath := d.genRequestURL(parms)
if urlPath == "" {
return errors.New("Failed to generate request URL")
return errors.New("failed to generate request URL")
}
_, err := getHTTPBody(urlPath)
if err != nil {
@ -134,7 +133,7 @@ func (d *AliDNS) UpdateDomainRecord(r DomainRecord) error {
}
func (d *AliDNS) genRequestURL(parms map[string]string) string {
pArr := []string{}
var pArr []string
ps := map[string]string{}
for k, v := range publicParm {
ps[k] = v

View File

@ -28,10 +28,17 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
}
}()
var lastIP string
looping := false
aliDNS := NewAliDNS(handler.Configuration.Email, handler.Configuration.Password)
for {
if looping {
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
looping = true
currentIP, err := godns.GetCurrentIP(handler.Configuration)
if err != nil {
@ -39,14 +46,19 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
continue
}
log.Println("currentIP is:", currentIP)
for _, subDomain := range domain.SubDomains {
hostname := subDomain + "." + domain.DomainName
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
if err != nil {
log.Println(err)
continue
}
//check against currently known IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
lastIP = currentIP
//check against locally cached IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
lastIP = currentIP
for _, subDomain := range domain.SubDomains {
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
records := aliDNS.GetDomainRecords(domain.DomainName, subDomain)
if records == nil || len(records) == 0 {
@ -62,18 +74,12 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
log.Printf("IP updated for subdomain:%s\r\n", subDomain)
}
// Send mail notification if notify is enabled
if handler.Configuration.Notify.Enabled {
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Printf("Failed to send notification")
}
// Send notification
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Printf("Failed to send notification")
}
}
}
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
}

View File

@ -9,6 +9,7 @@ import (
"log"
"net/http"
"runtime/debug"
"strings"
"time"
"github.com/TimothyYe/godns"
@ -76,7 +77,15 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
}()
var lastIP string
looping := false
for {
if looping {
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
looping = true
currentIP, err := godns.GetCurrentIP(handler.Configuration)
if err != nil {
log.Println("Error in GetCurrentIP:", err)
@ -87,8 +96,6 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
lastIP = currentIP
log.Println("Checking IP for domain", domain.DomainName)
zoneID := handler.getZone(domain.DomainName)
if zoneID != "" {
@ -102,14 +109,11 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
}
if rec.IP != currentIP {
log.Printf("IP mismatch: Current(%+v) vs Cloudflare(%+v)\r\n", currentIP, rec.IP)
handler.updateRecord(rec, currentIP)
lastIP = handler.updateRecord(rec, currentIP)
// Send mail notification if notify is enabled
if handler.Configuration.Notify.Enabled {
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
if err := godns.SendNotify(handler.Configuration, rec.Name, currentIP); err != nil {
log.Println("Failed to send notification")
}
// Send notification
if err := godns.SendNotify(handler.Configuration, rec.Name, currentIP); err != nil {
log.Println("Failed to send notification")
}
} else {
log.Printf("Record OK: %+v - %+v\r\n", rec.Name, rec.IP)
@ -119,14 +123,14 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
log.Println("Failed to find zone for domain:", domain.DomainName)
}
}
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
}
// Check if record is present in domain conf
func recordTracked(domain *godns.Domain, record *DNSRecord) bool {
if record.Name == domain.DomainName {
return true
}
for _, subDomain := range domain.SubDomains {
sd := fmt.Sprintf("%s.%s", subDomain, domain.DomainName)
if record.Name == sd {
@ -139,7 +143,7 @@ func recordTracked(domain *godns.Domain, record *DNSRecord) bool {
// Create a new request with auth in place and optional proxy
func (handler *Handler) newRequest(method, url string, body io.Reader) (*http.Request, *http.Client) {
client := godns.GetHttpClient(handler.Configuration)
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
if client == nil {
log.Println("cannot create HTTP client")
}
@ -162,7 +166,7 @@ func (handler *Handler) getZone(domain string) string {
var z ZoneResponse
req, client := handler.newRequest("GET", "/zones", nil)
req, client := handler.newRequest("GET", fmt.Sprintf("/zones?name=%s", domain), nil)
resp, err := client.Do(req)
if err != nil {
log.Println("Request error:", err.Error())
@ -194,8 +198,16 @@ func (handler *Handler) getDNSRecords(zoneID string) []DNSRecord {
var empty []DNSRecord
var r DNSRecordResponse
var recordType string
req, client := handler.newRequest("GET", "/zones/"+zoneID+"/dns_records?type=A", nil)
if handler.Configuration.IPType == "" || strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
recordType = "A"
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
recordType = "AAAA"
}
log.Println("Querying records with type:", recordType)
req, client := handler.newRequest("GET", fmt.Sprintf("/zones/"+zoneID+"/dns_records?type=%s&page=1&per_page=500", recordType), nil)
resp, err := client.Do(req)
if err != nil {
log.Println("Request error:", err.Error())
@ -219,10 +231,11 @@ func (handler *Handler) getDNSRecords(zoneID string) []DNSRecord {
}
// Update DNS A Record with new IP
func (handler *Handler) updateRecord(record DNSRecord, newIP string) {
func (handler *Handler) updateRecord(record DNSRecord, newIP string) string {
var r DNSRecordUpdateResponse
record.SetIP(newIP)
var lastIP string
j, _ := json.Marshal(record)
req, client := handler.newRequest("PUT",
@ -232,7 +245,7 @@ func (handler *Handler) updateRecord(record DNSRecord, newIP string) {
resp, err := client.Do(req)
if err != nil {
log.Println("Request error:", err.Error())
return
return ""
}
body, _ := ioutil.ReadAll(resp.Body)
@ -240,12 +253,14 @@ func (handler *Handler) updateRecord(record DNSRecord, newIP string) {
if err != nil {
log.Printf("Decoder error: %+v\n", err)
log.Printf("Response body: %+v\n", string(body))
return
return ""
}
if r.Success != true {
body, _ := ioutil.ReadAll(resp.Body)
log.Printf("Response failed: %+v\n", string(body))
} else {
log.Printf("Record updated: %+v - %+v", record.Name, record.IP)
lastIP = record.IP
}
return lastIP
}

View File

@ -14,7 +14,7 @@ import (
"time"
"github.com/TimothyYe/godns"
simplejson "github.com/bitly/go-simplejson"
"github.com/bitly/go-simplejson"
)
// Handler struct definition
@ -36,8 +36,16 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
}
}()
var lastIP string
looping := false
for {
if looping {
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
looping = true
log.Printf("Checking IP for domain %s \r\n", domain.DomainName)
domainID := handler.GetDomain(domain.DomainName)
@ -53,13 +61,19 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
}
log.Println("currentIP is:", currentIP)
//check against locally cached IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
lastIP = currentIP
for _, subDomain := range domain.SubDomains {
hostname := subDomain + "." + domain.DomainName
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
if err != nil {
log.Println(err)
continue
}
for _, subDomain := range domain.SubDomains {
//check against currently known IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
lastIP = currentIP
subDomainID, ip := handler.GetSubDomain(domainID, subDomain)
@ -73,22 +87,15 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
handler.UpdateIP(domainID, subDomainID, subDomain, currentIP)
// Send mail notification if notify is enabled
if handler.Configuration.Notify.Enabled {
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
// Send notification
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
} else {
log.Printf("%s.%s Current IP is same as domain IP, no need to update...\n", subDomain, domain.DomainName)
}
}
}
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
}
@ -163,7 +170,6 @@ func (handler *Handler) GetDomain(name string) int64 {
// GetSubDomain returns subdomain by domain id
func (handler *Handler) GetSubDomain(domainID int64, name string) (string, string) {
log.Println("debug:", domainID, name)
var ret, ip string
value := url.Values{}
value.Add("domain_id", strconv.FormatInt(domainID, 10))
@ -171,6 +177,15 @@ func (handler *Handler) GetSubDomain(domainID int64, name string) (string, strin
value.Add("length", "1")
value.Add("sub_domain", name)
if handler.Configuration.IPType == "" || strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
value.Add("record_type", "A")
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
value.Add("record_type", "AAAA")
} else {
log.Println("Error: must specify \"ip_type\" in config for DNSPod.")
return "", ""
}
response, err := handler.PostData("/Record.List", value)
if err != nil {
@ -212,7 +227,16 @@ func (handler *Handler) UpdateIP(domainID int64, subDomainID string, subDomainNa
value.Add("domain_id", strconv.FormatInt(domainID, 10))
value.Add("record_id", subDomainID)
value.Add("sub_domain", subDomainName)
value.Add("record_type", "A")
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
value.Add("record_type", "A")
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
value.Add("record_type", "AAAA")
} else {
log.Println("Error: must specify \"ip_type\" in config for DNSPod.")
return
}
value.Add("record_line", "默认")
value.Add("value", ip)
@ -233,13 +257,15 @@ func (handler *Handler) UpdateIP(domainID int64, subDomainID string, subDomainNa
if sjson.Get("status").Get("code").MustString() == "1" {
log.Println("New IP updated!")
} else {
log.Println("Failed to update IP record:", sjson.Get("status").Get("message").MustString())
}
}
// PostData post data and invoke DNSPod API
func (handler *Handler) PostData(url string, content url.Values) (string, error) {
client := godns.GetHttpClient(handler.Configuration)
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
if client == nil {
return "", errors.New("failed to create HTTP client")

View File

@ -0,0 +1,138 @@
package dreamhost
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"runtime/debug"
"strings"
"time"
"github.com/TimothyYe/godns"
"github.com/google/uuid"
)
var (
// DreamhostURL the API address for dreamhost.com
DreamhostURL = "https://api.dreamhost.com"
)
// Handler struct
type Handler struct {
Configuration *godns.Settings
}
// SetConfiguration pass dns settings and store it to handler instance
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
handler.Configuration = conf
}
// DomainLoop the main logic loop
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
defer func() {
if err := recover(); err != nil {
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
panicChan <- *domain
}
}()
looping := false
for {
if looping {
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
looping = true
currentIP, err := godns.GetCurrentIP(handler.Configuration)
if err != nil {
log.Println("get_currentIP:", err)
continue
}
log.Println("currentIP is:", currentIP)
for _, subDomain := range domain.SubDomains {
hostname := subDomain + "." + domain.DomainName
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
if err != nil {
log.Println(err)
continue
}
//check against currently known IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
handler.UpdateIP(hostname, currentIP, lastIP)
// Send notification
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
}
}
}
}
// UpdateIP update subdomain with current IP
func (handler *Handler) UpdateIP(hostname, currentIP, lastIP string) {
handler.updateDNS(lastIP, currentIP, hostname, "remove")
handler.updateDNS(lastIP, currentIP, hostname, "add")
}
// updateDNS can add or remove DNS records.
func (handler *Handler) updateDNS(dns, ip, hostname, action string) {
ipType := "A"
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
ipType = "AAAA"
}
// Generates UUID
uid, _ := uuid.NewRandom()
values := url.Values{}
values.Add("record", hostname)
values.Add("key", handler.Configuration.LoginToken)
values.Add("type", ipType)
values.Add("unique_id", uid.String())
switch action {
case "remove":
// Build URL query (remove)
values.Add("cmd", "dns-remove_record")
values.Add("value", dns)
case "add":
// Build URL query (add)
values.Add("cmd", "dns-add_record")
values.Add("value", ip)
default:
log.Fatalf("Unknown action %s\n", action)
}
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
req, _ := http.NewRequest("POST", DreamhostURL, strings.NewReader(values.Encode()))
req.SetBasicAuth(handler.Configuration.Email, handler.Configuration.Password)
if handler.Configuration.UserAgent != "" {
req.Header.Add("User-Agent", handler.Configuration.UserAgent)
}
resp, err := client.Do(req)
if err != nil {
log.Println("Request error...")
log.Println("Err:", err.Error())
} else {
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == http.StatusOK {
log.Println("Update IP success:", string(body))
} else {
log.Println("Update IP failed:", string(body))
}
}
}

View File

@ -5,6 +5,7 @@ import (
"io/ioutil"
"log"
"runtime/debug"
"strings"
"time"
"github.com/TimothyYe/godns"
@ -12,7 +13,7 @@ import (
var (
// DuckUrl the API address for Duck DNS
DuckUrl = "https://www.duckdns.org/update?domains=%s&token=%s&ip=%s"
DuckUrl = "https://www.duckdns.org/update?domains=%s&token=%s&%s"
)
// Handler struct
@ -34,27 +35,47 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
}
}()
var lastIP string
looping := false
for {
if looping {
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
looping = true
currentIP, err := godns.GetCurrentIP(handler.Configuration)
if err != nil {
log.Println("get_currentIP:", err)
continue
}
log.Println("currentIP is:", currentIP)
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
var ip string
//check against locally cached IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
lastIP = currentIP
client := godns.GetHttpClient(handler.Configuration)
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
ip = fmt.Sprintf("ip=%s", currentIP)
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
ip = fmt.Sprintf("ipv6=%s", currentIP)
}
for _, subDomain := range domain.SubDomains {
for _, subDomain := range domain.SubDomains {
hostname := subDomain + "." + domain.DomainName
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
if err != nil {
log.Println(err)
continue
}
//check against currently known IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
// update IP with HTTP GET request
resp, err := client.Get(fmt.Sprintf(DuckUrl, subDomain, handler.Configuration.LoginToken, currentIP))
resp, err := client.Get(fmt.Sprintf(DuckUrl, subDomain, handler.Configuration.LoginToken, ip))
if err != nil {
// handle error
log.Print("Failed to update sub domain:", subDomain)
@ -65,25 +86,17 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
body, err := ioutil.ReadAll(resp.Body)
if err != nil || string(body) != "OK" {
// handle error
log.Print("Failed to update sub domain:", subDomain, err.Error())
log.Println("Failed to update the IP")
continue
} else {
log.Print("IP updated to:", currentIP)
}
// Send mail notification if notify is enabled
if handler.Configuration.Notify.Enabled {
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
// Send notification
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
}
}
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
}

View File

@ -5,7 +5,6 @@ import (
"io/ioutil"
"log"
"net/http"
"net/url"
"runtime/debug"
"strings"
"time"
@ -14,8 +13,8 @@ import (
)
var (
// GoogleUrl the API address for Google Domains
GoogleUrl = "https://domains.google.com/nic/update"
// GoogleURL the API address for Google Domains
GoogleURL = "https://%s:%s@domains.google.com/nic/update?hostname=%s.%s&myip=%s"
)
// Handler struct
@ -37,58 +36,64 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
}
}()
var lastIP string
looping := false
for {
currentIP, err := godns.GetCurrentIP(handler.Configuration)
if looping {
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
looping = true
currentIP, err := godns.GetCurrentIP(handler.Configuration)
if err != nil {
log.Println("get_currentIP:", err)
continue
}
log.Println("currentIP is:", currentIP)
for _, subDomain := range domain.SubDomains {
hostname := subDomain + "." + domain.DomainName
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
if err != nil {
log.Println(err)
continue
}
//check against locally cached IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
lastIP = currentIP
for _, subDomain := range domain.SubDomains {
//check against currently known IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
// Send mail notification if notify is enabled
if handler.Configuration.Notify.Enabled {
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
// Send notification
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
}
}
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
}
// UpdateIP update subdomain with current IP
func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
values := url.Values{}
values.Add("hostname", fmt.Sprintf("%s.%s", subDomain, domain))
values.Add("myip", currentIP)
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
resp, err := client.Get(fmt.Sprintf(GoogleURL,
handler.Configuration.Email,
handler.Configuration.Password,
subDomain,
domain,
currentIP))
client := godns.GetHttpClient(handler.Configuration)
req, _ := http.NewRequest("POST", GoogleUrl, strings.NewReader(values.Encode()))
req.SetBasicAuth(handler.Configuration.Email, handler.Configuration.Password)
if handler.Configuration.UserAgent != "" {
req.Header.Add("User-Agent", handler.Configuration.UserAgent)
if err != nil {
// handle error
log.Print("Failed to update sub domain:", subDomain)
return
}
resp, err := client.Do(req)
defer resp.Body.Close()
if err != nil {
log.Println("Request error...")
@ -96,7 +101,11 @@ func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
} else {
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == http.StatusOK {
log.Println("Update IP success:", string(body))
if strings.Contains(string(body), "good") {
log.Println("Update IP success:", string(body))
} else if strings.Contains(string(body), "nochg") {
log.Println("IP not changed:", string(body))
}
} else {
log.Println("Update IP failed:", string(body))
}

View File

@ -5,9 +5,11 @@ import (
"github.com/TimothyYe/godns/handler/alidns"
"github.com/TimothyYe/godns/handler/cloudflare"
"github.com/TimothyYe/godns/handler/dnspod"
"github.com/TimothyYe/godns/handler/dreamhost"
"github.com/TimothyYe/godns/handler/duck"
"github.com/TimothyYe/godns/handler/google"
"github.com/TimothyYe/godns/handler/he"
"github.com/TimothyYe/godns/handler/noip"
)
// IHandler is the interface for all DNS handlers
@ -25,6 +27,8 @@ func CreateHandler(provider string) IHandler {
handler = IHandler(&cloudflare.Handler{})
case godns.DNSPOD:
handler = IHandler(&dnspod.Handler{})
case godns.DREAMHOST:
handler = IHandler(&dreamhost.Handler{})
case godns.HE:
handler = IHandler(&he.Handler{})
case godns.ALIDNS:
@ -33,6 +37,8 @@ func CreateHandler(provider string) IHandler {
handler = IHandler(&google.Handler{})
case godns.DUCK:
handler = IHandler(&duck.Handler{})
case godns.NOIP:
handler = IHandler(&noip.Handler{})
}
return handler

View File

@ -37,8 +37,15 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
}
}()
var lastIP string
looping := false
for {
if looping {
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
looping = true
currentIP, err := godns.GetCurrentIP(handler.Configuration)
if err != nil {
@ -48,27 +55,28 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
log.Println("currentIP is:", currentIP)
//check against locally cached IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
lastIP = currentIP
for _, subDomain := range domain.SubDomains {
for _, subDomain := range domain.SubDomains {
hostname := subDomain + "." + domain.DomainName
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
if err != nil {
log.Println(err)
continue
}
//check against currently known IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
// Send mail notification if notify is enabled
if handler.Configuration.Notify.Enabled {
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
// Send notification
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
}
}
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
}
@ -80,7 +88,7 @@ func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
values.Add("password", handler.Configuration.Password)
values.Add("myip", currentIP)
client := godns.GetHttpClient(handler.Configuration)
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
req, _ := http.NewRequest("POST", HEUrl, strings.NewReader(values.Encode()))
resp, err := client.Do(req)

View File

@ -0,0 +1,114 @@
package noip
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"runtime/debug"
"strings"
"time"
"github.com/TimothyYe/godns"
)
var (
// NoIPUrl the API address for NoIP
NoIPUrl = "https://%s:%s@dynupdate.no-ip.com/nic/update?hostname=%s&%s"
)
// Handler struct
type Handler struct {
Configuration *godns.Settings
}
// SetConfiguration pass dns settings and store it to handler instance
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
handler.Configuration = conf
}
// DomainLoop the main logic loop
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
defer func() {
if err := recover(); err != nil {
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
panicChan <- *domain
}
}()
looping := false
for {
if looping {
// Sleep with interval
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
}
looping = true
currentIP, err := godns.GetCurrentIP(handler.Configuration)
if err != nil {
log.Println("get_currentIP:", err)
continue
}
log.Println("currentIP is:", currentIP)
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
var ip string
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
ip = fmt.Sprintf("myip=%s", currentIP)
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
ip = fmt.Sprintf("myipv6=%s", currentIP)
}
for _, subDomain := range domain.SubDomains {
hostname := subDomain + "." + domain.DomainName
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
if err != nil {
log.Println(err)
continue
}
//check against currently known IP, if no change, skip update
if currentIP == lastIP {
log.Printf("IP is the same as cached one. Skip update.\n")
} else {
req, _ := http.NewRequest("GET", fmt.Sprintf(
NoIPUrl,
handler.Configuration.Email,
handler.Configuration.Password,
hostname,
ip), nil)
if handler.Configuration.UserAgent != "" {
req.Header.Add("User-Agent", handler.Configuration.UserAgent)
}
// update IP with HTTP GET request
resp, err := client.Do(req)
if err != nil {
// handle error
log.Print("Failed to update sub domain:", subDomain)
continue
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil || !strings.Contains(string(body), "good") {
log.Println("Failed to update the IP")
continue
} else {
log.Print("IP updated to:", currentIP)
}
// Send notification
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Println("Failed to send notification")
}
}
}
}
}

110
resolver/resolver.go Normal file
View File

@ -0,0 +1,110 @@
// Package resolver is a simple dns resolver
// based on miekg/dns
package resolver
import (
"errors"
"math/rand"
"net"
"os"
"strings"
"time"
"github.com/miekg/dns"
)
// DNSResolver represents a dns resolver
type DNSResolver struct {
Servers []string
RetryTimes int
r *rand.Rand
}
// New initializes DnsResolver.
func New(servers []string) *DNSResolver {
for i := range servers {
servers[i] = net.JoinHostPort(servers[i], "53")
}
return &DNSResolver{servers, len(servers) * 2, rand.New(rand.NewSource(time.Now().UnixNano()))}
}
// NewFromResolvConf initializes DnsResolver from resolv.conf like file.
func NewFromResolvConf(path string) (*DNSResolver, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return &DNSResolver{}, errors.New("no such file or directory: " + path)
}
config, err := dns.ClientConfigFromFile(path)
if err != nil {
return &DNSResolver{}, err
}
var servers []string
for _, ipAddress := range config.Servers {
servers = append(servers, net.JoinHostPort(ipAddress, "53"))
}
return &DNSResolver{servers, len(servers) * 2, rand.New(rand.NewSource(time.Now().UnixNano()))}, err
}
// LookupHost returns IP addresses of provied host.
// In case of timeout retries query RetryTimes times.
func (r *DNSResolver) LookupHost(host string, dnsType uint16) ([]net.IP, error) {
return r.lookupHost(host, dnsType, r.RetryTimes)
}
func (r *DNSResolver) lookupHost(host string, dnsType uint16, triesLeft int) ([]net.IP, error) {
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
switch dnsType {
case dns.TypeA:
m1.Question[0] = dns.Question{Name: dns.Fqdn(host), Qtype: dns.TypeA, Qclass: dns.ClassINET}
case dns.TypeAAAA:
m1.Question[0] = dns.Question{Name: dns.Fqdn(host), Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}
}
in, err := dns.Exchange(m1, r.Servers[r.r.Intn(len(r.Servers))])
var result []net.IP
if err != nil {
if strings.HasSuffix(err.Error(), "i/o timeout") && triesLeft > 0 {
triesLeft--
return r.lookupHost(host, dnsType, triesLeft)
}
return result, err
}
if in != nil && in.Rcode != dns.RcodeSuccess {
return result, errors.New(dns.RcodeToString[in.Rcode])
}
if dnsType == dns.TypeA {
if len(in.Answer) > 0 {
for _, record := range in.Answer {
if t, ok := record.(*dns.A); ok {
result = append(result, t.A)
}
}
} else {
return result, errors.New("empty result")
}
}
if dnsType == dns.TypeAAAA {
if len(in.Answer) > 0 {
for _, record := range in.Answer {
if t, ok := record.(*dns.AAAA); ok {
result = append(result, t.AAAA)
}
}
} else {
return result, errors.New("Cannot resolve this domain, please make sure the IP type is right")
}
}
return result, err
}

45
resolver/resolver_test.go Normal file
View File

@ -0,0 +1,45 @@
package resolver
import (
"fmt"
"reflect"
"testing"
"github.com/miekg/dns"
)
func TestNew(t *testing.T) {
servers := []string{"8.8.8.8", "8.8.4.4"}
expectedServers := []string{"8.8.8.8:53", "8.8.4.4:53"}
resolver := New(servers)
if !reflect.DeepEqual(resolver.Servers, expectedServers) {
t.Error("resolver.Servers: ", resolver.Servers, "should be equal to", expectedServers)
}
}
func TestLookupHost_ValidServer(t *testing.T) {
resolver := New([]string{"8.8.8.8", "8.8.4.4"})
result, err := resolver.LookupHost("google-public-dns-a.google.com", dns.TypeA)
if err != nil {
fmt.Println(err.Error())
t.Error("Should succeed dns lookup")
}
if result[0].String() != "8.8.8.8" {
t.Error("google-public-dns-a.google.com should be resolved to 8.8.8.8")
}
}
func TestLookupHostIPv6_ValidServer(t *testing.T) {
resolver := New([]string{"2001:4860:4860::8888", "2001:4860:4860::8844"})
result, err := resolver.LookupHost("google-public-dns-a.google.com", dns.TypeAAAA)
if err != nil {
fmt.Println(err.Error())
t.Error("Should succeed dns lookup")
}
if result[0].String() != "2001:4860:4860::8888" {
t.Error("result should be: 2001:4860:4860::8888")
}
}

View File

@ -12,8 +12,26 @@ type Domain struct {
SubDomains []string `json:"sub_domains"`
}
// Notify struct for slack notification
type SlackNotify struct {
Enabled bool `json:"enabled"`
BotApiToken string `json:"bot_api_token"`
Channel string `json:"channel"`
MsgTemplate string `json:"message_template"`
UseProxy bool `json:"use_proxy"`
}
// Notify struct for telegram notification
type TelegramNotify struct {
Enabled bool `json:"enabled"`
BotApiKey string `json:"bot_api_key"`
ChatId string `json:"chat_id"`
MsgTemplate string `json:"message_template"`
UseProxy bool `json:"use_proxy"`
}
// Notify struct for SMTP notification
type Notify struct {
type MailNotify struct {
Enabled bool `json:"enabled"`
SMTPServer string `json:"smtp_server"`
SMTPUsername string `json:"smtp_username"`
@ -22,6 +40,13 @@ type Notify struct {
SendTo string `json:"send_to"`
}
// Notify struct
type Notify struct {
Telegram TelegramNotify `json:"telegram"`
Mail MailNotify `json:"mail"`
Slack SlackNotify `json:"slack"`
}
// Settings struct
type Settings struct {
Provider string `json:"provider"`
@ -30,14 +55,16 @@ type Settings struct {
LoginToken string `json:"login_token"`
Domains []Domain `json:"domains"`
IPUrl string `json:"ip_url"`
IPV6Url string `json:"ipv6_url"`
Interval int `json:"interval"`
UserAgent string `json:"user_agent,omitempty"`
LogPath string `json:"log_path"`
Socks5Proxy string `json:"socks5_proxy"`
Notify Notify `json:"notify"`
IPInterface string `json:"ip_interface"`
//the code is not ready to update AAAA record
//IPType string `json:"ip_type"`
IPType string `json:"ip_type"`
Resolver string `json:"resolver"`
UseProxy bool `json:"use_proxy"`
}
// LoadSettings -- Load settings from config file

285
utils.go
View File

@ -2,16 +2,22 @@ package godns
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"html/template"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strings"
dnsResolver "github.com/TimothyYe/godns/resolver"
"github.com/miekg/dns"
"golang.org/x/net/proxy"
gomail "gopkg.in/gomail.v2"
"gopkg.in/gomail.v2"
)
var (
@ -46,6 +52,14 @@ const (
GOOGLE = "Google"
// DUCK for Duck DNS
DUCK = "DuckDNS"
// DREAMHOST for Dreamhost
DREAMHOST = "Dreamhost"
// NOIP for NoIP
NOIP = "NoIP"
// IPV4 for IPV4 mode
IPV4 = "IPV4"
// IPV6 for IPV6 mode
IPV6 = "IPV6"
)
//GetIPFromInterface gets IP address from the specific interface
@ -84,23 +98,19 @@ func GetIPFromInterface(configuration *Settings) (string, error) {
continue
}
//the code is not ready for updating an AAAA record
/*
if (isIPv4(ip.String())){
if (configuration.IPType=="IPv6"){
continue;
}
}else{
if (configuration.IPType!="IPv6"){
continue;
}
} */
if !isIPv4(ip.String()) {
continue
if isIPv4(ip.String()) {
if strings.ToUpper(configuration.IPType) != IPV4 {
continue
}
} else {
if strings.ToUpper(configuration.IPType) != IPV6 {
continue
}
}
return ip.String(), nil
if ip.String() != "" {
return ip.String(), nil
}
}
return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
}
@ -110,10 +120,10 @@ func isIPv4(ip string) bool {
}
// GetHttpClient creates the HTTP client and return it
func GetHttpClient(configuration *Settings) *http.Client {
func GetHttpClient(configuration *Settings, useProxy bool) *http.Client {
client := &http.Client{}
if configuration.Socks5Proxy != "" {
if useProxy && configuration.Socks5Proxy != "" {
log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
if err != nil {
@ -133,7 +143,7 @@ func GetHttpClient(configuration *Settings) *http.Client {
func GetCurrentIP(configuration *Settings) (string, error) {
var err error
if configuration.IPUrl != "" {
if configuration.IPUrl != "" || configuration.IPV6Url != "" {
ip, err := GetIPOnline(configuration)
if err != nil {
log.Println("get ip online failed. Fallback to get ip from interface if possible.")
@ -158,22 +168,15 @@ func GetCurrentIP(configuration *Settings) (string, error) {
func GetIPOnline(configuration *Settings) (string, error) {
client := &http.Client{}
if configuration.Socks5Proxy != "" {
var response *http.Response
var err error
log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
if err != nil {
log.Println("can't connect to the proxy:", err)
return "", err
}
httpTransport := &http.Transport{}
client.Transport = httpTransport
httpTransport.Dial = dialer.Dial
if configuration.IPType == "" || strings.ToUpper(configuration.IPType) == IPV4 {
response, err = client.Get(configuration.IPUrl)
} else {
response, err = client.Get(configuration.IPV6Url)
}
response, err := client.Get(configuration.IPUrl)
if err != nil {
log.Println("Cannot get IP...")
return "", err
@ -187,15 +190,16 @@ func GetIPOnline(configuration *Settings) (string, error) {
// CheckSettings check the format of settings
func CheckSettings(config *Settings) error {
if config.Provider == DNSPOD {
switch config.Provider {
case DNSPOD:
if config.Password == "" && config.LoginToken == "" {
return errors.New("password or login token cannot be empty")
}
} else if config.Provider == HE {
case HE:
if config.Password == "" {
return errors.New("password cannot be empty")
}
} else if config.Provider == CLOUDFLARE {
case CLOUDFLARE:
if config.LoginToken == "" {
if config.Email == "" {
return errors.New("email cannot be empty")
@ -204,55 +208,207 @@ func CheckSettings(config *Settings) error {
return errors.New("password cannot be empty")
}
}
} else if config.Provider == ALIDNS {
case ALIDNS:
if config.Email == "" {
return errors.New("email cannot be empty")
}
if config.Password == "" {
return errors.New("password cannot be empty")
}
} else if config.Provider == DUCK {
case DUCK:
if config.LoginToken == "" {
return errors.New("login token cannot be empty")
}
} else if config.Provider == GOOGLE {
case GOOGLE:
fallthrough
case NOIP:
if config.Email == "" {
return errors.New("email cannot be empty")
}
if config.Password == "" {
return errors.New("password cannot be empty")
}
} else {
return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS")
case DREAMHOST:
if config.LoginToken == "" {
return errors.New("login token cannot be empty")
}
default:
return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS/Dreamhost")
}
return nil
}
// SendNotify sends mail notify if IP is changed
func SendNotify(configuration *Settings, domain, currentIP string) error {
// SendTelegramNotify sends notify if IP is changed
func SendTelegramNotify(configuration *Settings, domain, currentIP string) error {
if !configuration.Notify.Telegram.Enabled {
return nil
}
if configuration.Notify.Telegram.BotApiKey == "" {
return errors.New("bot api key cannot be empty")
}
if configuration.Notify.Telegram.ChatId == "" {
return errors.New("chat id cannot be empty")
}
client := GetHttpClient(configuration, configuration.Notify.Telegram.UseProxy)
tpl := configuration.Notify.Telegram.MsgTemplate
if tpl == "" {
tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated"
}
msg := buildTemplate(currentIP, domain, tpl)
reqURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s",
configuration.Notify.Telegram.BotApiKey,
configuration.Notify.Telegram.ChatId,
msg)
var response *http.Response
var err error
response, err = client.Get(reqURL)
if err != nil {
return err
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
type ResponseParameters struct {
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
RetryAfter int `json:"retry_after"` // optional
}
type APIResponse struct {
Ok bool `json:"ok"`
Result json.RawMessage `json:"result"`
ErrorCode int `json:"error_code"`
Description string `json:"description"`
Parameters *ResponseParameters `json:"parameters"`
}
var resp APIResponse
err = json.Unmarshal(body, &resp)
if err != nil {
fmt.Println("error:", err)
return errors.New("failed to parse response")
}
if !resp.Ok {
return errors.New(resp.Description)
}
return nil
}
// SendMailNotify sends mail notify if IP is changed
func SendMailNotify(configuration *Settings, domain, currentIP string) error {
if !configuration.Notify.Mail.Enabled {
return nil
}
log.Print("Sending notification to:", configuration.Notify.Mail.SendTo)
m := gomail.NewMessage()
m.SetHeader("From", configuration.Notify.SMTPUsername)
m.SetHeader("To", configuration.Notify.SendTo)
m.SetHeader("From", configuration.Notify.Mail.SMTPUsername)
m.SetHeader("To", configuration.Notify.Mail.SendTo)
m.SetHeader("Subject", "GoDNS Notification")
log.Println("currentIP:", currentIP)
log.Println("domain:", domain)
m.SetBody("text/html", buildTemplate(currentIP, domain))
m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate))
d := gomail.NewPlainDialer(configuration.Notify.SMTPServer, configuration.Notify.SMTPPort, configuration.Notify.SMTPUsername, configuration.Notify.SMTPPassword)
d := gomail.NewDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword)
// Send the email config by sendlist .
if err := d.DialAndSend(m); err != nil {
log.Println("Send email notification with error:", err.Error())
return err
}
return nil
}
func buildTemplate(currentIP, domain string) string {
// SendSlack sends slack if IP is changed
func SendSlackNotify(configuration *Settings, domain, currentIP string) error {
if !configuration.Notify.Slack.Enabled {
return nil
}
if configuration.Notify.Slack.BotApiToken == "" {
return errors.New("bot api token cannot be empty")
}
if configuration.Notify.Slack.Channel == "" {
return errors.New("channel cannot be empty")
}
client := GetHttpClient(configuration, configuration.Notify.Slack.UseProxy)
tpl := configuration.Notify.Slack.MsgTemplate
if tpl == "" {
tpl = "_Your IP address is changed to_\n\n*{{ .CurrentIP }}*\n\nDomain *{{ .Domain }}* is updated"
}
msg := buildTemplate(currentIP, domain, tpl)
var response *http.Response
var err error
formData := url.Values{
"token": {configuration.Notify.Slack.BotApiToken},
"channel": {configuration.Notify.Slack.Channel},
"text": {msg},
}
response, err = client.PostForm("https://slack.com/api/chat.postMessage", formData)
if err != nil {
return err
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
type ResponseParameters struct {
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
RetryAfter int `json:"retry_after"` // optional
}
type APIResponse struct {
Ok bool `json:"ok"`
Result json.RawMessage `json:"result"`
ErrorCode int `json:"error_code"`
Description string `json:"description"`
Parameters *ResponseParameters `json:"parameters"`
}
var resp APIResponse
err = json.Unmarshal(body, &resp)
if err != nil {
fmt.Println("error:", err)
return errors.New("failed to parse response")
}
if !resp.Ok {
return errors.New(resp.Description)
}
return nil
}
// SendNotify sends notify if IP is changed
func SendNotify(configuration *Settings, domain, currentIP string) error {
err := SendTelegramNotify(configuration, domain, currentIP)
if err != nil {
log.Println("Send telegram notification with error:", err.Error())
}
err = SendMailNotify(configuration, domain, currentIP)
if err != nil {
log.Println("Send email notification with error:", err.Error())
}
err = SendSlackNotify(configuration, domain, currentIP)
if err != nil {
log.Println("Send slack notification with error:", err.Error())
}
return nil
}
func buildTemplate(currentIP, domain string, tplsrc string) string {
t := template.New("notification template")
if _, err := t.Parse(mailTemplate); err != nil {
if _, err := t.Parse(tplsrc); err != nil {
log.Println("Failed to parse template")
return ""
}
@ -273,3 +429,34 @@ func buildTemplate(currentIP, domain string) string {
return tpl.String()
}
// ResolveDNS will query DNS for a given hostname.
func ResolveDNS(hostname, resolver, ipType string) (string, error) {
var dnsType uint16
if ipType == "" || strings.ToUpper(ipType) == IPV4 {
dnsType = dns.TypeA
} else {
dnsType = dns.TypeAAAA
}
// If no DNS server is set in config file, falls back to default resolver.
if resolver == "" {
dnsAdress, err := net.LookupHost(hostname)
if err != nil {
return "<nil>", err
}
return dnsAdress[0], nil
}
res := dnsResolver.New([]string{resolver})
// In case of i/o timeout
res.RetryTimes = 5
ip, err := res.LookupHost(hostname, dnsType)
if err != nil {
return "<nil>", err
}
return ip[0].String(), nil
}

View File

@ -5,7 +5,7 @@ import (
)
func TestGetCurrentIP(t *testing.T) {
conf := &Settings{IPUrl: "http://members.3322.org/dyndns/getip"}
conf := &Settings{IPUrl: "https://myip.biturl.top"}
ip, _ := GetCurrentIP(conf)
if ip == "" {
@ -13,13 +13,6 @@ func TestGetCurrentIP(t *testing.T) {
} else {
t.Log("IP is:" + ip)
}
conf = &Settings{Socks5Proxy: "localhost:8899", IPUrl: "http://members.3322.org/dyndns/getip"}
ip, err := GetCurrentIP(conf)
if ip != "" && err == nil {
t.Error("should return error")
}
}
func TestCheckSettings(t *testing.T) {