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

Compare commits

...

279 Commits
V1.2 ... master

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
e726e2da5f
update README 2020-02-05 10:49:07 +08:00
Timothy
14bce004f6
Merge pull request #51 from TimothyYe/cf
add API token support for Cloudflare
2020-02-05 10:46:21 +08:00
Timothy
e9ec737610
add API token support for Cloudflare 2020-02-05 10:38:29 +08:00
Timothy
6ec63e4499
add IPV6 support 2020-01-30 23:54:47 +08:00
Timothy
85c21e6035
Update go.yml 2020-01-16 10:40:49 +08:00
Timothy
c9c128f2df
Update go.yml 2020-01-16 10:34:59 +08:00
Timothy
cd983cff57
Update template.go 2020-01-16 10:24:13 +08:00
Timothy
bbb71111a3
update mail template 2020-01-10 14:45:22 +08:00
Timothy
2ffed6e960
remove travis CI 2020-01-10 14:34:42 +08:00
Timothy
857127de8a
Merge pull request #48 from test1943/patch-1
Preserve TTL value.
2019-09-26 10:10:51 +08:00
test1943
ce60904eda
Preserve TTL value.
Right now ttl value is overwritten to default value every time.
2019-09-26 01:42:02 +08:00
Timothy
5182b91102 update gomod 2019-09-05 10:31:57 +08:00
Timothy
9af20940a8 Merge branch 'master' of github.com:TimothyYe/godns 2019-09-04 10:33:34 +08:00
Timothy
18f23c4dcf fix basic auth issue 2019-09-04 10:33:25 +08:00
Timothy
7c9f66ebfa
Update go.yml 2019-09-02 23:32:41 +08:00
Timothy
60aa9aac27 Merge branch 'master' of github.com:TimothyYe/godns 2019-09-02 23:29:40 +08:00
Timothy
917111522a
Update go.yml 2019-09-02 23:28:47 +08:00
Timothy
7ea034976a add customized user-agent 2019-09-02 19:05:35 +08:00
Timothy
112b773ffb fix issue 2019-08-30 11:56:56 +08:00
Timothy
4f71fbbcc1 fix misspell issue 2019-08-06 10:50:17 +08:00
Timothy
91ca1d9552 remove symbol & debug info 2019-08-02 15:01:15 +08:00
Timothy
025a5c0bbb refactor handlers 2019-07-30 14:35:13 +08:00
Timothy
7720a92ad6 update Makefile 2019-07-30 10:31:17 +08:00
Timothy
91d89d82ce fix DuckDNS handler 2019-07-30 10:24:14 +08:00
Timothy
dccfcb76bb update README 2019-07-30 10:17:40 +08:00
Timothy
9bfe67f3b4
Update README.md 2019-07-29 23:54:45 +08:00
Timothy
95529f6de6 add DuckDNS support 2019-07-29 23:53:08 +08:00
Timothy
e81e664d5e
Create FUNDING.yml 2019-07-27 01:35:38 +08:00
Timothy
515711cac8
Update README.md 2019-07-27 00:44:55 +08:00
Timothy
484468bf1a
Merge pull request #40 from fabianskibr/change-windows-service
Update README.md
2019-07-25 12:22:21 +08:00
fabianskibr
3fa582031c
Update README.md 2019-07-24 13:34:43 -03:00
Timothy
f9ebb41c80 Merge branch 'master' of github.com:TimothyYe/godns 2019-05-24 16:40:53 +08:00
Timothy
4a72cf96e6 update README 2019-05-24 16:40:46 +08:00
Timothy
af7e81ad96
Update README.md 2019-05-23 10:05:42 +08:00
Timothy
13e7548464
Update README.md 2019-05-23 10:05:02 +08:00
Timothy
a27a941ea9
Update README.md 2019-05-23 10:04:16 +08:00
Timothy
2aa29a7c8b
Update README.md 2019-05-23 10:03:18 +08:00
Timothy
6899356881 update README 2019-05-23 09:55:59 +08:00
Timothy
6eba9cd2d7 Merge branch 'google' 2019-05-23 09:52:02 +08:00
Timothy
81a561e076
Merge pull request #38 from huwenchao/master
chore: make the dnspod handler error log and readme.md more specificlly
2019-05-13 17:08:19 +08:00
huwenchao
af9a1bb0da chore: make the dns handler error log and readme.md more specificlly 2019-05-11 17:07:43 +08:00
Timothy
d1019c5cc1 Set theme jekyll-theme-cayman 2019-05-02 13:39:52 +08:00
TimothyYe
1d49bcd173 add google handler 2019-04-24 13:10:39 +08:00
Timothy
1bf8e22d25
Update README.md 2019-04-22 10:18:56 +08:00
Timothy
39aba3508a
Update README.md 2019-04-22 10:16:04 +08:00
Timothy
39bc2dc98b
Update README.md 2019-04-22 10:08:06 +08:00
TimothyYe
530dff39cf update Makefile 2019-04-21 17:47:06 +08:00
TimothyYe
6a7bcaf5d5 make interval configurable 2019-04-21 17:19:57 +08:00
TimothyYe
04134b20a4 make interval configurable 2019-04-21 17:18:05 +08:00
TimothyYe
50c69bf548 update README 2019-04-18 13:30:00 +08:00
Timothy
cb1ce2a8ba
Update config_sample.json 2019-04-18 12:42:42 +08:00
Timothy
9df305ac8c
Update README.md 2019-04-02 10:42:52 +08:00
Timothy
329cf4095b
Update README.md 2019-04-02 10:42:00 +08:00
TimothyYe
4a3b2216d3 fix lint warnings 2019-02-27 14:10:51 +08:00
TimothyYe
7e30cdf6b2 update travis ci config 2019-02-27 13:52:30 +08:00
TimothyYe
5fd782d540 update go mod file 2019-02-27 11:23:17 +08:00
TimothyYe
1e9f43af49 Merge branch 'master' of github.com:TimothyYe/godns 2019-01-31 11:16:47 +08:00
TimothyYe
bb4c5f3192 fix issue #35 2019-01-31 11:16:03 +08:00
Timothy
706771ae79
Update README.md 2019-01-24 10:06:26 +08:00
TimothyYe
dc376d91b4 fix lint warnings 2019-01-22 20:35:45 +08:00
TimothyYe
57b3c4c79d add AliDNS support 2019-01-22 20:18:19 +08:00
TimothyYe
66187e0269 send mail notification 2019-01-21 11:11:07 +08:00
TimothyYe
304d3cac50 update test case 2019-01-20 15:09:53 +08:00
TimothyYe
6455f324cd update test case 2019-01-20 14:59:17 +08:00
TimothyYe
0fdb8af727 only update sub domain 2019-01-20 14:44:30 +08:00
TimothyYe
413c2dc983 update travis ci config 2019-01-16 17:58:07 +08:00
TimothyYe
fd85cd8896 Merge branch 'master' of github.com:TimothyYe/godns 2019-01-16 17:50:16 +08:00
TimothyYe
7a04b71b16 remove vendor, use go mod 2019-01-16 17:49:59 +08:00
Timothy
8fa782175c
Update README.md 2018-12-26 13:52:18 +08:00
TimothyYe
6d66016635 update comments 2018-12-07 16:50:56 +08:00
TimothyYe
4c82260c4d format code and fix the go lint warnings 2018-12-07 16:04:59 +08:00
Timothy
fb5c679bf7
Merge pull request #29 from SadPencil/master
New features
2018-12-07 15:56:06 +08:00
Sad Pencil
4a1a1a391b update document for getting an IP from an interface and running as a Windows service 2018-12-07 03:22:53 +08:00
Sad Pencil
851e2d7e46 support for getting an IP from an interface, and skip DNS record update if IP address is the same with last one 2018-12-07 02:50:27 +08:00
TimothyYe
78f52ebe54 Merge branch 'master' of github.com:TimothyYe/godns 2018-11-14 10:06:48 +08:00
TimothyYe
059c1530b0 update Makefile 2018-11-14 10:06:35 +08:00
Timothy
cc83af7abb
Update README.md 2018-10-29 10:47:46 +04:00
Timothy
015eb5bb71
Merge pull request #26 from kerma/master
Support for Cloudflare
2018-10-14 22:03:26 +08:00
Kerma
3598247d64 Fix tests and more linting errors 2018-10-12 20:49:03 +03:00
Kerma
46738d5bc6 Linter fixes 2018-10-12 19:45:04 +03:00
Kerma
fa5fdc7c8c Add Cloudflare handler 2018-10-12 19:03:17 +03:00
Timothy
58799a5892
Update README.md 2018-09-04 11:38:16 +08:00
TimothyYe
a500ad329f for V1.5.3 2018-09-04 11:30:59 +08:00
Timothy
6daac3746c
Update README.md 2018-09-03 22:13:18 +08:00
Timothy
11b8edb1b0
Update README.md 2018-09-03 22:12:55 +08:00
Timothy
ecc8d45eb2
Update README.md 2018-08-24 18:02:40 +08:00
Timothy
453197d26c
Update README.md 2018-08-24 18:01:08 +08:00
Timothy
fdeed35438
Update README.md 2018-08-24 18:00:16 +08:00
TimothyYe
4b3d9b1c6b update travis ci config, use go dep 2018-08-24 14:00:20 +08:00
Timothy
99ccb5f265
Merge pull request #24 from hguandl/master
MIPS can be compiled normally after Go 1.10. Branch mips32 becomes deprecated
2018-07-04 11:26:40 +08:00
hguandl
bef31d48d1 Update .gitignore 2018-07-04 10:58:54 +08:00
hguandl
ddeac43bf6 Add golang v1.10.x 2018-07-04 10:56:05 +08:00
hguandl
5344c3c393 Merge remote-tracking branch 'upstream/master' 2018-07-04 10:52:50 +08:00
Timothy
b919752145
Update utils_test.go 2018-06-16 15:49:16 +08:00
Timothy
7d0ab96fb5
Update utils_test.go 2018-06-16 14:19:14 +08:00
Timothy Ye
7d7a616aea fix issue #22 2018-06-15 22:16:21 +08:00
Timothy
cc51388579 fix issue #20 2018-05-14 11:09:24 +08:00
Timothy
4fede06128 update README file 2018-04-19 10:57:36 +08:00
Timothy
0b7bb3d6b9 update README file 2018-04-19 10:56:45 +08:00
Timothy Ye
f428da55c8 use stdout as default log output. 2018-04-17 20:42:17 +08:00
Timothy
2a23abbcbd
Update README.md 2017-12-06 11:21:04 +08:00
Timothy
ad24376fcc remove unused code 2017-11-06 18:13:22 +08:00
Timothy
733b297553 add snapshots 2017-11-06 18:04:44 +08:00
Timothy
d4bf2ca8d1 fix golint warnings 2017-11-06 17:53:11 +08:00
Timothy
8e5314ff09 update config sample file 2017-11-06 17:37:30 +08:00
Timothy
50a5a8f308 remove debug info 2017-11-06 17:27:26 +08:00
Timothy
0155dee427 compile template 2017-11-06 17:26:40 +08:00
Timothy
7a65d4e0ea refactor send notify logic 2017-11-06 16:48:43 +08:00
Timothy
4c4f34c55b send mail notify if IP is changed 2017-11-06 11:32:27 +08:00
Timothy
ccbdf4e31d add notify model, add send notify method 2017-11-02 16:45:50 +08:00
Timothy
9d6efbc806 update README file 2017-11-02 14:50:43 +08:00
Timothy
281ede6071 remove log size & log num 2017-11-02 14:49:49 +08:00
Timothy
549a98a48f update README file 2017-10-30 15:07:55 +08:00
Timothy
eef6c9c1aa move Dockerfile 2017-10-30 15:04:10 +08:00
Timothy
ebb174f604 fix golint warnings 2017-10-30 14:53:42 +08:00
Timothy
cd798db786 Merge branch 'master' of github.com:TimothyYe/godns 2017-10-30 14:44:54 +08:00
Timothy
661afa0e0d update gitignore file 2017-10-30 14:44:36 +08:00
Timothy
2d935d704e
Update .travis.yml 2017-10-30 01:39:59 -05:00
Timothy
9020971add bug fix for sleeping 2017-10-30 14:33:30 +08:00
Timothy
4b69c69f34 update Makefile 2017-10-30 14:25:09 +08:00
Timothy
49d1478394 update release version 2017-10-30 14:21:03 +08:00
Timothy
bfb7961229 move Makefile, add program logo 2017-10-30 14:17:18 +08:00
Timothy
f8dac9d4e9 add test case 2017-10-30 11:13:46 +08:00
Timothy
d48d204af6 Merge branch 'master' of github.com:TimothyYe/godns 2017-10-30 11:04:45 +08:00
Timothy
508aa7321a fix #14 Avoid too much api call times by creating a file to store last IP address 2017-10-30 11:04:25 +08:00
Timothy
bccb29ff50 Update README.md 2017-10-27 02:34:32 -05:00
Timothy
0b30f52e71 add cover.run badge 2017-10-27 14:59:52 +08:00
Timothy
732e97a294 add test case 2017-10-27 14:50:35 +08:00
Timothy
d0dfa698b0 add test case 2017-10-27 14:47:36 +08:00
Timothy
377d91d969 add test case 2017-10-27 14:44:44 +08:00
Timothy
62ac21417e remove unused code, add test cases 2017-10-27 14:34:17 +08:00
Timothy
fad5b0fa20 Update README.md 2017-10-27 01:12:12 -05:00
Timothy
18fdb9818f remove logger, use go's official log package 2017-10-27 14:07:43 +08:00
Timothy
3c91676188 update logger 2017-10-27 12:05:33 +08:00
Timothy
545a546b7b remove unused code 2017-10-27 12:03:52 +08:00
Timothy
dbf4bf15bd fix golint warnings 2017-10-27 12:00:42 +08:00
Timothy
4472bc9e0e fix golint warnings 2017-10-27 11:59:57 +08:00
Timothy
e0bde61c5c fix golint warnings 2017-10-27 11:57:54 +08:00
Timothy
d4875c1064 Update .travis.yml 2017-10-26 22:42:25 -05:00
Timothy
4280f86362 remove unused code 2017-10-27 11:39:44 +08:00
Timothy
2fd1736ac6 add cmd directory 2017-10-27 11:37:31 +08:00
Timothy
4e1084fada Update .travis.yml 2017-10-26 22:30:52 -05:00
Timothy
653ac6bcd8 Update .travis.yml 2017-10-26 22:29:49 -05:00
Timothy
40576303da Merge branch 'master' of github.com:TimothyYe/godns 2017-10-27 11:26:48 +08:00
Timothy
7bf7ae2caa update testcase 2017-10-27 11:26:36 +08:00
Timothy
35bc13e10e Update README.md 2017-10-26 22:17:03 -05:00
Timothy
790c12ae48 Update README.md 2017-10-26 22:16:09 -05:00
Timothy
05aca524ee update testcase 2017-10-27 11:14:22 +08:00
Timothy
4bca24fe97 refactor code struct 2017-10-27 11:12:13 +08:00
Timothy
d46fefa18b format code 2017-10-27 10:22:12 +08:00
Timothy
723f4d3322 fix golint warnings 2017-10-27 09:59:08 +08:00
Timothy
622b77d431 fix golint warnings 2017-10-26 18:11:38 +08:00
Timothy Ye
d3f82cffe6 Merge pull request #12 from georgetso/feature/chan_blocking
block main goroutine with chan communication
2017-10-14 15:11:48 +08:00
George
ed5b504e5b block main goroutine with chan communication 2017-10-14 11:38:56 +08:00
Timothy Ye
c128dd84db Update README.md 2017-10-10 22:05:30 +08:00
Timothy Ye
5b907f081a Update README.md 2017-10-10 00:37:02 -05:00
Timothy
2d258f4eab Merge branch 'master' of github.com:TimothyYe/godns 2017-10-10 13:27:47 +08:00
Timothy
b5397fb1f3 add arm platform support 2017-10-10 13:27:37 +08:00
Timothy Ye
026200fae9 Update README.md 2017-10-10 00:24:38 -05:00
Timothy Ye
8743fce090 Update README.md 2017-10-09 21:05:10 -05:00
Timothy Ye
57f8ad4445 Update README.md 2017-10-09 21:03:41 -05:00
Timothy Ye
6559089eae Update README.md 2017-10-09 21:02:06 -05:00
Timothy Ye
edac8f7f22 Update README.md 2017-10-09 21:01:47 -05:00
Timothy Ye
bbf787e6e3 Update README.md 2017-10-09 21:01:19 -05:00
Timothy
ceb8ee8548 refactor update IP logic 2017-10-10 09:36:44 +08:00
Timothy
564ceee22d add SOCKS5 proxy support. 2017-10-09 20:27:43 +08:00
Timothy
9b458a0d1d Integrate with socks5 proxy 2017-10-09 20:23:24 +08:00
Timothy Ye
7e9bd7f532 Update README.md 2017-10-09 04:48:09 -05:00
Timothy Ye
cd6f969bc7 Update README.md 2017-10-09 04:43:12 -05:00
Timothy Ye
39400f628a Update README.md 2017-10-09 04:35:41 -05:00
Timothy Ye
0bcfd39f41 Update README.md 2017-10-09 04:34:34 -05:00
Timothy Ye
40a2b9bc9a Update README.md 2017-10-09 04:24:57 -05:00
Timothy
72fc565a96 add snapshots 2017-10-09 17:10:34 +08:00
Timothy
ce90fe41ef implement HE dns handler 2017-10-09 17:01:39 +08:00
Timothy
c62bb219cc update config sample file 2017-10-09 16:28:45 +08:00
Timothy
cd3e7e3cb0 refactor DNSPod handler 2017-10-09 16:27:34 +08:00
Timothy
fd0424630a rename files 2017-10-09 16:05:13 +08:00
Timothy
9936fbc4e3 integrate with he dns 2017-10-09 15:59:09 +08:00
Timothy Ye
a4dbb864bf Update README.md 2017-10-08 22:21:25 -05:00
Timothy Ye
7005b5f17e Delete .gitlab-ci.yml 2017-09-19 14:43:41 +08:00
Timothy Ye
2b60374ac0 Update .travis.yml 2017-09-15 16:55:03 +08:00
Timothy Ye
d6fcfcd4f5 Update README.md 2017-09-15 16:32:11 +08:00
Timothy Ye
e0dd532cfe Update README.md 2017-09-15 16:30:28 +08:00
hguandl
0723452a66 ignore .DS_Store 2017-08-16 00:30:41 +08:00
43 changed files with 3294 additions and 853 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://www.paypal.me/timothyye

20
.github/workflows/go.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Go
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.15.7
uses: actions/setup-go@v1
with:
go-version: 1.15.7
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Build
run: cd cmd/godns && go build -v .

9
.gitignore vendored
View File

@ -1,3 +1,6 @@
# System files on macOS
.DS_Store
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
@ -26,9 +29,13 @@ config.json
*.swp
*.gz
godns
godns.exe
config.json
cmd/godns/config.json
vendor/*
/.idea
/godns.iml
/godns.ipr
/godns.iws
.current_ip
.DS_Store

View File

@ -1,24 +0,0 @@
before_script:
- export GOPATH=$(pwd)
- export GOBIN=$GOPATH/bin
stages:
- build
- test
build-my-project:
image: golang:1.6.2
stage: build
script:
- mkdir $GOPATH/bin
- go get
- go build
test-my-project:
image: golang:1.6.2
stage: test
script:
- mkdir $GOPATH/bin
- go get
- go build
- go test

View File

@ -1,11 +0,0 @@
language: go
go:
- 1.7
- 1.8
install:
go get -v
script:
- cp ./config_sample.json ./config.json
- go test -v ./...

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

View File

@ -1,9 +1,9 @@
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"]
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,28 +2,43 @@
BINARY=godns
# Builds the project
build:
go build -o ${BINARY}
GO111MODULE=on go build -ldflags "-X main.Version=${VERSION}" -o ${BINARY} cmd/godns/godns.go
# Installs our project: copies binaries
install:
go install
GO111MODULE=on go install
image:
# Build docker image
go clean
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
go build
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 go build
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 -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 -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 go build
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
go clean
make image
# Cleans our projects: deletes binaries
clean:
go clean
rm -rf ./godns
rm -rf ./godns.exe
rm -rf *.gz
.PHONY: clean build

622
README.md
View File

@ -7,113 +7,613 @@
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
```
[![Release][7]][8] [![MIT licensed][9]][10] [![Build Status][1]][2] [![Downloads][5]][6] [![Docker][3]][4] [![Go Report Card][11]][12]
[![Apache licensed][9]][10] [![Docker][3]][4] [![Go Report Card][11]][12] [![Cover.Run][15]][16] [![GoDoc][13]][14]
[1]: https://travis-ci.org/TimothyYe/godns.svg?branch=master
[2]: https://travis-ci.org/TimothyYe/godns
[3]: https://images.microbadger.com/badges/image/timothyye/godns.svg
[4]: https://microbadger.com/images/timothyye/godns
[5]: https://img.shields.io/badge/downloads-1.95MB-brightgreen.svg
[6]: https://github.com/TimothyYe/godns/releases
[7]: https://img.shields.io/badge/release-v1.2-brightgreen.svg
[8]: https://github.com/TimothyYe/godns/releases
[9]: https://img.shields.io/badge/license-Apache-blue.svg
[10]: LICENSE
[11]: https://goreportcard.com/badge/github.com/timothyye/godns
[12]: https://goreportcard.com/report/github.com/timothyye/godns
[13]: https://godoc.org/github.com/TimothyYe/godns?status.svg
[14]: https://godoc.org/github.com/TimothyYe/godns
[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) 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.
## MIPS32 platform
---
- [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)
For MIPS32 platform, please checkout the [mips32](https://github.com/TimothyYe/godns/tree/mips32) branch, this branch is contributed by [hguandl](https://github.com/hguandl), in this branch, the support for mips32 is added, which means it could run properly on Openwrt and LEDE.
---
## Supported DNS Providers
## Pre-condition
| 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: |
* GoDNS relies on [DNSPod](http://dnspod.cn) and its API.
[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
* To use GoDNS, you need a domain hosted on [DNSPod](http://dnspod.cn).
Tip: You can follow this [issue](https://github.com/TimothyYe/godns/issues/76) to view the current status of DDNS for root domains.
## Build it
## Supported Platforms
### Get & build it from source code
* Linux
* MacOS
* ARM Linux (Raspberry Pi, etc.)
* Windows
* MIPS32 platform
* Get source code from Github:
To compile binaries for MIPS (mips or mipsle), run:
```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
git clone https://github.com/timothyye/godns.git
```
* Go into the godns directory, get related library and then build it:
```bash
cd godns
go get
go build
cd cmd/godns # go to the GoDNS directory
go get -v # get dependencies
go build # build
```
## Get help
You can also download a compiled binary from the [releases](https://github.com/TimothyYe/godns/releases).
## Usage
Print usage/help by running:
```bash
$ ./godns -h
Usage of ./godns:
-c string
Specify a config file (default "./config.json")
-d Run it as docker mode
-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 domain/sub-domain info, username and password of DNSPod account.
* Configure log file path, max size of log file, max count of log file.
* Configure user id, group id for safety.
* Save it in the same directory of GoDNS, or use -c=your_conf_path command.
### Overview
## Run it as a daemon manually
* 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
### Configuration properties
* `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.
<details>
<summary>Using email & Global API Key</summary>
```json
{
"provider": "Cloudflare",
"email": "you@example.com",
"password": "Global 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",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
<details>
<summary>Using the API Token</summary>
```json
{
"provider": "Cloudflare",
"login_token": "API Token",
"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",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
#### 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",
"login_token": "your_id,your_token",
"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,
"socks5_proxy": ""
}
```
</details>
#### 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",
"email": "Your_Username",
"password": "Your_Password",
"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",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
#### 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",
"email": "AccessKeyID",
"password": "AccessKeySecret",
"login_token": "",
"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",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
#### DuckDNS
For DuckDNS, only need to provide the `token`, config 1 default domain & subdomains.
<details>
<summary>Example</summary>
```json
{
"provider": "DuckDNS",
"password": "",
"login_token": "3aaaaaaaa-f411-4198-a5dc-8381cac61b87",
"domains": [
{
"domain_name": "www.duckdns.org",
"sub_domains": [
"myname"
]
}
],
"resolver": "8.8.8.8",
"ip_url": "https://myip.biturl.top",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
#### 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",
"password": "YourPassword",
"login_token": "",
"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",
"interval": 300,
"socks5_proxy": ""
}
```
</details>
<details>
<summary>Provider configuration</summary>
Add a new "A record" and make sure that "Enable entry for dynamic dns" is checked:
<img src="./snapshots/he1.png" width="640" />
Fill in your own DDNS key or generate a random DDNS key for this new created "A record":
<img src="./snapshots/he2.png" width="640" />
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>
### Notifications
GoDNS can send a notification each time the IP changes.
#### Email
Emails are sent over [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol). Update your configuration with the following snippet:
```json
"notify": {
"mail": {
"enabled": true,
"smtp_server": "smtp.example.com",
"smtp_username": "user",
"smtp_password": "password",
"smtp_port": 25,
"send_to": "my_mail@example.com"
}
}
```
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" />
#### Telegram
To receive a [Telegram](https://telegram.org/) message each time the IP changes, update your configuration with the following snippet:
```json
"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
},
}
```
The `message_template` property supports [markdown](https://www.markdownguide.org). New lines needs to be escaped with `%0A`.
#### 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:
```
nssm install YOURSERVICENAME
```
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.
4. The service will now start along Windows.
Note: you can uninstall the service by running:
```
nssm remove YOURSERVICENAME
```
## Run it in docker
## Special Thanks
Now godns supports to run in docker.
<img src="https://i.imgur.com/xhe5RLZ.jpg" width="80px" align="right" />
* Pull godns image from docker hub:
```bash
docker pull timothyye/godns:1.2
```
Thanks JetBrains for sponsoring this project with [free open source license](https://www.jetbrains.com/community/opensource/).
* Run godns in container and pass config parameters to it via enviroment variables:
```bash
docker run -d --name godns --restart=always \
-v /path/to/config.json:/usr/local/godns/config.json timothyye/godns:1.2
```
## Enjoy it!
> I like GoLand, it is an amazing and productive tool.

1
_config.yml Normal file
View File

@ -0,0 +1 @@
theme: jekyll-theme-cayman

70
cmd/godns/godns.go Normal file
View File

@ -0,0 +1,70 @@
package main
import (
"flag"
"fmt"
"os"
"log"
"github.com/TimothyYe/godns"
"github.com/TimothyYe/godns/handler"
"github.com/fatih/color"
)
var (
configuration godns.Settings
optConf = flag.String("c", "./config.json", "Specify a config file")
optHelp = flag.Bool("h", false, "Show help")
// Version is current version of GoDNS
Version = "0.1"
)
func main() {
flag.Parse()
if *optHelp {
color.Cyan(godns.Logo, Version)
flag.Usage()
return
}
// Load settings from configurations file
if err := godns.LoadSettings(*optConf, &configuration); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
if err := godns.CheckSettings(&configuration); err != nil {
fmt.Println("Settings is invalid! ", err.Error())
os.Exit(1)
}
// Init log settings
log.SetPrefix("[GoDNS] ")
log.Println("GoDNS started, entering main loop...")
dnsLoop()
}
func dnsLoop() {
panicChan := make(chan godns.Domain)
log.Println("Creating DNS handler with provider:", configuration.Provider)
h := handler.CreateHandler(configuration.Provider)
h.SetConfiguration(&configuration)
for i := range configuration.Domains {
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 h.DomainLoop(&failDomain, panicChan)
panicCount++
if panicCount >= godns.PanicMax {
os.Exit(1)
}
}
}

View File

@ -1,18 +1,47 @@
{
"email": "example@gmail.com",
"provider": "DNSPod",
"password": "",
"login_token": "",
"domains": [{
"domains": [
{
"domain_name": "example.com",
"sub_domains": ["www","test"]
},{
"sub_domains": [
"www",
"test"
]
},
{
"domain_name": "example2.com",
"sub_domains": ["www","test"]
"sub_domains": [
"www",
"test"
]
}
],
"ip_url": "http://members.3322.org/dyndns/getip",
"log_path": "./godns.log",
"log_size": 16,
"log_num": 3,
"socks5_proxy": ""
"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": {
"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": ""
}
}
}

View File

@ -1,214 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/bitly/go-simplejson"
"golang.org/x/net/proxy"
)
func getCurrentIP(url string) (string, error) {
response, err := http.Get(url)
if err != nil {
log.Println("Cannot get IP...")
return "", err
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
return string(body), nil
}
func generateHeader(content url.Values) url.Values {
header := url.Values{}
if configuration.LoginToken != "" {
header.Add("login_token", configuration.LoginToken)
} else {
header.Add("login_email", configuration.Email)
header.Add("login_password", configuration.Password)
}
header.Add("format", "json")
header.Add("lang", "en")
header.Add("error_on_empty", "no")
if content != nil {
for k := range content {
header.Add(k, content.Get(k))
}
}
return header
}
func apiVersion() {
postData("/Info.Version", nil)
}
func getDomain(name string) int64 {
var ret int64
values := url.Values{}
values.Add("type", "all")
values.Add("offset", "0")
values.Add("length", "20")
response, err := postData("/Domain.List", values)
if err != nil {
log.Println("Failed to get domain list...")
return -1
}
sjson, parseErr := simplejson.NewJson([]byte(response))
if parseErr != nil {
log.Println(parseErr)
return -1
}
if sjson.Get("status").Get("code").MustString() == "1" {
domains, _ := sjson.Get("domains").Array()
for _, d := range domains {
m := d.(map[string]interface{})
if m["name"] == name {
id := m["id"]
switch t := id.(type) {
case json.Number:
ret, _ = t.Int64()
}
break
}
}
if len(domains) == 0 {
log.Println("domains slice is empty.")
}
} else {
log.Println("get_domain:status code:", sjson.Get("status").Get("code").MustString())
}
return ret
}
func 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))
value.Add("offset", "0")
value.Add("length", "1")
value.Add("sub_domain", name)
response, err := postData("/Record.List", value)
if err != nil {
log.Println("Failed to get domain list")
return "", ""
}
sjson, parseErr := simplejson.NewJson([]byte(response))
if parseErr != nil {
log.Println(parseErr)
return "", ""
}
if sjson.Get("status").Get("code").MustString() == "1" {
records, _ := sjson.Get("records").Array()
for _, d := range records {
m := d.(map[string]interface{})
if m["name"] == name {
ret = m["id"].(string)
ip = m["value"].(string)
break
}
}
if len(records) == 0 {
log.Println("records slice is empty.")
}
} else {
log.Println("get_subdomain:status code:", sjson.Get("status").Get("code").MustString())
}
return ret, ip
}
func updateIP(domainID int64, subDomainID string, subDomainName string, ip string) {
value := url.Values{}
value.Add("domain_id", strconv.FormatInt(domainID, 10))
value.Add("record_id", subDomainID)
value.Add("sub_domain", subDomainName)
value.Add("record_type", "A")
value.Add("record_line", "默认")
value.Add("value", ip)
response, err := postData("/Record.Modify", value)
if err != nil {
log.Println("Failed to update record to new IP!")
log.Println(err)
return
}
sjson, parseErr := simplejson.NewJson([]byte(response))
if parseErr != nil {
log.Println(parseErr)
return
}
if sjson.Get("status").Get("code").MustString() == "1" {
log.Println("New IP updated!")
}
}
func postData(url string, content url.Values) (string, error) {
client := &http.Client{}
if configuration.Socks5Proxy != "" {
log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
if err != nil {
fmt.Println("can't connect to the proxy:", err)
return "", err
}
httpTransport := &http.Transport{}
client.Transport = httpTransport
httpTransport.Dial = dialer.Dial
}
values := generateHeader(content)
req, _ := http.NewRequest("POST", "https://dnsapi.cn"+url, strings.NewReader(values.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", fmt.Sprintf("GoDNS/0.1 (%s)", configuration.Email))
response, err := client.Do(req)
if err != nil {
log.Println("Post failed...")
log.Println(err)
return "", err
}
defer response.Body.Close()
resp, _ := ioutil.ReadAll(response.Body)
return string(resp), nil
}

View File

@ -1,15 +0,0 @@
package main
import (
"testing"
)
func testGetCurrentIP(t *testing.T) {
ip, _ := getCurrentIP("http://members.3322.org/dyndns/getip")
if ip == "" {
t.Log("IP is empty...")
} else {
t.Log("IP is:" + ip)
}
}

6
glide.lock generated
View File

@ -1,6 +0,0 @@
hash: 1da678bb0eda2c40266e5014e089aa36ebfa0a360d20552a77ef77a00b2d1bfb
updated: 2016-05-25T15:08:31.725668117+08:00
imports:
- name: github.com/bitly/go-simplejson
version: aabad6e819789e569bd6aabf444c935aa9ba1e44
devImports: []

View File

@ -1,3 +0,0 @@
package: github.com/timothyye/godns
import:
- package: github.com/bitly/go-simplejson

18
go.mod Normal file
View File

@ -0,0 +1,18 @@
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
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
)
go 1.13

46
go.sum Normal file
View File

@ -0,0 +1,46 @@
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
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=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
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=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=

116
godns.go
View File

@ -1,116 +0,0 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"runtime/debug"
"strings"
"time"
)
const (
// PANIC_MAX is the max allowed panic times
PANIC_MAX = 5
// INTERVAL is minute
INTERVAL = 5
)
var (
configuration Settings
optConf = flag.String("c", "./config.json", "Specify a config file")
optHelp = flag.Bool("h", false, "Show help")
panicCount = 0
)
func main() {
flag.Parse()
if *optHelp {
flag.Usage()
return
}
//Load settings from configurations file
if err := LoadSettings(*optConf, &configuration); err != nil {
fmt.Println(err.Error())
log.Println(err.Error())
os.Exit(1)
}
if err := checkSettings(&configuration); err != nil {
log.Println("Settings is invalid! ", err.Error())
os.Exit(1)
}
if err := InitLogger(configuration.LogPath, configuration.LogSize, configuration.LogNum); err != nil {
log.Println("InitLogger error:", err.Error())
os.Exit(1)
}
dnsLoop()
}
func dnsLoop() {
for _, domain := range configuration.Domains {
go domainLoop(&domain)
}
select {}
}
func domainLoop(domain *Domain) {
defer func() {
if err := recover(); err != nil {
panicCount++
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
fmt.Println(identifyPanic())
log.Print(identifyPanic())
if panicCount < PANIC_MAX {
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
go domainLoop(domain)
} else {
os.Exit(1)
}
}
}()
for {
domainID := getDomain(domain.DomainName)
if domainID == -1 {
continue
}
currentIP, err := getCurrentIP(configuration.IPUrl)
if err != nil {
log.Println("get_currentIP:", err)
continue
}
log.Println("currentIp is:", currentIP)
for _, subDomain := range domain.SubDomains {
subDomainID, ip := getSubDomain(domainID, subDomain)
if subDomainID == "" || ip == "" {
log.Printf("domain: %s.%s subDomainID: %s ip: %s\n", subDomain, domain.DomainName, subDomainID, ip)
continue
}
//Continue to check the IP of sub-domain
if len(ip) > 0 && !strings.Contains(currentIP, ip) {
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
updateIP(domainID, subDomainID, subDomain, currentIP)
} else {
log.Printf("%s.%s Current IP is same as domain IP, no need to update...\n", subDomain, domain.DomainName)
}
}
//Interval is 5 minutes
time.Sleep(time.Minute * INTERVAL)
}
}

19
gometalinter.json Normal file
View File

@ -0,0 +1,19 @@
{
"Deadline": "2m",
"Enable": [
"goimports",
"golint",
"ineffassign",
"misspell",
"vet",
"varcheck",
"deadcode",
"structcheck"
],
"Exclude": [
"snapshots/",
"upstart/",
"systemd/"
],
"LineLength": 100
}

166
handler/alidns/alidns.go Normal file
View File

@ -0,0 +1,166 @@
package alidns
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"sync"
"time"
)
// AliDNS token
type AliDNS struct {
AccessKeyID string
AccessKeySecret string
}
var (
publicParm = map[string]string{
"AccessKeyId": "",
"Format": "JSON",
"Version": "2015-01-09",
"SignatureMethod": "HMAC-SHA1",
"Timestamp": "",
"SignatureVersion": "1.0",
"SignatureNonce": "",
}
baseURL = "http://alidns.aliyuncs.com/"
instance *AliDNS
once sync.Once
)
type domainRecordsResp struct {
RequestID string `json:"RequestId"`
TotalCount int
PageNumber int
PageSize int
DomainRecords domainRecords
}
type domainRecords struct {
Record []DomainRecord
}
// DomainRecord struct
type DomainRecord struct {
DomainName string
RecordID string `json:"RecordId"`
RR string
Type string
Value string
Line string
Priority int
TTL int
Status string
Locked bool
}
func getHTTPBody(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if resp.StatusCode == http.StatusOK {
return body, err
}
return nil, fmt.Errorf("status %d, Error:%s", resp.StatusCode, body)
}
// NewAliDNS function creates instance of AliDNS and return
func NewAliDNS(key, secret string) *AliDNS {
once.Do(func() {
instance = &AliDNS{
AccessKeyID: key,
AccessKeySecret: secret,
}
})
return instance
}
// GetDomainRecords gets all the doamin records according to input subdomain key
func (d *AliDNS) GetDomainRecords(domain, rr string) []DomainRecord {
resp := &domainRecordsResp{}
parms := map[string]string{
"Action": "DescribeSubDomainRecords",
"SubDomain": fmt.Sprintf("%s.%s", rr, domain),
}
urlPath := d.genRequestURL(parms)
body, err := getHTTPBody(urlPath)
if err != nil {
fmt.Printf("GetDomainRecords error.%+v\n", err)
} else {
if err := json.Unmarshal(body, resp); err != nil {
fmt.Printf("GetDomainRecords error. %+v\n", err)
return nil
}
return resp.DomainRecords.Record
}
return nil
}
// UpdateDomainRecord updates domain record
func (d *AliDNS) UpdateDomainRecord(r DomainRecord) error {
parms := map[string]string{
"Action": "UpdateDomainRecord",
"RecordId": r.RecordID,
"RR": r.RR,
"Type": r.Type,
"Value": r.Value,
"TTL": strconv.Itoa(r.TTL),
"Line": r.Line,
}
urlPath := d.genRequestURL(parms)
if urlPath == "" {
return errors.New("failed to generate request URL")
}
_, err := getHTTPBody(urlPath)
if err != nil {
fmt.Printf("UpdateDomainRecord error.%+v\n", err)
}
return err
}
func (d *AliDNS) genRequestURL(parms map[string]string) string {
var pArr []string
ps := map[string]string{}
for k, v := range publicParm {
ps[k] = v
}
for k, v := range parms {
ps[k] = v
}
now := time.Now().UTC()
ps["AccessKeyId"] = d.AccessKeyID
ps["SignatureNonce"] = strconv.Itoa(int(now.UnixNano()) + rand.Intn(99999))
ps["Timestamp"] = now.Format("2006-01-02T15:04:05Z")
for k, v := range ps {
pArr = append(pArr, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(pArr)
path := strings.Join(pArr, "&")
s := "GET&%2F&" + url.QueryEscape(path)
s = strings.Replace(s, "%3A", "%253A", -1)
s = strings.Replace(s, "%40", "%2540", -1)
s = strings.Replace(s, "%2A", "%252A", -1)
mac := hmac.New(sha1.New, []byte(d.AccessKeySecret+"&"))
if _, err := mac.Write([]byte(s)); err != nil {
return ""
}
sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return fmt.Sprintf("%s?%s&Signature=%s", baseURL, path, url.QueryEscape(sign))
}

View File

@ -0,0 +1,85 @@
package alidns
import (
"fmt"
"log"
"runtime/debug"
"time"
"github.com/TimothyYe/godns"
)
// 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
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 {
log.Println("Failed to get current IP:", 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 {
lastIP = currentIP
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 {
log.Printf("Cannot get subdomain %s from AliDNS.\r\n", subDomain)
continue
}
records[0].Value = currentIP
if err := aliDNS.UpdateDomainRecord(records[0]); err != nil {
log.Printf("Failed to update IP for subdomain:%s\r\n", subDomain)
continue
} else {
log.Printf("IP updated for subdomain:%s\r\n", subDomain)
}
// Send notification
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
log.Printf("Failed to send notification")
}
}
}
}
}

View File

@ -0,0 +1,266 @@
package cloudflare
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"runtime/debug"
"strings"
"time"
"github.com/TimothyYe/godns"
)
// Handler struct definition
type Handler struct {
Configuration *godns.Settings
API string
}
// DNSRecordResponse struct
type DNSRecordResponse struct {
Records []DNSRecord `json:"result"`
Success bool `json:"success"`
}
// DNSRecordUpdateResponse struct
type DNSRecordUpdateResponse struct {
Record DNSRecord `json:"result"`
Success bool `json:"success"`
}
// DNSRecord for Cloudflare API
type DNSRecord struct {
ID string `json:"id"`
IP string `json:"content"`
Name string `json:"name"`
Proxied bool `json:"proxied"`
Type string `json:"type"`
ZoneID string `json:"zone_id"`
TTL int32 `json:"ttl"`
}
// SetIP updates DNSRecord.IP
func (r *DNSRecord) SetIP(ip string) {
r.IP = ip
}
// ZoneResponse is a wrapper for Zones
type ZoneResponse struct {
Zones []Zone `json:"result"`
Success bool `json:"success"`
}
// Zone object with id and name
type Zone struct {
ID string `json:"id"`
Name string `json:"name"`
}
// SetConfiguration pass dns settings and store it to handler instance
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
handler.Configuration = conf
handler.API = "https://api.cloudflare.com/client/v4"
}
// 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
}
}()
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)
continue
}
log.Println("Current IP 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 {
log.Println("Checking IP for domain", domain.DomainName)
zoneID := handler.getZone(domain.DomainName)
if zoneID != "" {
records := handler.getDNSRecords(zoneID)
// update records
for _, rec := range records {
if !recordTracked(domain, &rec) {
log.Println("Skiping record:", rec.Name)
continue
}
if rec.IP != currentIP {
log.Printf("IP mismatch: Current(%+v) vs Cloudflare(%+v)\r\n", currentIP, rec.IP)
lastIP = handler.updateRecord(rec, currentIP)
// 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)
}
}
} else {
log.Println("Failed to find zone for domain:", domain.DomainName)
}
}
}
}
// 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 {
return true
}
}
return false
}
// 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, handler.Configuration.UseProxy)
if client == nil {
log.Println("cannot create HTTP client")
}
req, _ := http.NewRequest(method, handler.API+url, body)
req.Header.Set("Content-Type", "application/json")
if handler.Configuration.Email != "" && handler.Configuration.Password != "" {
req.Header.Set("X-Auth-Email", handler.Configuration.Email)
req.Header.Set("X-Auth-Key", handler.Configuration.Password)
} else if handler.Configuration.LoginToken != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", handler.Configuration.LoginToken))
}
return req, client
}
// Find the correct zone via domain name
func (handler *Handler) getZone(domain string) string {
var z ZoneResponse
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())
return ""
}
body, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &z)
if err != nil {
log.Printf("Decoder error: %+v\n", err)
log.Printf("Response body: %+v\n", string(body))
return ""
}
if z.Success != true {
log.Printf("Response failed: %+v\n", string(body))
return ""
}
for _, zone := range z.Zones {
if zone.Name == domain {
return zone.ID
}
}
return ""
}
// Get all DNS A records for a zone
func (handler *Handler) getDNSRecords(zoneID string) []DNSRecord {
var empty []DNSRecord
var r DNSRecordResponse
var recordType string
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())
return empty
}
body, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &r)
if err != nil {
log.Printf("Decoder error: %+v\n", err)
log.Printf("Response body: %+v\n", string(body))
return empty
}
if r.Success != true {
body, _ := ioutil.ReadAll(resp.Body)
log.Printf("Response failed: %+v\n", string(body))
return empty
}
return r.Records
}
// Update DNS A Record with new IP
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",
"/zones/"+record.ZoneID+"/dns_records/"+record.ID,
bytes.NewBuffer(j),
)
resp, err := client.Do(req)
if err != nil {
log.Println("Request error:", err.Error())
return ""
}
body, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &r)
if err != nil {
log.Printf("Decoder error: %+v\n", err)
log.Printf("Response body: %+v\n", string(body))
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

@ -0,0 +1,167 @@
package cloudflare
import (
"encoding/json"
"strings"
"testing"
"github.com/TimothyYe/godns"
)
func TestResponseToJSON(t *testing.T) {
s := strings.NewReader(`
{
"errors": [],
"messages": [],
"result": [
{
"id": "mk2b6fa491c12445a4376666a32429e1",
"name": "example.com",
"status": "active"
}
],
"result_info": {
"count": 1,
"page": 1,
"per_page": 20,
"total_count": 1,
"total_pages": 1
},
"success": true
}`)
var resp ZoneResponse
err := json.NewDecoder(s).Decode(&resp)
if err != nil {
t.Error(err.Error())
}
if resp.Success != true {
t.Errorf("Success Error: %#v != true ", resp.Success)
}
if resp.Zones[0].ID != "mk2b6fa491c12445a4376666a32429e1" {
t.Errorf("ID Error: %#v != mk2b6fa491c12445a4376666a32429e1 ", resp.Zones[0].ID)
}
if resp.Zones[0].Name != "example.com" {
t.Errorf("Name Error: %#v != example.com", resp.Zones[0].Name)
}
}
func TestDNSResponseToJSON(t *testing.T) {
s := strings.NewReader(`
{
"errors": [],
"messages": [],
"result": [
{
"content": "127.0.0.1",
"id": "F11cc63e02a42d38174b8e7c548a7b6f",
"name": "example.com",
"type": "A",
"zone_id": "mk2b6fa491c12445a4376666a32429e1",
"zone_name": "example.com"
}
],
"success": true
}`)
var resp DNSRecordResponse
err := json.NewDecoder(s).Decode(&resp)
if err != nil {
t.Error(err.Error())
}
if resp.Success != true {
t.Errorf("Success Error: %#v != true ", resp.Success)
}
if resp.Records[0].ID != "F11cc63e02a42d38174b8e7c548a7b6f" {
t.Errorf("ID Error: %#v != F11cc63e02a42d38174b8e7c548a7b6f ", resp.Records[0].ID)
}
if resp.Records[0].Name != "example.com" {
t.Errorf("Name Error: %#v != example.com", resp.Records[0].Name)
}
}
func TestDNSUpdateResponseToJSON(t *testing.T) {
s := strings.NewReader(`
{
"result": {
"id": "F11cc63e02a42d38174b8e7c548a7b6f",
"type": "A",
"name": "example.com",
"content": "127.0.0.1",
"proxiable": true,
"proxied": true,
"ttl": 1,
"locked": false,
"zone_id": "mk2b6fa491c12445a4376666a32429e1",
"zone_name": "example.com",
"modified_on": "2018-10-12T14:29:53.205191Z",
"created_on": "2018-10-12T14:29:53.205191Z",
"meta": {
"auto_added": false,
"managed_by_apps": false,
"managed_by_argo_tunnel": false
}
},
"success": true,
"errors": [],
"messages": []
}`)
var resp DNSRecordUpdateResponse
err := json.NewDecoder(s).Decode(&resp)
if err != nil {
t.Error(err.Error())
}
if resp.Success != true {
t.Errorf("Success Error: %#v != true ", resp.Success)
}
if resp.Record.ID != "F11cc63e02a42d38174b8e7c548a7b6f" {
t.Errorf("ID Error: %#v != F11cc63e02a42d38174b8e7c548a7b6f ", resp.Record.ID)
}
if resp.Record.Name != "example.com" {
t.Errorf("Name Error: %#v != example.com", resp.Record.Name)
}
}
func TestRecordTracked(t *testing.T) {
s := strings.NewReader(`
{
"errors": [],
"messages": [],
"result": [
{
"content": "127.0.0.1",
"id": "F11cc63e02a42d38174b8e7c548a7b6f",
"name": "example.com",
"type": "A",
"zone_id": "mk2b6fa491c12445a4376666a32429e1",
"zone_name": "example.com"
},
{
"content": "127.0.0.1",
"id": "G00cc63e02a42d38174b8e7c548a7b6f",
"name": "www.example.com",
"type": "A",
"zone_id": "mk2b6fa491c12445a4376666a32429e1",
"zone_name": "www.example.com"
}
],
"success": true
}`)
var resp DNSRecordResponse
err := json.NewDecoder(s).Decode(&resp)
if err != nil {
t.Error(err.Error())
}
domain := &godns.Domain{
DomainName: "example.com",
SubDomains: []string{"www"},
}
for _, rec := range resp.Records {
if recordTracked(domain, &rec) {
t.Logf("Record founded: %+v\r\n", rec.Name)
}
}
}

View File

@ -0,0 +1,292 @@
package dnspod
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"runtime/debug"
"strconv"
"strings"
"time"
"github.com/TimothyYe/godns"
"github.com/bitly/go-simplejson"
)
// Handler struct definition
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
log.Printf("Checking IP for domain %s \r\n", domain.DomainName)
domainID := handler.GetDomain(domain.DomainName)
if domainID == -1 {
continue
}
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 {
lastIP = currentIP
subDomainID, ip := handler.GetSubDomain(domainID, subDomain)
if subDomainID == "" || ip == "" {
log.Printf("Domain or subdomain not configured yet. domain: %s.%s subDomainID: %s ip: %s\n", subDomain, domain.DomainName, subDomainID, ip)
continue
}
// Continue to check the IP of subdomain
if len(ip) > 0 && strings.TrimRight(currentIP, "\n") != strings.TrimRight(ip, "\n") {
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
handler.UpdateIP(domainID, subDomainID, subDomain, 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")
}
} else {
log.Printf("%s.%s Current IP is same as domain IP, no need to update...\n", subDomain, domain.DomainName)
}
}
}
}
}
// GenerateHeader generates the request header for DNSPod API
func (handler *Handler) GenerateHeader(content url.Values) url.Values {
header := url.Values{}
if handler.Configuration.LoginToken != "" {
header.Add("login_token", handler.Configuration.LoginToken)
}
header.Add("format", "json")
header.Add("lang", "en")
header.Add("error_on_empty", "no")
if content != nil {
for k := range content {
header.Add(k, content.Get(k))
}
}
return header
}
// GetDomain returns specific domain by name
func (handler *Handler) GetDomain(name string) int64 {
var ret int64
values := url.Values{}
values.Add("type", "all")
values.Add("offset", "0")
values.Add("length", "20")
response, err := handler.PostData("/Domain.List", values)
if err != nil {
log.Println("Failed to get domain list...")
return -1
}
sjson, parseErr := simplejson.NewJson([]byte(response))
if parseErr != nil {
log.Println(parseErr)
return -1
}
if sjson.Get("status").Get("code").MustString() == "1" {
domains, _ := sjson.Get("domains").Array()
for _, d := range domains {
m := d.(map[string]interface{})
if m["name"] == name {
id := m["id"]
switch t := id.(type) {
case json.Number:
ret, _ = t.Int64()
}
break
}
}
if len(domains) == 0 {
log.Println("domains slice is empty.")
}
} else {
log.Println("get_domain:status code:", sjson.Get("status").Get("code").MustString())
}
return ret
}
// GetSubDomain returns subdomain by domain id
func (handler *Handler) GetSubDomain(domainID int64, name string) (string, string) {
var ret, ip string
value := url.Values{}
value.Add("domain_id", strconv.FormatInt(domainID, 10))
value.Add("offset", "0")
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 {
log.Println("Failed to get domain list")
return "", ""
}
sjson, parseErr := simplejson.NewJson([]byte(response))
if parseErr != nil {
log.Println(parseErr)
return "", ""
}
if sjson.Get("status").Get("code").MustString() == "1" {
records, _ := sjson.Get("records").Array()
for _, d := range records {
m := d.(map[string]interface{})
if m["name"] == name {
ret = m["id"].(string)
ip = m["value"].(string)
break
}
}
if len(records) == 0 {
log.Println("records slice is empty.")
}
} else {
log.Println("get_subdomain:status code:", sjson.Get("status").Get("code").MustString())
}
return ret, ip
}
// UpdateIP update subdomain with current IP
func (handler *Handler) UpdateIP(domainID int64, subDomainID string, subDomainName string, ip string) {
value := url.Values{}
value.Add("domain_id", strconv.FormatInt(domainID, 10))
value.Add("record_id", subDomainID)
value.Add("sub_domain", subDomainName)
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)
response, err := handler.PostData("/Record.Modify", value)
if err != nil {
log.Println("Failed to update record to new IP!")
log.Println(err)
return
}
sjson, parseErr := simplejson.NewJson([]byte(response))
if parseErr != nil {
log.Println(parseErr)
return
}
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, handler.Configuration.UseProxy)
if client == nil {
return "", errors.New("failed to create HTTP client")
}
values := handler.GenerateHeader(content)
req, _ := http.NewRequest("POST", "https://dnsapi.cn"+url, strings.NewReader(values.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", fmt.Sprintf("GoDNS/0.1 (%s)", ""))
response, err := client.Do(req)
if err != nil {
log.Println("Post failed...")
log.Println(err)
return "", err
}
defer response.Body.Close()
resp, _ := ioutil.ReadAll(response.Body)
return string(resp), nil
}

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

@ -0,0 +1,102 @@
package duck
import (
"fmt"
"io/ioutil"
"log"
"runtime/debug"
"strings"
"time"
"github.com/TimothyYe/godns"
)
var (
// DuckUrl the API address for Duck DNS
DuckUrl = "https://www.duckdns.org/update?domains=%s&token=%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("ip=%s", currentIP)
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
ip = fmt.Sprintf("ipv6=%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 {
// update IP with HTTP GET request
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)
continue
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil || string(body) != "OK" {
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")
}
}
}
}
}

View File

@ -0,0 +1,113 @@
package google
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"runtime/debug"
"strings"
"time"
"github.com/TimothyYe/godns"
)
var (
// GoogleURL the API address for Google Domains
GoogleURL = "https://%s:%s@domains.google.com/nic/update?hostname=%s.%s&myip=%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)
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 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(domain, subDomain, currentIP string) {
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
resp, err := client.Get(fmt.Sprintf(GoogleURL,
handler.Configuration.Email,
handler.Configuration.Password,
subDomain,
domain,
currentIP))
if err != nil {
// handle error
log.Print("Failed to update sub domain:", subDomain)
return
}
defer resp.Body.Close()
if err != nil {
log.Println("Request error...")
log.Println("Err:", err.Error())
} else {
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == http.StatusOK {
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))
}
}
}

45
handler/handler.go Normal file
View File

@ -0,0 +1,45 @@
package handler
import (
"github.com/TimothyYe/godns"
"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
type IHandler interface {
SetConfiguration(*godns.Settings)
DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain)
}
// CreateHandler creates DNS handler by different providers
func CreateHandler(provider string) IHandler {
var handler IHandler
switch provider {
case godns.CLOUDFLARE:
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:
handler = IHandler(&alidns.Handler{})
case godns.GOOGLE:
handler = IHandler(&google.Handler{})
case godns.DUCK:
handler = IHandler(&duck.Handler{})
case godns.NOIP:
handler = IHandler(&noip.Handler{})
}
return handler
}

107
handler/he/he_handler.go Normal file
View File

@ -0,0 +1,107 @@
package he
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"runtime/debug"
"strings"
"time"
"github.com/TimothyYe/godns"
)
var (
// HEUrl the API address for he.net
HEUrl = "https://dyn.dns.he.net/nic/update"
)
// 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)
//check against locally cached IP, if no change, skip update
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 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(domain, subDomain, currentIP string) {
values := url.Values{}
values.Add("hostname", fmt.Sprintf("%s.%s", subDomain, domain))
values.Add("password", handler.Configuration.Password)
values.Add("myip", currentIP)
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
req, _ := http.NewRequest("POST", HEUrl, strings.NewReader(values.Encode()))
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

@ -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")
}
}
}
}
}

310
logger.go
View File

@ -1,310 +0,0 @@
package main
import (
"bufio"
"bytes"
"errors"
"io"
"io/ioutil"
"log"
"os"
"runtime/debug"
"strconv"
"strings"
"sync"
"time"
)
const (
// L_INFO log level
L_INFO int = iota
// L_WARNING log level
L_WARNING
// L_DEBUG log level
L_DEBUG
// PRE_INFO log level
PRE_INFO = "[ INFO]"
// PRE_WARNING log level
PRE_WARNING = "[WARNING]"
// PRE_DEBUG log level
PRE_DEBUG = "[ DEBUG]"
)
// Logger struct
type Logger struct {
DEV_MODE bool
fd *os.File
size int
num int
level int
mu sync.Mutex
muSplit sync.Mutex
flushInterval int64 //Second
flushSize int
buf *bytes.Buffer
log *log.Logger
}
// NewLogger returns a new created logger
func NewLogger(logfile string, size, num int, level int, flushInterval int64, flushSize int) (logger *Logger, err error) {
if size < 1 || num < 1 || level < L_INFO || len(logfile) < 1 {
err = errors.New("NewLogWriter:param error.")
return
}
logger = &Logger{size: size * 1024, num: num, level: level, DEV_MODE: false}
logger.fd, err = os.OpenFile(logfile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModeAppend|0666)
if err != nil {
logger = nil
return
}
log.SetOutput(logger)
if flushInterval > 0 && flushSize > 0 {
logger.buf = new(bytes.Buffer)
logger.log = log.New(logger.buf, "", log.LstdFlags)
go func(interval int64, logger *Logger) {
defer func() {
if r := recover(); r != nil {
log.Printf("logger Tick, Recovered in %v:\n %s", r, debug.Stack())
}
}()
c := time.Tick(time.Duration(interval) * time.Second)
for _ = range c {
logger.Flush()
}
}(flushInterval, logger)
}
return
}
// InitLogger initialize logger with specified log filename & size
func InitLogger(logfile string, size, num int) (err error) {
logger, err := NewLogger(logfile, size, num, L_INFO, -1, -1)
if logger != nil {
logger.level = L_INFO - 1
}
return
}
// immplement write
func (logger *Logger) Write(p []byte) (n int, err error) {
if logger.DEV_MODE {
n, err = os.Stdout.Write(p)
return
}
n, err = logger.fd.Write(p)
if err == nil {
fi, e := logger.fd.Stat()
if e != nil {
err = e
return
}
if fi.Size() > int64(logger.size) {
logger.muSplit.Lock()
defer logger.muSplit.Unlock()
fname := fi.Name()
strings.HasSuffix(fname, ".log")
fbase := fname[:len(fname)-3]
oldBs := make([]byte, 0, logger.size)
newBs := []byte{}
fd, e := os.Open(fname)
if e != nil {
err = e
return
}
rd := bufio.NewReader(fd)
for {
line, e := rd.ReadBytes('\n')
if e == io.EOF {
break
}
if e != nil {
err = e
return
}
if len(oldBs)+len(line) > logger.size {
newBs = append(newBs, line...)
} else {
oldBs = append(oldBs, line...)
}
}
fd.Close()
_, err = logger.saveLog(1, fbase, oldBs)
if err != nil {
return
}
err = logger.fd.Close()
if err != nil {
return
}
err = os.Remove(fname)
if err != nil {
return
}
logger.fd, err = os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModeAppend|0666)
if err != nil {
return
}
_, err = logger.fd.Write(newBs)
if err != nil {
return
}
}
}
return
}
func (logger *Logger) saveLog(index int, fbase string, data []byte) (n int, err error) {
fn := fbase + strconv.Itoa(index) + ".log"
_, err = os.Stat(fn)
if index < logger.num && err == nil {
var b []byte
b, err = ioutil.ReadFile(fn)
if err != nil {
return
}
n, err = logger.saveLog(index+1, fbase, b)
if err != nil {
return
}
}
fd, err := os.OpenFile(fn, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm|0666)
if err != nil {
return
}
defer fd.Close()
n, err = fd.Write(data)
return
}
// Flush buf data to std log
func (logger *Logger) Flush() {
if logger.buf.Len() > 0 {
logger.mu.Lock()
defer logger.mu.Unlock()
log.SetFlags(0)
log.Print(logger.buf)
log.SetFlags(log.LstdFlags)
logger.buf.Reset()
}
}
// Clean prefix and check buf size
func (logger *Logger) clean() {
logger.log.SetPrefix("")
if logger.buf.Len()/1024 > logger.flushSize {
go logger.Flush()
}
}
func (logger *Logger) setPrefix(lv int) bool {
if lv > logger.level {
return false
}
switch lv {
case L_INFO:
logger.log.SetPrefix(PRE_INFO)
case L_WARNING:
logger.log.SetPrefix(PRE_WARNING)
case L_DEBUG:
logger.log.SetPrefix(PRE_DEBUG)
default:
return false
}
return true
}
func (logger *Logger) logPrint(lv int, args ...interface{}) {
logger.mu.Lock()
defer logger.mu.Unlock()
if !logger.setPrefix(lv) {
return
}
logger.log.Print(args...)
logger.clean()
}
func (logger *Logger) logPrintln(lv int, args ...interface{}) {
logger.mu.Lock()
defer logger.mu.Unlock()
if !logger.setPrefix(lv) {
return
}
logger.log.Println(args...)
logger.clean()
}
func (logger *Logger) logPrintf(lv int, format string, args ...interface{}) {
logger.mu.Lock()
defer logger.mu.Unlock()
if !logger.setPrefix(lv) {
return
}
logger.log.Printf(format, args...)
logger.clean()
}
// Close fd
func (logger *Logger) Close() {
if logger.fd != nil {
logger.Flush()
logger.fd.Close()
}
}
// Info output info log
func (logger *Logger) Info(args ...interface{}) {
logger.logPrint(L_INFO, args...)
}
// Infoln output info log with newline
func (logger *Logger) Infoln(args ...interface{}) {
logger.logPrintln(L_INFO, args...)
}
// Infof output formatted info log
func (logger *Logger) Infof(format string, args ...interface{}) {
logger.logPrintf(L_INFO, format, args...)
}
// Warning output warning log
func (logger *Logger) Warning(args ...interface{}) {
logger.logPrint(L_WARNING, args...)
}
//Warningln output warning log with newline
func (logger *Logger) Warningln(args ...interface{}) {
logger.logPrintln(L_WARNING, args...)
}
// Warningf output formatted warning log
func (logger *Logger) Warningf(format string, args ...interface{}) {
logger.logPrintf(L_WARNING, format, args...)
}
// Debug output debug log
func (logger *Logger) Debug(args ...interface{}) {
logger.logPrint(L_DEBUG, args...)
logger.Flush()
}
// Debugln output debug log with newline
func (logger *Logger) Debugln(args ...interface{}) {
logger.logPrintln(L_DEBUG, args...)
logger.Flush()
}
// Debugf output formatted debug log
func (logger *Logger) Debugf(format string, args ...interface{}) {
logger.logPrintf(L_DEBUG, format, args...)
logger.Flush()
}

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

@ -1,34 +1,75 @@
package main
package godns
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
)
//Domain struct
// Domain struct
type Domain struct {
DomainName string `json:"domain_name"`
SubDomains []string `json:"sub_domains"`
}
//Settings struct
// 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 MailNotify struct {
Enabled bool `json:"enabled"`
SMTPServer string `json:"smtp_server"`
SMTPUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"`
SMTPPort int `json:"smtp_port"`
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"`
Email string `json:"email"`
Password string `json:"password"`
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"`
LogSize int `json:"log_size"`
LogNum int `json:"log_num"`
Socks5Proxy string `json:"socks5_proxy"`
Notify Notify `json:"notify"`
IPInterface string `json:"ip_interface"`
IPType string `json:"ip_type"`
Resolver string `json:"resolver"`
UseProxy bool `json:"use_proxy"`
}
//LoadSettings -- Load settings from config file
// LoadSettings -- Load settings from config file
func LoadSettings(configPath string, settings *Settings) error {
//LoadSettings from config file
// LoadSettings from config file
file, err := ioutil.ReadFile(configPath)
if err != nil {
fmt.Println("Error occurs while reading config file, please make sure config file exists!")
@ -41,36 +82,9 @@ func LoadSettings(configPath string, settings *Settings) error {
return err
}
return nil
}
//LoadDomains -- Load domains from domains string
func LoadDomains(domainsOrginStr string, domains *[]Domain) error {
domainsMap := make(map[string]*Domain)
domainsArray := strings.Split(domainsOrginStr, ",")
for _, host := range domainsArray {
dotCount := strings.Count(host, ".")
if dotCount < 2 {
continue
}
len := len(host)
pos := strings.Index(host, ".")
subDomain := host[0:pos]
domainName := host[pos+1 : len]
if d, exist := domainsMap[domainName]; exist {
d.SubDomains = append(d.SubDomains, subDomain)
} else {
d := new(Domain)
d.DomainName = domainName
d.SubDomains = append(d.SubDomains, subDomain)
domainsMap[domainName] = d
}
}
for _, d := range domainsMap {
*domains = append(*domains, *d)
if settings.Interval == 0 {
// set default interval as 5 minutes if interval is 0
settings.Interval = 5 * 60
}
return nil

View File

@ -1,4 +1,4 @@
package main
package godns
import (
"testing"
@ -13,6 +13,11 @@ func TestLoadSetting(t *testing.T) {
}
if settings.IPUrl == "" {
t.Error("Cannot load ip_url from config file")
t.Error("cannot load ip_url from config file")
}
err = LoadSettings("./file/does/not/exists", &settings)
if err == nil {
t.Error("file doesn't exist, should return error")
}
}

BIN
snapshots/he1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

BIN
snapshots/he2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
snapshots/mail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
snapshots/notify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

105
template.go Normal file
View File

@ -0,0 +1,105 @@
package godns
var mailTemplate = `
<html>
<body>
<div role="section">
<div style="background-color: #281557;">
<div class="layout one-col" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-full-width" style="background-color: #281557;"><td class="layout__edges">&nbsp;</td><td style="width: 600px" class="w560"><![endif]-->
<div class="column" style="max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);text-align: left;color: #8e959c;font-size: 14px;line-height: 21px;font-family: sans-serif;">
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;line-height: 10px;font-size: 1px;">&nbsp;</div>
</div>
</div>
<!--[if (mso)|(IE)]></td><td class="layout__edges">&nbsp;</td></tr></table><![endif]-->
</div>
</div>
</div>
<div style="background-color: #281557;">
<div class="layout one-col" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-full-width" style="background-color: #281557;"><td class="layout__edges">&nbsp;</td><td style="width: 600px" class="w560"><![endif]-->
<div class="column" style="max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);text-align: left;color: #8e959c;font-size: 14px;line-height: 21px;font-family: sans-serif;">
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;line-height: 50px;font-size: 1px;">&nbsp;</div>
</div>
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;mso-text-raise: 4px;">
<h1 class="size-28" style="Margin-top: 0;Margin-bottom: 0;font-style: normal;font-weight: normal;color: #000;font-size: 24px;line-height: 32px;font-family: avenir,sans-serif;text-align: center;"
lang="x-size-28">
<span class="font-avenir">
<span style="color:#ffffff">Your IP address has been changed to</span>
</span>
</h1>
<h1 class="size-48" style="Margin-top: 20px;Margin-bottom: 0;font-style: normal;font-weight: normal;color: #000;font-size: 36px;line-height: 43px;font-family: avenir,sans-serif;text-align: center;"
lang="x-size-48">
<span class="font-avenir">
<strong>
<span style="color:#ffffff">{{ .CurrentIP }}</span>
</strong>
</span>
</h1>
<h2 class="size-28" style="Margin-top: 20px;Margin-bottom: 16px;font-style: normal;font-weight: normal;color: #e31212;font-size: 24px;line-height: 32px;font-family: Avenir,sans-serif;text-align: center;"
lang="x-size-28">
<font color="#ffffff">
<strong>Domain {{ .Domain }} is updated</strong>
</font>
</h2>
</div>
</div>
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;line-height: 15px;font-size: 1px;">&nbsp;</div>
</div>
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;line-height: 35px;font-size: 1px;">&nbsp;</div>
</div>
</div>
<!--[if (mso)|(IE)]></td><td class="layout__edges">&nbsp;</td></tr></table><![endif]-->
</div>
</div>
</div>
<div style="mso-line-height-rule: exactly;line-height: 20px;font-size: 20px;">&nbsp;</div>
<div style="mso-line-height-rule: exactly;" role="contentinfo">
<div class="layout email-footer" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
<!--[if (mso)|(IE)]><table align="center" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-email-footer"><td style="width: 400px;" valign="top" class="w360"><![endif]-->
<div class="column wide" style="text-align: left;font-size: 12px;line-height: 19px;color: #adb3b9;font-family: sans-serif;Float: left;max-width: 400px;min-width: 320px; width: 320px;width: calc(8000% - 47600px);">
<div style="Margin-left: 20px;Margin-right: 20px;Margin-top: 10px;Margin-bottom: 10px;">
<div style="font-size: 12px;line-height: 19px;">
</div>
<div style="font-size: 12px;line-height: 19px;Margin-top: 18px;">
</div>
<!--[if mso]>&nbsp;<![endif]-->
</div>
</div>
<!--[if (mso)|(IE)]></td><td style="width: 200px;" valign="top" class="w160"><![endif]-->
<div class="column narrow" style="text-align: left;font-size: 12px;line-height: 19px;color: #adb3b9;font-family: sans-serif;Float: left;max-width: 320px;min-width: 200px; width: 320px;width: calc(72200px - 12000%);">
<div style="Margin-left: 20px;Margin-right: 20px;Margin-top: 10px;Margin-bottom: 10px;">
</div>
</div>
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
</div>
</div>
</div>
<div style="mso-line-height-rule: exactly;line-height: 40px;font-size: 40px;">&nbsp;</div>
</body>
</div>
</html>
`

465
utils.go
View File

@ -1,51 +1,462 @@
package main
package godns
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"html/template"
"io/ioutil"
"log"
"runtime"
"net"
"net/http"
"net/url"
"strings"
dnsResolver "github.com/TimothyYe/godns/resolver"
"github.com/miekg/dns"
"golang.org/x/net/proxy"
"gopkg.in/gomail.v2"
)
func identifyPanic() string {
var name, file string
var line int
var pc [16]uintptr
var (
// Logo for GoDNS
Logo = `
n := runtime.Callers(3, pc[:])
for _, pc := range pc[:n] {
fn := runtime.FuncForPC(pc)
if fn == nil {
GoDNS V%s
https://github.com/TimothyYe/godns
`
)
const (
// PanicMax is the max allowed panic times
PanicMax = 5
// DNSPOD for dnspod.cn
DNSPOD = "DNSPod"
// HE for he.net
HE = "HE"
// CLOUDFLARE for cloudflare.com
CLOUDFLARE = "Cloudflare"
// ALIDNS for AliDNS
ALIDNS = "AliDNS"
// GOOGLE for Google Domains
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
func GetIPFromInterface(configuration *Settings) (string, error) {
ifaces, err := net.InterfaceByName(configuration.IPInterface)
if err != nil {
log.Println("can't get network device "+configuration.IPInterface+":", err)
return "", err
}
addrs, err := ifaces.Addrs()
if err != nil {
log.Println("can't get address from "+configuration.IPInterface+":", err)
return "", err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil {
continue
}
file, line = fn.FileLine(pc)
name = fn.Name()
if !strings.HasPrefix(name, "runtime.") {
break
if !(ip.IsGlobalUnicast() &&
!(ip.IsUnspecified() ||
ip.IsMulticast() ||
ip.IsLoopback() ||
ip.IsLinkLocalUnicast() ||
ip.IsLinkLocalMulticast() ||
ip.IsInterfaceLocalMulticast())) {
continue
}
if isIPv4(ip.String()) {
if strings.ToUpper(configuration.IPType) != IPV4 {
continue
}
} else {
if strings.ToUpper(configuration.IPType) != IPV6 {
continue
}
}
if ip.String() != "" {
return ip.String(), nil
}
}
return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
}
func isIPv4(ip string) bool {
return strings.Count(ip, ":") < 2
}
// GetHttpClient creates the HTTP client and return it
func GetHttpClient(configuration *Settings, useProxy bool) *http.Client {
client := &http.Client{}
if useProxy && configuration.Socks5Proxy != "" {
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 nil
}
httpTransport := &http.Transport{}
client.Transport = httpTransport
httpTransport.Dial = dialer.Dial
}
return client
}
//GetCurrentIP gets an IP from either internet or specific interface, depending on configuration
func GetCurrentIP(configuration *Settings) (string, error) {
var err error
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.")
} else {
return ip, nil
}
}
switch {
case name != "":
return fmt.Sprintf("%v:%v", name, line)
case file != "":
return fmt.Sprintf("%v:%v", file, line)
if configuration.IPInterface != "" {
ip, err := GetIPFromInterface(configuration)
if err != nil {
log.Println("get ip from interface failed. There is no more ways to try.")
} else {
return ip, nil
}
}
return fmt.Sprintf("pc:%x", pc)
return "", err
}
func usage() {
log.Println("[command] -c=[config file path]")
flag.PrintDefaults()
// GetIPOnline gets public IP from internet
func GetIPOnline(configuration *Settings) (string, error) {
client := &http.Client{}
var response *http.Response
var err error
if configuration.IPType == "" || strings.ToUpper(configuration.IPType) == IPV4 {
response, err = client.Get(configuration.IPUrl)
} else {
response, err = client.Get(configuration.IPV6Url)
}
if err != nil {
log.Println("Cannot get IP...")
return "", err
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
return strings.Trim(string(body), "\n"), nil
}
func checkSettings(config *Settings) error {
if (config.Email == "" || config.Password == "") && config.LoginToken == "" {
return errors.New("Input email/password or login token cannot be empty!")
// CheckSettings check the format of settings
func CheckSettings(config *Settings) error {
switch config.Provider {
case DNSPOD:
if config.Password == "" && config.LoginToken == "" {
return errors.New("password or login token cannot be empty")
}
case HE:
if config.Password == "" {
return errors.New("password cannot be empty")
}
case CLOUDFLARE:
if config.LoginToken == "" {
if config.Email == "" {
return errors.New("email cannot be empty")
}
if config.Password == "" {
return errors.New("password cannot be empty")
}
}
case ALIDNS:
if config.Email == "" {
return errors.New("email cannot be empty")
}
if config.Password == "" {
return errors.New("password cannot be empty")
}
case DUCK:
if config.LoginToken == "" {
return errors.New("login token cannot be empty")
}
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")
}
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
}
// 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.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, mailTemplate))
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 {
return err
}
return nil
}
// 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(tplsrc); err != nil {
log.Println("Failed to parse template")
return ""
}
data := struct {
CurrentIP string
Domain string
}{
currentIP,
domain,
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, data); err != nil {
log.Println(err.Error())
return ""
}
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
}

42
utils_test.go Normal file
View File

@ -0,0 +1,42 @@
package godns
import (
"testing"
)
func TestGetCurrentIP(t *testing.T) {
conf := &Settings{IPUrl: "https://myip.biturl.top"}
ip, _ := GetCurrentIP(conf)
if ip == "" {
t.Log("IP is empty...")
} else {
t.Log("IP is:" + ip)
}
}
func TestCheckSettings(t *testing.T) {
settingError := &Settings{}
if err := CheckSettings(settingError); err == nil {
t.Error("setting is invalid, should return error")
}
settingDNSPod := &Settings{Provider: "DNSPod", LoginToken: "aaa"}
if err := CheckSettings(settingDNSPod); err == nil {
t.Log("setting with login token, passed")
} else {
t.Error("setting with login token, should be passed")
}
settingDNSPod = &Settings{Provider: "DNSPod"}
if err := CheckSettings(settingDNSPod); err == nil {
t.Error("setting with invalid parameters, should be failed")
}
settingHE := &Settings{Provider: "HE", Password: ""}
if err := CheckSettings(settingHE); err != nil {
t.Log("HE setting without password, passed")
} else {
t.Error("HE setting without password, should be faild")
}
}