mirror of
https://github.com/taigrr/godns
synced 2025-01-18 04:03:25 -08:00
Compare commits
75 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5ad83edfe8 | ||
|
069fd1dcb5 | ||
|
fa03a1b959 | ||
|
17c9b6fa17 | ||
|
6dfbb60dfd | ||
|
07e5d7f99e | ||
|
10c67fa23a | ||
|
afc6ba8241 | ||
|
7ac3eed730 | ||
|
ac7dc021eb | ||
|
2154fcc762 | ||
|
99af25e496 | ||
|
dd4805df92 | ||
|
cdf32c509d | ||
|
fd4c30eec7 | ||
|
212ce47ea7 | ||
|
cefae90569 | ||
|
c720f7bca1 | ||
|
7a60d3415f | ||
|
e33cd6e1cb | ||
|
f62c20a145 | ||
|
ad6e17dc06 | ||
|
f807baa3ce | ||
|
2d546702ff | ||
|
f6a27491f3 | ||
|
3ba95289e9 | ||
|
d7755ab15e | ||
|
e070900b3b | ||
|
21590d4c64 | ||
|
a7c2b0a56e | ||
|
1ddd1dfb8b | ||
|
e94d99e25c | ||
|
f70f3bc0a3 | ||
|
3eaed255d9 | ||
|
633e1187d0 | ||
|
0f670c66f9 | ||
|
0f9ea20f10 | ||
|
81ecf1d096 | ||
|
fa1432be99 | ||
|
045cb692a5 | ||
|
2021a2404c | ||
|
eec9e9a881 | ||
|
b1e94fda52 | ||
|
de42aa6073 | ||
|
31ebe517e5 | ||
|
3c2e9a805e | ||
|
cf0a80a729 | ||
|
49de482a53 | ||
|
20c2b96765 | ||
|
97195eccce | ||
|
9186772d50 | ||
|
09503d0c97 | ||
|
5f405ba486 | ||
|
8a3471ad1c | ||
|
e91015f051 | ||
|
d923cbde61 | ||
|
6a164f8b0b | ||
|
e8ce3434ee | ||
|
690e138164 | ||
|
73cd0517a0 | ||
|
f2e9c0ab00 | ||
|
71077db79b | ||
|
10e125d06d | ||
|
1c1aa6c420 | ||
|
57ecf1f7bb | ||
|
9397f6b272 | ||
|
68b729e635 | ||
|
9a43df7906 | ||
|
61786fdfb5 | ||
|
f0a6291406 | ||
|
41d54f1354 | ||
|
019388f7b5 | ||
|
4ca167ac43 | ||
|
df549ddc33 | ||
|
79134b3315 |
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
@ -7,10 +7,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.13.6
|
- name: Set up Go 1.15.7
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
go-version: 1.13.6
|
go-version: 1.15.7
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
|
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal 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
|
13
Dockerfile
13
Dockerfile
@ -2,13 +2,8 @@ FROM golang:alpine AS builder
|
|||||||
RUN mkdir /godns
|
RUN mkdir /godns
|
||||||
ADD . /godns/
|
ADD . /godns/
|
||||||
WORKDIR /godns
|
WORKDIR /godns
|
||||||
RUN go build -o godns cmd/godns/godns.go
|
RUN CGO_ENABLED=0 go build -o godns cmd/godns/godns.go
|
||||||
|
|
||||||
FROM alpine
|
FROM gcr.io/distroless/base
|
||||||
RUN apk add --update ca-certificates
|
COPY --from=builder /godns/godns /godns
|
||||||
RUN mkdir /usr/local/godns
|
ENTRYPOINT ["/godns"]
|
||||||
COPY --from=builder /godns/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"]
|
|
6
Makefile
6
Makefile
@ -2,15 +2,15 @@
|
|||||||
BINARY=godns
|
BINARY=godns
|
||||||
# Builds the project
|
# Builds the project
|
||||||
build:
|
build:
|
||||||
GO111MODULE=on go build cmd/godns/godns.go -o ${BINARY} -ldflags "-X main.Version=${VERSION}"
|
GO111MODULE=on go build -ldflags "-X main.Version=${VERSION}" -o ${BINARY} cmd/godns/godns.go
|
||||||
# Installs our project: copies binaries
|
# Installs our project: copies binaries
|
||||||
install:
|
install:
|
||||||
GO111MODULE=on go install
|
GO111MODULE=on go install
|
||||||
image:
|
image:
|
||||||
# Build docker image
|
# Build docker image
|
||||||
go clean
|
go clean
|
||||||
docker buildx build --platform linux/amd64,linux/386,linux/arm64,linux/arm/v7 -t timothyye/godns:${VERSION} . --push
|
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t timothyye/godns:${VERSION} . --push
|
||||||
docker buildx build --platform linux/amd64,linux/386,linux/arm64,linux/arm/v7 -t timothyye/godns:latest . --push
|
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t timothyye/godns:latest . --push
|
||||||
release:
|
release:
|
||||||
# Clean
|
# Clean
|
||||||
go clean
|
go clean
|
||||||
|
515
README.md
515
README.md
@ -7,7 +7,7 @@
|
|||||||
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
||||||
```
|
```
|
||||||
|
|
||||||
[![MIT licensed][9]][10] [-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) [![Docker][3]][4] [![Go Report Card][11]][12] [![Cover.Run][15]][16] [![GoDoc][13]][14]
|
[![Apache licensed][9]][10] [![Docker][3]][4] [![Go Report Card][11]][12] [![Cover.Run][15]][16] [![GoDoc][13]][14]
|
||||||
|
|
||||||
[3]: https://images.microbadger.com/badges/image/timothyye/godns.svg
|
[3]: https://images.microbadger.com/badges/image/timothyye/godns.svg
|
||||||
[4]: https://microbadger.com/images/timothyye/godns
|
[4]: https://microbadger.com/images/timothyye/godns
|
||||||
@ -20,68 +20,107 @@
|
|||||||
[15]: https://img.shields.io/badge/cover.run-88.2%25-green.svg
|
[15]: https://img.shields.io/badge/cover.run-88.2%25-green.svg
|
||||||
[16]: https://cover.run/go/github.com/timothyye/godns
|
[16]: https://cover.run/go/github.com/timothyye/godns
|
||||||
|
|
||||||
GoDNS is a dynamic DNS (DDNS) client tool, it is based on my early open source project: [DynDNS](https://github.com/TimothyYe/DynDNS).
|
[GoDNS](https://github.com/TimothyYe/godns) is a dynamic DNS (DDNS) client tool. It is a rewrite in [Go](https://golang.org) of my early [DynDNS](https://github.com/TimothyYe/DynDNS) open source project.
|
||||||
|
|
||||||
Now I rewrite [DynDNS](https://github.com/TimothyYe/DynDNS) by Golang and call it [GoDNS](https://github.com/TimothyYe/godns).
|
Currently supports updating A records for subdomains. Doesn't support updating of root domains.
|
||||||
|
|
||||||
|
---
|
||||||
|
- [Supported DNS Providers](#supported-dns-providers)
|
||||||
|
- [Supported Platforms](#supported-platforms)
|
||||||
|
- [Pre-conditions](#pre-conditions)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Configuration properties](#configuration-properties)
|
||||||
|
- [Configuration examples](#configuration-examples)
|
||||||
|
- [Cloudflare](#cloudflare)
|
||||||
|
- [DNSPod](#dnspod)
|
||||||
|
- [Dreamhost](#dreamhost)
|
||||||
|
- [Google Domains](#google-domains)
|
||||||
|
- [AliDNS](#alidns)
|
||||||
|
- [DuckDNS](#duckdns)
|
||||||
|
- [No-IP](#no-ip)
|
||||||
|
- [HE.net](#henet)
|
||||||
|
- [Notifications](#notifications)
|
||||||
|
- [Email](#email)
|
||||||
|
- [Telegram](#telegram)
|
||||||
|
- [Slack](#slack)
|
||||||
|
- [Miscellaneous topics](#miscellaneous-topics)
|
||||||
|
- [IPv6 support](#ipv6-support)
|
||||||
|
- [Network interface IP address](#network-interface-ip-address)
|
||||||
|
- [SOCKS5 proxy support](#socks5-proxy-support)
|
||||||
|
- [Running GoDNS](#running-godns)
|
||||||
|
- [As a manual daemon](#as-a-manual-daemon)
|
||||||
|
- [As a managed daemon (with upstart)](#as-a-managed-daemon-with-upstart)
|
||||||
|
- [As a managed daemon (with systemd)](#as-a-managed-daemon-with-systemd)
|
||||||
|
- [As a Docker container](#as-a-docker-container)
|
||||||
|
- [As a Windows service](#as-a-windows-service)
|
||||||
|
- [Special Thanks](#special-thanks)
|
||||||
|
|
||||||
|
---
|
||||||
## Supported DNS Providers
|
## Supported DNS Providers
|
||||||
|
|
||||||
* Cloudflare ([https://cloudflare.com](https://cloudflare.com))
|
| Provider | IPv4 support | IPv6 support | Root Domain | Subdomains |
|
||||||
* Google Domains ([https://domains.google](https://domains.google))
|
| ------------------------------------- | :----------------: | :----------------: | :----------------: | :----------------: |
|
||||||
* DNSPod ([https://www.dnspod.cn/](https://www.dnspod.cn/))
|
| [Cloudflare][cloudflare] | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
* HE.net (Hurricane Electric) ([https://dns.he.net/](https://dns.he.net/))
|
| [Google Domains][google.domains] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
||||||
* AliDNS ([https://help.aliyun.com/product/29697.html](https://help.aliyun.com/product/29697.html))
|
| [DNSPod][dnspod] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
||||||
* DuckDNS ([https://www.duckdns.org](https://www.duckdns.org))
|
| [HE.net (Hurricane Electric)][he.net] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
||||||
|
| [AliDNS][alidns] | :white_check_mark: | :x: | :x: | :white_check_mark: |
|
||||||
|
| [DuckDNS][duckdns] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
||||||
|
| [Dreamhost][dreamhost] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
||||||
|
| [No-IP][no-ip] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
||||||
|
|
||||||
|
[cloudflare]: https://cloudflare.com
|
||||||
|
[google.domains]: https://domains.google
|
||||||
|
[dnspod]: https://www.dnspod.cn
|
||||||
|
[he.net]: https://dns.he.net
|
||||||
|
[alidns]: https://help.aliyun.com/product/29697.html
|
||||||
|
[duckdns]: https://www.duckdns.org
|
||||||
|
[dreamhost]: https://www.dreamhost.com
|
||||||
|
[no-ip]: https://www.noip.com
|
||||||
|
|
||||||
|
Tip: You can follow this [issue](https://github.com/TimothyYe/godns/issues/76) to view the current status of DDNS for root domains.
|
||||||
|
|
||||||
## Supported Platforms
|
## Supported Platforms
|
||||||
|
|
||||||
* Linux
|
* Linux
|
||||||
* MacOS
|
* MacOS
|
||||||
* ARM Linux (Raspberry Pi, etc...)
|
* ARM Linux (Raspberry Pi, etc.)
|
||||||
* Windows
|
* Windows
|
||||||
* MIPS32 platform
|
* MIPS32 platform
|
||||||
|
|
||||||
## MIPS32 platform
|
To compile binaries for MIPS (mips or mipsle), run:
|
||||||
|
|
||||||
To compile binaries for MIPS (mips or mipsle):
|
```bash
|
||||||
|
GOOS=linux GOARCH=mips/mipsle GOMIPS=softfloat go build -a
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary can run on routers as well.
|
||||||
|
|
||||||
|
## Pre-conditions
|
||||||
|
|
||||||
|
To use GoDNS, it is assumed:
|
||||||
|
* You registered (now own) a domain
|
||||||
|
* Domain was delegated to a supported [DNS provider](#supported-dns-providers) (i.e. it has nameserver `NS` records pointing at a supported provider)
|
||||||
|
|
||||||
|
Alternatively, you can sign in to [DuckDNS](https://www.duckdns.org) (with a social account) and get a subdomain on the duckdns.org domain for free.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Build GoDNS by running (from the root of the repository):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GOOS=linux GOARCH=mips/mipsle GOMIPS=softfloat go build -a
|
cd cmd/godns # go to the GoDNS directory
|
||||||
|
go get -v # get dependencies
|
||||||
|
go build # build
|
||||||
```
|
```
|
||||||
|
|
||||||
And the binary can run well on routers.
|
You can also download a compiled binary from the [releases](https://github.com/TimothyYe/godns/releases).
|
||||||
|
## Usage
|
||||||
|
|
||||||
## Pre-condition
|
Print usage/help by running:
|
||||||
|
|
||||||
* Register and own a domain.
|
|
||||||
|
|
||||||
* Domain's nameserver points to [DNSPod](https://www.dnspod.cn/) or [HE.net](https://dns.he.net/) or [Cloudflare](https://www.cloudflare.com/) or [Google Domains](https://domains.google) or [AliDNS](https://dc.console.aliyun.com).
|
|
||||||
|
|
||||||
* Or just register an account from [DuckDNS](https://www.duckdns.org/).
|
|
||||||
|
|
||||||
## Get it
|
|
||||||
|
|
||||||
### Build it from source code
|
|
||||||
|
|
||||||
* Get source code from Github:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/timothyye/godns.git
|
|
||||||
```
|
|
||||||
|
|
||||||
* Go into the godns directory, get related library and then build it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd cmd/godns
|
|
||||||
go get -v
|
|
||||||
go build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Download from releases
|
|
||||||
|
|
||||||
Download compiled binaries from [releases](https://github.com/TimothyYe/godns/releases)
|
|
||||||
|
|
||||||
## Get help
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ./godns -h
|
$ ./godns -h
|
||||||
@ -91,62 +130,37 @@ Usage of ./godns:
|
|||||||
-h Show help
|
-h Show help
|
||||||
```
|
```
|
||||||
|
|
||||||
## Config it
|
## Configuration
|
||||||
|
|
||||||
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
|
### Overview
|
||||||
* Rename it to **config.json**.
|
|
||||||
* Configure your provider, domain/subdomain info, username and password, etc.
|
|
||||||
* Configure the SMTP options if you want, a mail notification will sent to your mailbox once the IP is changed.
|
|
||||||
* Save it in the same directory of GoDNS, or use -c=your_conf_path command.
|
|
||||||
|
|
||||||
## Config fields
|
* Make a copy of [config_sample.json](./config_sample.json) and name it `config.json`
|
||||||
|
* Configure your provider, domain/subdomain info, credentials, etc.
|
||||||
|
* Configure a notification medium (e.g. SMTP to receive emails) to get notified when your IP address changes
|
||||||
|
* Place the file in the same directory of GoDNS or use the `-c=path/to/your/file.json` option
|
||||||
|
|
||||||
* provider: The providers that GoDNS supports, available values are: `Cloudflare`, `Google`, `DNSPod`, `AliDNS`, `HE`, `DuckDNS`.
|
### Configuration properties
|
||||||
* email: Email or account name of your DNS provider.
|
|
||||||
* password: Password of your account.
|
|
||||||
* login_token: API token of your account.
|
|
||||||
* domains: Domains list, with your sub domains.
|
|
||||||
* ip_url: A site helps you to get your public IPv4 IP address.
|
|
||||||
* ipv6_url: A site helps you to get your public IPv6 address.
|
|
||||||
* ip_type: To configure GoDNS under IPv4 mode or IPv6 mode, available values are: `IPv4`, `IPv6`.
|
|
||||||
* interval: The interval `seconds` that GoDNS check your public IP.
|
|
||||||
* socks5_proxy: Socks5 proxy server.
|
|
||||||
|
|
||||||
## IPv6 support
|
* `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.
|
||||||
|
|
||||||
Supported provider(s):
|
### Configuration examples
|
||||||
* Cloudflare
|
|
||||||
* HE.net
|
|
||||||
* DNSPod
|
|
||||||
* DuckDNS
|
|
||||||
* Google Domains
|
|
||||||
|
|
||||||
To enable the `IPv6` mode of GoDNS, you only need two steps:
|
#### Cloudflare
|
||||||
* Set the `ip_type` as `IPv6`, and make sure the `ipv6_url` is configured.
|
|
||||||
* Add one `AAAA` record to your provider.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"domains": [
|
|
||||||
{
|
|
||||||
"domain_name": "example.com",
|
|
||||||
"sub_domains": [
|
|
||||||
"ipv6"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ipv6_url": "https://api-ipv6.ip.sb/ip",
|
|
||||||
"ip_type": "IPv6"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Config example for 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.
|
For Cloudflare, you need to provide the email & Global API Key as password (or to use the API token) and config all the domains & subdomains.
|
||||||
|
|
||||||
* Using email & Global API Key
|
<details>
|
||||||
|
<summary>Using email & Global API Key</summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -161,13 +175,16 @@ For Cloudflare, you need to provide the email & Global API Key as password (or t
|
|||||||
"sub_domains": ["www","test"]
|
"sub_domains": ["www","test"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"ip_url": "https://myip.biturl.top",
|
||||||
"interval": 300,
|
"interval": 300,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
* Using the API Token
|
<details>
|
||||||
|
<summary>Using the API Token</summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -181,16 +198,21 @@ For Cloudflare, you need to provide the email & Global API Key as password (or t
|
|||||||
"sub_domains": ["www","test"]
|
"sub_domains": ["www","test"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"ip_url": "https://myip.biturl.top",
|
||||||
"interval": 300,
|
"interval": 300,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Config example for DNSPod
|
#### DNSPod
|
||||||
|
|
||||||
For DNSPod, you need to provide your API Token(you can create it [here](https://www.dnspod.cn/console/user/security)), and config all the domains & subdomains.
|
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
|
```json
|
||||||
{
|
{
|
||||||
"provider": "DNSPod",
|
"provider": "DNSPod",
|
||||||
@ -203,17 +225,51 @@ For DNSPod, you need to provide your API Token(you can create it [here](https://
|
|||||||
"sub_domains": ["www","test"]
|
"sub_domains": ["www","test"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"ip_url": "https://myip.biturl.top",
|
||||||
"ip_type": "IPV4",
|
"ip_type": "IPV4",
|
||||||
"interval": 300,
|
"interval": 300,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Config example for Google Domains
|
#### Dreamhost
|
||||||
|
|
||||||
|
For Dreamhost, you need to provide your API Token(you can create it [here](https://panel.dreamhost.com/?tree=home.api)), and config all the domains & subdomains.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Example</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"provider": "Dreamhost",
|
||||||
|
"login_token": "your_api_key",
|
||||||
|
"domains": [{
|
||||||
|
"domain_name": "example.com",
|
||||||
|
"sub_domains": ["www","test"]
|
||||||
|
},{
|
||||||
|
"domain_name": "example2.com",
|
||||||
|
"sub_domains": ["www","test"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
|
"ip_url": "https://myip.biturl.top",
|
||||||
|
"ip_type": "IPV4",
|
||||||
|
"interval": 300,
|
||||||
|
"resolver": "ns1.dreamhost.com",
|
||||||
|
"socks5_proxy": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Google Domains
|
||||||
|
|
||||||
For Google Domains, you need to provide email & password, and config all the domains & subdomains.
|
For Google Domains, you need to provide email & password, and config all the domains & subdomains.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Example</summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"provider": "Google",
|
"provider": "Google",
|
||||||
@ -227,16 +283,21 @@ For Google Domains, you need to provide email & password, and config all the dom
|
|||||||
"sub_domains": ["www","test"]
|
"sub_domains": ["www","test"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"ip_url": "https://myip.biturl.top",
|
||||||
"interval": 300,
|
"interval": 300,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Config example for AliDNS
|
#### AliDNS
|
||||||
|
|
||||||
For AliDNS, you need to provide `AccessKeyID` & `AccessKeySecret` as `email` & `password`, and config all the domains & subdomains.
|
For AliDNS, you need to provide `AccessKeyID` & `AccessKeySecret` as `email` & `password`, and config all the domains & subdomains.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Example</summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"provider": "AliDNS",
|
"provider": "AliDNS",
|
||||||
@ -251,16 +312,21 @@ For AliDNS, you need to provide `AccessKeyID` & `AccessKeySecret` as `email` & `
|
|||||||
"sub_domains": ["www","test"]
|
"sub_domains": ["www","test"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"ip_url": "https://myip.biturl.top",
|
||||||
"interval": 300,
|
"interval": 300,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Config example for DuckDNS
|
#### DuckDNS
|
||||||
|
|
||||||
For DuckDNS, only need to provide the `token`, config 1 default domain & subdomains.
|
For DuckDNS, only need to provide the `token`, config 1 default domain & subdomains.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Example</summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"provider": "DuckDNS",
|
"provider": "DuckDNS",
|
||||||
@ -274,16 +340,46 @@ For DuckDNS, only need to provide the `token`, config 1 default domain & subdoma
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"ip_url": "https://myip.biturl.top",
|
||||||
"interval": 300,
|
"interval": 300,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Config example for HE.net
|
#### No-IP
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Example</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"provider": "NoIP",
|
||||||
|
"email": "mail@example.com",
|
||||||
|
"password": "YourPassword",
|
||||||
|
"domains": [
|
||||||
|
{
|
||||||
|
"domain_name": "ddns.net",
|
||||||
|
"sub_domains": ["timothyye6"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ip_type": "IPv4",
|
||||||
|
"ip_url": "https://myip.biturl.top",
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
|
"interval": 300,
|
||||||
|
"socks5_proxy": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### HE.net
|
||||||
|
|
||||||
For HE, email is not needed, just fill DDNS key to password, and config all the domains & subdomains.
|
For HE, email is not needed, just fill DDNS key to password, and config all the domains & subdomains.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Example</summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"provider": "HE",
|
"provider": "HE",
|
||||||
@ -297,43 +393,37 @@ For HE, email is not needed, just fill DDNS key to password, and config all the
|
|||||||
"sub_domains": ["www","test"]
|
"sub_domains": ["www","test"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"resolver": "8.8.8.8",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"ip_url": "https://myip.biturl.top",
|
||||||
"interval": 300,
|
"interval": 300,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### HE.net DDNS configuration
|
<details>
|
||||||
|
<summary>Provider configuration</summary>
|
||||||
|
|
||||||
Add a new "A record", make sure that "Enable entry for dynamic dns" is checked:
|
Add a new "A record" and make sure that "Enable entry for dynamic dns" is checked:
|
||||||
|
|
||||||
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/he1.png?raw=true" width="640" />
|
<img src="./snapshots/he1.png" width="640" />
|
||||||
|
|
||||||
Fill your own DDNS key or generate a random DDNS key for this new created "A record":
|
Fill in your own DDNS key or generate a random DDNS key for this new created "A record":
|
||||||
|
|
||||||
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/he2.png?raw=true" width="640" />
|
<img src="./snapshots/he2.png" width="640" />
|
||||||
|
|
||||||
Remember the DDNS key and fill it as password to the config.json.
|
Remember the DDNS key and set it in the `password` property in the configuration file.
|
||||||
|
|
||||||
__NOTICE__: If you have multiple domains or subdomains, make sure their DDNS key are the same.
|
__NOTICE__: If you have multiple domains or subdomains, make sure their DDNS key are the same.
|
||||||
|
</details>
|
||||||
|
|
||||||
### Get an IP address from the interface
|
### Notifications
|
||||||
|
|
||||||
For some reasons if you want to get an IP directly from the interface, say `eth0` for Linux or `Local Area Connection` for Windows, update config file like this:
|
GoDNS can send a notification each time the IP changes.
|
||||||
|
|
||||||
```json
|
#### Email
|
||||||
"ip_url": "",
|
|
||||||
"ip_interface": "eth0",
|
|
||||||
```
|
|
||||||
|
|
||||||
If you set both `ip_url` and `ip_interface`, it first tries to get an IP address online, and if not succeed, gets
|
Emails are sent over [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol). Update your configuration with the following snippet:
|
||||||
an IP address from the interface as a fallback.
|
|
||||||
|
|
||||||
Note that IPv6 address will be ignored currently.
|
|
||||||
|
|
||||||
### Email notification support
|
|
||||||
|
|
||||||
Update config file and provide your SMTP options, a notification mail will be sent to your mailbox once the IP is changed and updated.
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"notify": {
|
"notify": {
|
||||||
@ -348,13 +438,13 @@ Update config file and provide your SMTP options, a notification mail will be se
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notification mail example:
|
Each time the IP changes, you will receive an email like that:
|
||||||
|
|
||||||
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/mail.png?raw=true" />
|
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/mail.png?raw=true" />
|
||||||
|
|
||||||
### Telegram notification support
|
#### Telegram
|
||||||
|
|
||||||
Update config file and provide your Telegram options, a notification message will be sent to your telegram channel once the IP is changed and updated.
|
To receive a [Telegram](https://telegram.org/) message each time the IP changes, update your configuration with the following snippet:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"notify": {
|
"notify": {
|
||||||
@ -362,81 +452,168 @@ Update config file and provide your Telegram options, a notification message wil
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"bot_api_key": "11111:aaaa-bbbb",
|
"bot_api_key": "11111:aaaa-bbbb",
|
||||||
"chat_id": "-123456",
|
"chat_id": "-123456",
|
||||||
"message_template": "Domain *{{ .Domain }}* is updated to %0A{{ .CurrentIP }}"
|
"message_template": "Domain *{{ .Domain }}* is updated to %0A{{ .CurrentIP }}",
|
||||||
|
"use_proxy": false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Markdown is supported in message template, and use `%0A` for newline.
|
|
||||||
|
|
||||||
### SOCKS5 proxy support
|
The `message_template` property supports [markdown](https://www.markdownguide.org). New lines needs to be escaped with `%0A`.
|
||||||
|
|
||||||
You can also use SOCKS5 proxy, just fill SOCKS5 address to the ```socks5_proxy``` item:
|
#### Slack
|
||||||
|
|
||||||
|
To receive a [Slack](https://slack.com) message each time the IP changes, update your configuration with the following snippet:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"socks5_proxy": "127.0.0.1:7070"
|
"notify": {
|
||||||
|
"slack": {
|
||||||
|
"enabled": true,
|
||||||
|
"bot_api_token": "xoxb-xxx",
|
||||||
|
"channel": "your_channel",
|
||||||
|
"message_template": "Domain *{{ .Domain }}* is updated to \n{{ .CurrentIP }}",
|
||||||
|
"use_proxy": false
|
||||||
|
},
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now all the queries will go through the specified SOCKS5 proxy.
|
The `message_template` property supports [markdown](https://www.markdownguide.org). New lines needs to be escaped with `\n`.
|
||||||
|
|
||||||
## Run it as a daemon manually
|
### 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
|
```bash
|
||||||
nohup ./godns &
|
nohup ./godns &
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run it as a daemon, manage it via Upstart
|
Note: when the program stops, it will not be restarted.
|
||||||
|
|
||||||
* Install `upstart` first
|
### As a managed daemon (with upstart)
|
||||||
* Copy `./upstart/godns.conf` to `/etc/init`
|
|
||||||
* Start it as a system service:
|
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
|
```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.
|
1. Download the latest version of [NSSM](https://nssm.cc/download)
|
||||||
* Copy `./systemd/godns.service` to `/lib/systemd/system`
|
|
||||||
* Start it as a systemd service:
|
|
||||||
|
|
||||||
```bash
|
2. In an administrative prompt, from the folder where NSSM was downloaded, e.g. `C:\Downloads\nssm\` **win64**, run:
|
||||||
sudo systemctl enable godns
|
|
||||||
sudo systemctl start godns
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run it with docker
|
```
|
||||||
|
nssm install YOURSERVICENAME
|
||||||
|
```
|
||||||
|
|
||||||
Now godns supports to run in docker.
|
3. Follow the interface to configure the service. In the "Application" tab just indicate where the `godns.exe` file is. Optionally you can also define a description on the "Details" tab and define a log file on the "I/O" tab. Finish by clicking on the "Install service" button.
|
||||||
|
|
||||||
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
|
4. The service will now start along Windows.
|
||||||
* Rename it to **config.json**.
|
|
||||||
* Run GoDNS with docker:
|
|
||||||
|
|
||||||
```bash
|
Note: you can uninstall the service by running:
|
||||||
docker run -d --name godns --restart=always \
|
|
||||||
-v /path/to/config.json:/usr/local/godns/config.json timothyye/godns:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run it as a Windows service
|
|
||||||
|
|
||||||
After creating your config.json file:
|
|
||||||
|
|
||||||
* Get the latest [NSSM](https://nssm.cc/download) for create a windows service.
|
|
||||||
* Open the command prompt (as administrator) in the folder where you downloaded NSSM (e.g. C:\Downloads\nssm\ **win64**) and run:
|
|
||||||
|
|
||||||
```
|
|
||||||
nssm install YOURSERVICENAME
|
|
||||||
```
|
|
||||||
|
|
||||||
You will have an interface to configure your service, it is very simple in the "Application" tab just indicate where your `godns.exe` file is. Optionally you can also define a description on the "Details" tab and define a log file on the "I/O" tab.
|
|
||||||
|
|
||||||
* Finish using the "Install service" button.
|
|
||||||
* Done. Now whenever windows start your service will be loaded.
|
|
||||||
* To uninstall:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
nssm remove YOURSERVICENAME
|
nssm remove YOURSERVICENAME
|
||||||
```
|
```
|
||||||
|
|
||||||
## Enjoy it!
|
## Special Thanks
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/xhe5RLZ.jpg" width="80px" align="right" />
|
||||||
|
|
||||||
|
Thanks JetBrains for sponsoring this project with [free open source license](https://www.jetbrains.com/community/opensource/).
|
||||||
|
|
||||||
|
> I like GoLand, it is an amazing and productive tool.
|
||||||
|
@ -50,17 +50,17 @@ func dnsLoop() {
|
|||||||
panicChan := make(chan godns.Domain)
|
panicChan := make(chan godns.Domain)
|
||||||
|
|
||||||
log.Println("Creating DNS handler with provider:", configuration.Provider)
|
log.Println("Creating DNS handler with provider:", configuration.Provider)
|
||||||
handler := handler.CreateHandler(configuration.Provider)
|
h := handler.CreateHandler(configuration.Provider)
|
||||||
handler.SetConfiguration(&configuration)
|
h.SetConfiguration(&configuration)
|
||||||
for i := range configuration.Domains {
|
for i := range configuration.Domains {
|
||||||
go handler.DomainLoop(&configuration.Domains[i], panicChan)
|
go h.DomainLoop(&configuration.Domains[i], panicChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
panicCount := 0
|
panicCount := 0
|
||||||
for {
|
for {
|
||||||
failDomain := <-panicChan
|
failDomain := <-panicChan
|
||||||
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
|
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
|
||||||
go handler.DomainLoop(&failDomain, panicChan)
|
go h.DomainLoop(&failDomain, panicChan)
|
||||||
|
|
||||||
panicCount++
|
panicCount++
|
||||||
if panicCount >= godns.PanicMax {
|
if panicCount >= godns.PanicMax {
|
||||||
|
@ -22,15 +22,18 @@
|
|||||||
"ipv6_url": "https://api-ipv6.ip.sb/ip",
|
"ipv6_url": "https://api-ipv6.ip.sb/ip",
|
||||||
"ip_type": "IPv4",
|
"ip_type": "IPv4",
|
||||||
"interval": 300,
|
"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",
|
"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",
|
"ip_interface": "eth0",
|
||||||
"socks5_proxy": "",
|
"socks5_proxy": "",
|
||||||
|
"use_proxy": false,
|
||||||
"notify": {
|
"notify": {
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"bot_api_key": "",
|
"bot_api_key": "",
|
||||||
"chat_id": "",
|
"chat_id": "",
|
||||||
"message_template": ""
|
"message_template": "",
|
||||||
|
"use_proxy": false
|
||||||
},
|
},
|
||||||
"mail": {
|
"mail": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
6
go.mod
6
go.mod
@ -3,12 +3,14 @@ module github.com/TimothyYe/godns
|
|||||||
require (
|
require (
|
||||||
github.com/bitly/go-simplejson v0.5.0
|
github.com/bitly/go-simplejson v0.5.0
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
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/fatih/color v1.7.0
|
||||||
|
github.com/google/uuid v1.1.1
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.4 // indirect
|
github.com/mattn/go-isatty v0.0.4 // indirect
|
||||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc
|
github.com/miekg/dns v1.1.29
|
||||||
golang.org/x/sys v0.0.0-20190226215855-775f8194d0f9 // indirect
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
)
|
)
|
||||||
|
23
go.sum
23
go.sum
@ -2,8 +2,12 @@ github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkN
|
|||||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
github.com/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 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
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 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@ -13,10 +17,29 @@ github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRU
|
|||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-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 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
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 h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
|
||||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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 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-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 h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
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 h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||||
|
@ -74,7 +74,7 @@ func getHTTPBody(url string) ([]byte, error) {
|
|||||||
if resp.StatusCode == http.StatusOK {
|
if resp.StatusCode == http.StatusOK {
|
||||||
return body, err
|
return body, err
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Status %d, Error:%s", resp.StatusCode, body)
|
return nil, fmt.Errorf("status %d, Error:%s", resp.StatusCode, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAliDNS function creates instance of AliDNS and return
|
// NewAliDNS function creates instance of AliDNS and return
|
||||||
@ -92,9 +92,8 @@ func NewAliDNS(key, secret string) *AliDNS {
|
|||||||
func (d *AliDNS) GetDomainRecords(domain, rr string) []DomainRecord {
|
func (d *AliDNS) GetDomainRecords(domain, rr string) []DomainRecord {
|
||||||
resp := &domainRecordsResp{}
|
resp := &domainRecordsResp{}
|
||||||
parms := map[string]string{
|
parms := map[string]string{
|
||||||
"Action": "DescribeDomainRecords",
|
"Action": "DescribeSubDomainRecords",
|
||||||
"DomainName": domain,
|
"SubDomain": fmt.Sprintf("%s.%s", rr, domain),
|
||||||
"RRKeyWord": rr,
|
|
||||||
}
|
}
|
||||||
urlPath := d.genRequestURL(parms)
|
urlPath := d.genRequestURL(parms)
|
||||||
body, err := getHTTPBody(urlPath)
|
body, err := getHTTPBody(urlPath)
|
||||||
@ -124,7 +123,7 @@ func (d *AliDNS) UpdateDomainRecord(r DomainRecord) error {
|
|||||||
|
|
||||||
urlPath := d.genRequestURL(parms)
|
urlPath := d.genRequestURL(parms)
|
||||||
if urlPath == "" {
|
if urlPath == "" {
|
||||||
return errors.New("Failed to generate request URL")
|
return errors.New("failed to generate request URL")
|
||||||
}
|
}
|
||||||
_, err := getHTTPBody(urlPath)
|
_, err := getHTTPBody(urlPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -134,7 +133,7 @@ func (d *AliDNS) UpdateDomainRecord(r DomainRecord) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliDNS) genRequestURL(parms map[string]string) string {
|
func (d *AliDNS) genRequestURL(parms map[string]string) string {
|
||||||
pArr := []string{}
|
var pArr []string
|
||||||
ps := map[string]string{}
|
ps := map[string]string{}
|
||||||
for k, v := range publicParm {
|
for k, v := range publicParm {
|
||||||
ps[k] = v
|
ps[k] = v
|
||||||
|
@ -28,10 +28,17 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var lastIP string
|
looping := false
|
||||||
aliDNS := NewAliDNS(handler.Configuration.Email, handler.Configuration.Password)
|
aliDNS := NewAliDNS(handler.Configuration.Email, handler.Configuration.Password)
|
||||||
|
|
||||||
for {
|
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)
|
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -39,14 +46,19 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Println("currentIP is:", currentIP)
|
log.Println("currentIP is:", currentIP)
|
||||||
|
for _, subDomain := range domain.SubDomains {
|
||||||
|
hostname := subDomain + "." + domain.DomainName
|
||||||
|
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//check against currently known IP, if no change, skip update
|
||||||
|
if currentIP == lastIP {
|
||||||
|
log.Printf("IP is the same as cached one. Skip update.\n")
|
||||||
|
} else {
|
||||||
|
lastIP = currentIP
|
||||||
|
|
||||||
//check against locally cached IP, if no change, skip update
|
|
||||||
if currentIP == lastIP {
|
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
|
||||||
} else {
|
|
||||||
lastIP = currentIP
|
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
||||||
records := aliDNS.GetDomainRecords(domain.DomainName, subDomain)
|
records := aliDNS.GetDomainRecords(domain.DomainName, subDomain)
|
||||||
if records == nil || len(records) == 0 {
|
if records == nil || len(records) == 0 {
|
||||||
@ -68,9 +80,6 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,15 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var lastIP string
|
var lastIP string
|
||||||
|
looping := false
|
||||||
for {
|
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)
|
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error in GetCurrentIP:", err)
|
log.Println("Error in GetCurrentIP:", err)
|
||||||
@ -88,8 +96,6 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
if currentIP == lastIP {
|
if currentIP == lastIP {
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
log.Printf("IP is the same as cached one. Skip update.\n")
|
||||||
} else {
|
} else {
|
||||||
lastIP = currentIP
|
|
||||||
|
|
||||||
log.Println("Checking IP for domain", domain.DomainName)
|
log.Println("Checking IP for domain", domain.DomainName)
|
||||||
zoneID := handler.getZone(domain.DomainName)
|
zoneID := handler.getZone(domain.DomainName)
|
||||||
if zoneID != "" {
|
if zoneID != "" {
|
||||||
@ -103,7 +109,7 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
if rec.IP != currentIP {
|
if rec.IP != currentIP {
|
||||||
log.Printf("IP mismatch: Current(%+v) vs Cloudflare(%+v)\r\n", currentIP, rec.IP)
|
log.Printf("IP mismatch: Current(%+v) vs Cloudflare(%+v)\r\n", currentIP, rec.IP)
|
||||||
handler.updateRecord(rec, currentIP)
|
lastIP = handler.updateRecord(rec, currentIP)
|
||||||
|
|
||||||
// Send notification
|
// Send notification
|
||||||
if err := godns.SendNotify(handler.Configuration, rec.Name, currentIP); err != nil {
|
if err := godns.SendNotify(handler.Configuration, rec.Name, currentIP); err != nil {
|
||||||
@ -117,14 +123,14 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
log.Println("Failed to find zone for domain:", domain.DomainName)
|
log.Println("Failed to find zone for domain:", domain.DomainName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sleep with interval
|
|
||||||
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
|
||||||
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if record is present in domain conf
|
// Check if record is present in domain conf
|
||||||
func recordTracked(domain *godns.Domain, record *DNSRecord) bool {
|
func recordTracked(domain *godns.Domain, record *DNSRecord) bool {
|
||||||
|
if record.Name == domain.DomainName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
for _, subDomain := range domain.SubDomains {
|
for _, subDomain := range domain.SubDomains {
|
||||||
sd := fmt.Sprintf("%s.%s", subDomain, domain.DomainName)
|
sd := fmt.Sprintf("%s.%s", subDomain, domain.DomainName)
|
||||||
if record.Name == sd {
|
if record.Name == sd {
|
||||||
@ -137,7 +143,7 @@ func recordTracked(domain *godns.Domain, record *DNSRecord) bool {
|
|||||||
|
|
||||||
// Create a new request with auth in place and optional proxy
|
// 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) {
|
func (handler *Handler) newRequest(method, url string, body io.Reader) (*http.Request, *http.Client) {
|
||||||
client := godns.GetHttpClient(handler.Configuration)
|
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
||||||
if client == nil {
|
if client == nil {
|
||||||
log.Println("cannot create HTTP client")
|
log.Println("cannot create HTTP client")
|
||||||
}
|
}
|
||||||
@ -160,7 +166,7 @@ func (handler *Handler) getZone(domain string) string {
|
|||||||
|
|
||||||
var z ZoneResponse
|
var z ZoneResponse
|
||||||
|
|
||||||
req, client := handler.newRequest("GET", "/zones", nil)
|
req, client := handler.newRequest("GET", fmt.Sprintf("/zones?name=%s", domain), nil)
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Request error:", err.Error())
|
log.Println("Request error:", err.Error())
|
||||||
@ -201,7 +207,7 @@ func (handler *Handler) getDNSRecords(zoneID string) []DNSRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Querying records with type:", recordType)
|
log.Println("Querying records with type:", recordType)
|
||||||
req, client := handler.newRequest("GET", fmt.Sprintf("/zones/"+zoneID+"/dns_records?type=%s", recordType), nil)
|
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)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Request error:", err.Error())
|
log.Println("Request error:", err.Error())
|
||||||
@ -225,10 +231,11 @@ func (handler *Handler) getDNSRecords(zoneID string) []DNSRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update DNS A Record with new IP
|
// Update DNS A Record with new IP
|
||||||
func (handler *Handler) updateRecord(record DNSRecord, newIP string) {
|
func (handler *Handler) updateRecord(record DNSRecord, newIP string) string {
|
||||||
|
|
||||||
var r DNSRecordUpdateResponse
|
var r DNSRecordUpdateResponse
|
||||||
record.SetIP(newIP)
|
record.SetIP(newIP)
|
||||||
|
var lastIP string
|
||||||
|
|
||||||
j, _ := json.Marshal(record)
|
j, _ := json.Marshal(record)
|
||||||
req, client := handler.newRequest("PUT",
|
req, client := handler.newRequest("PUT",
|
||||||
@ -238,7 +245,7 @@ func (handler *Handler) updateRecord(record DNSRecord, newIP string) {
|
|||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Request error:", err.Error())
|
log.Println("Request error:", err.Error())
|
||||||
return
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
@ -246,12 +253,14 @@ func (handler *Handler) updateRecord(record DNSRecord, newIP string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Decoder error: %+v\n", err)
|
log.Printf("Decoder error: %+v\n", err)
|
||||||
log.Printf("Response body: %+v\n", string(body))
|
log.Printf("Response body: %+v\n", string(body))
|
||||||
return
|
return ""
|
||||||
}
|
}
|
||||||
if r.Success != true {
|
if r.Success != true {
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
log.Printf("Response failed: %+v\n", string(body))
|
log.Printf("Response failed: %+v\n", string(body))
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Record updated: %+v - %+v", record.Name, record.IP)
|
log.Printf("Record updated: %+v - %+v", record.Name, record.IP)
|
||||||
|
lastIP = record.IP
|
||||||
}
|
}
|
||||||
|
return lastIP
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
"github.com/TimothyYe/godns"
|
||||||
simplejson "github.com/bitly/go-simplejson"
|
"github.com/bitly/go-simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler struct definition
|
// Handler struct definition
|
||||||
@ -36,8 +36,16 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var lastIP string
|
looping := false
|
||||||
for {
|
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)
|
log.Printf("Checking IP for domain %s \r\n", domain.DomainName)
|
||||||
domainID := handler.GetDomain(domain.DomainName)
|
domainID := handler.GetDomain(domain.DomainName)
|
||||||
|
|
||||||
@ -53,13 +61,19 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
log.Println("currentIP is:", currentIP)
|
log.Println("currentIP is:", currentIP)
|
||||||
|
|
||||||
//check against locally cached IP, if no change, skip update
|
for _, subDomain := range domain.SubDomains {
|
||||||
if currentIP == lastIP {
|
hostname := subDomain + "." + domain.DomainName
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
||||||
} else {
|
if err != nil {
|
||||||
lastIP = currentIP
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
//check against currently known IP, if no change, skip update
|
||||||
|
if currentIP == lastIP {
|
||||||
|
log.Printf("IP is the same as cached one. Skip update.\n")
|
||||||
|
} else {
|
||||||
|
lastIP = currentIP
|
||||||
|
|
||||||
subDomainID, ip := handler.GetSubDomain(domainID, subDomain)
|
subDomainID, ip := handler.GetSubDomain(domainID, subDomain)
|
||||||
|
|
||||||
@ -82,9 +96,6 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +177,15 @@ func (handler *Handler) GetSubDomain(domainID int64, name string) (string, strin
|
|||||||
value.Add("length", "1")
|
value.Add("length", "1")
|
||||||
value.Add("sub_domain", name)
|
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)
|
response, err := handler.PostData("/Record.List", value)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -213,7 +233,7 @@ func (handler *Handler) UpdateIP(domainID int64, subDomainID string, subDomainNa
|
|||||||
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
||||||
value.Add("record_type", "AAAA")
|
value.Add("record_type", "AAAA")
|
||||||
} else {
|
} else {
|
||||||
log.Println("Error: must specify \"ip_type\" in config for DNSPod.");
|
log.Println("Error: must specify \"ip_type\" in config for DNSPod.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +265,7 @@ func (handler *Handler) UpdateIP(domainID int64, subDomainID string, subDomainNa
|
|||||||
|
|
||||||
// PostData post data and invoke DNSPod API
|
// PostData post data and invoke DNSPod API
|
||||||
func (handler *Handler) PostData(url string, content url.Values) (string, error) {
|
func (handler *Handler) PostData(url string, content url.Values) (string, error) {
|
||||||
client := godns.GetHttpClient(handler.Configuration)
|
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
||||||
|
|
||||||
if client == nil {
|
if client == nil {
|
||||||
return "", errors.New("failed to create HTTP client")
|
return "", errors.New("failed to create HTTP client")
|
||||||
|
138
handler/dreamhost/dreamhost_handler.go
Normal file
138
handler/dreamhost/dreamhost_handler.go
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,32 +35,45 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var lastIP string
|
looping := false
|
||||||
|
|
||||||
for {
|
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)
|
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("get_currentIP:", err)
|
log.Println("get_currentIP:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("currentIP is:", currentIP)
|
log.Println("currentIP is:", currentIP)
|
||||||
|
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
||||||
|
var ip string
|
||||||
|
|
||||||
//check against locally cached IP, if no change, skip update
|
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
|
||||||
if currentIP == lastIP {
|
ip = fmt.Sprintf("ip=%s", currentIP)
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
||||||
} else {
|
ip = fmt.Sprintf("ipv6=%s", currentIP)
|
||||||
lastIP = currentIP
|
}
|
||||||
client := godns.GetHttpClient(handler.Configuration)
|
|
||||||
var ip string
|
|
||||||
|
|
||||||
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
|
for _, subDomain := range domain.SubDomains {
|
||||||
ip = fmt.Sprintf("ip=%s", currentIP)
|
hostname := subDomain + "." + domain.DomainName
|
||||||
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
||||||
ip = fmt.Sprintf("ipv6=%s", currentIP)
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
//check against currently known IP, if no change, skip update
|
||||||
|
if currentIP == lastIP {
|
||||||
|
log.Printf("IP is the same as cached one. Skip update.\n")
|
||||||
|
} else {
|
||||||
// update IP with HTTP GET request
|
// update IP with HTTP GET request
|
||||||
resp, err := client.Get(fmt.Sprintf(DuckUrl, subDomain, handler.Configuration.LoginToken, ip))
|
resp, err := client.Get(fmt.Sprintf(DuckUrl, subDomain, handler.Configuration.LoginToken, ip))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -85,9 +98,5 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,25 +36,35 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var lastIP string
|
looping := false
|
||||||
|
|
||||||
for {
|
for {
|
||||||
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
if looping {
|
||||||
|
// Sleep with interval
|
||||||
|
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
||||||
|
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
looping = true
|
||||||
|
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("get_currentIP:", err)
|
log.Println("get_currentIP:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Println("currentIP is:", currentIP)
|
log.Println("currentIP is:", currentIP)
|
||||||
|
for _, subDomain := range domain.SubDomains {
|
||||||
|
hostname := subDomain + "." + domain.DomainName
|
||||||
|
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
//check against locally cached IP, if no change, skip update
|
//check against currently known IP, if no change, skip update
|
||||||
if currentIP == lastIP {
|
if currentIP == lastIP {
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
log.Printf("IP is the same as cached one. Skip update.\n")
|
||||||
} else {
|
} else {
|
||||||
lastIP = currentIP
|
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
log.Printf("[%s.%s] Start to update record IP...\n", subDomain, domain.DomainName)
|
|
||||||
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
|
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
|
||||||
|
|
||||||
// Send notification
|
// Send notification
|
||||||
@ -63,16 +73,13 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sleep with interval
|
|
||||||
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
|
||||||
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateIP update subdomain with current IP
|
// UpdateIP update subdomain with current IP
|
||||||
func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
|
func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
|
||||||
client := godns.GetHttpClient(handler.Configuration)
|
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
||||||
resp, err := client.Get(fmt.Sprintf(GoogleURL,
|
resp, err := client.Get(fmt.Sprintf(GoogleURL,
|
||||||
handler.Configuration.Email,
|
handler.Configuration.Email,
|
||||||
handler.Configuration.Password,
|
handler.Configuration.Password,
|
||||||
@ -83,6 +90,7 @@ func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// handle error
|
// handle error
|
||||||
log.Print("Failed to update sub domain:", subDomain)
|
log.Print("Failed to update sub domain:", subDomain)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
@ -5,9 +5,11 @@ import (
|
|||||||
"github.com/TimothyYe/godns/handler/alidns"
|
"github.com/TimothyYe/godns/handler/alidns"
|
||||||
"github.com/TimothyYe/godns/handler/cloudflare"
|
"github.com/TimothyYe/godns/handler/cloudflare"
|
||||||
"github.com/TimothyYe/godns/handler/dnspod"
|
"github.com/TimothyYe/godns/handler/dnspod"
|
||||||
|
"github.com/TimothyYe/godns/handler/dreamhost"
|
||||||
"github.com/TimothyYe/godns/handler/duck"
|
"github.com/TimothyYe/godns/handler/duck"
|
||||||
"github.com/TimothyYe/godns/handler/google"
|
"github.com/TimothyYe/godns/handler/google"
|
||||||
"github.com/TimothyYe/godns/handler/he"
|
"github.com/TimothyYe/godns/handler/he"
|
||||||
|
"github.com/TimothyYe/godns/handler/noip"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IHandler is the interface for all DNS handlers
|
// IHandler is the interface for all DNS handlers
|
||||||
@ -25,6 +27,8 @@ func CreateHandler(provider string) IHandler {
|
|||||||
handler = IHandler(&cloudflare.Handler{})
|
handler = IHandler(&cloudflare.Handler{})
|
||||||
case godns.DNSPOD:
|
case godns.DNSPOD:
|
||||||
handler = IHandler(&dnspod.Handler{})
|
handler = IHandler(&dnspod.Handler{})
|
||||||
|
case godns.DREAMHOST:
|
||||||
|
handler = IHandler(&dreamhost.Handler{})
|
||||||
case godns.HE:
|
case godns.HE:
|
||||||
handler = IHandler(&he.Handler{})
|
handler = IHandler(&he.Handler{})
|
||||||
case godns.ALIDNS:
|
case godns.ALIDNS:
|
||||||
@ -33,6 +37,8 @@ func CreateHandler(provider string) IHandler {
|
|||||||
handler = IHandler(&google.Handler{})
|
handler = IHandler(&google.Handler{})
|
||||||
case godns.DUCK:
|
case godns.DUCK:
|
||||||
handler = IHandler(&duck.Handler{})
|
handler = IHandler(&duck.Handler{})
|
||||||
|
case godns.NOIP:
|
||||||
|
handler = IHandler(&noip.Handler{})
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
@ -37,8 +37,15 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var lastIP string
|
looping := false
|
||||||
for {
|
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)
|
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -48,12 +55,19 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
log.Println("currentIP is:", currentIP)
|
log.Println("currentIP is:", currentIP)
|
||||||
|
|
||||||
//check against locally cached IP, if no change, skip update
|
//check against locally cached IP, if no change, skip update
|
||||||
if currentIP == lastIP {
|
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
|
||||||
} else {
|
|
||||||
lastIP = currentIP
|
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
for _, subDomain := range domain.SubDomains {
|
||||||
|
hostname := subDomain + "." + domain.DomainName
|
||||||
|
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//check against currently known IP, if no change, skip update
|
||||||
|
if currentIP == lastIP {
|
||||||
|
log.Printf("IP is the same as cached one. Skip update.\n")
|
||||||
|
} else {
|
||||||
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
||||||
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
|
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
|
||||||
|
|
||||||
@ -63,9 +77,6 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -77,7 +88,7 @@ func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
|
|||||||
values.Add("password", handler.Configuration.Password)
|
values.Add("password", handler.Configuration.Password)
|
||||||
values.Add("myip", currentIP)
|
values.Add("myip", currentIP)
|
||||||
|
|
||||||
client := godns.GetHttpClient(handler.Configuration)
|
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", HEUrl, strings.NewReader(values.Encode()))
|
req, _ := http.NewRequest("POST", HEUrl, strings.NewReader(values.Encode()))
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
|
114
handler/noip/noip_handler.go
Normal file
114
handler/noip/noip_handler.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package noip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TimothyYe/godns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// NoIPUrl the API address for NoIP
|
||||||
|
NoIPUrl = "https://%s:%s@dynupdate.no-ip.com/nic/update?hostname=%s&%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler struct
|
||||||
|
type Handler struct {
|
||||||
|
Configuration *godns.Settings
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfiguration pass dns settings and store it to handler instance
|
||||||
|
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
|
||||||
|
handler.Configuration = conf
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainLoop the main logic loop
|
||||||
|
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
||||||
|
panicChan <- *domain
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
looping := false
|
||||||
|
|
||||||
|
for {
|
||||||
|
if looping {
|
||||||
|
// Sleep with interval
|
||||||
|
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
||||||
|
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
looping = true
|
||||||
|
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("get_currentIP:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("currentIP is:", currentIP)
|
||||||
|
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
||||||
|
|
||||||
|
var ip string
|
||||||
|
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
|
||||||
|
ip = fmt.Sprintf("myip=%s", currentIP)
|
||||||
|
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
||||||
|
ip = fmt.Sprintf("myipv6=%s", currentIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, subDomain := range domain.SubDomains {
|
||||||
|
hostname := subDomain + "." + domain.DomainName
|
||||||
|
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//check against currently known IP, if no change, skip update
|
||||||
|
if currentIP == lastIP {
|
||||||
|
log.Printf("IP is the same as cached one. Skip update.\n")
|
||||||
|
} else {
|
||||||
|
req, _ := http.NewRequest("GET", fmt.Sprintf(
|
||||||
|
NoIPUrl,
|
||||||
|
handler.Configuration.Email,
|
||||||
|
handler.Configuration.Password,
|
||||||
|
hostname,
|
||||||
|
ip), nil)
|
||||||
|
|
||||||
|
if handler.Configuration.UserAgent != "" {
|
||||||
|
req.Header.Add("User-Agent", handler.Configuration.UserAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update IP with HTTP GET request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
log.Print("Failed to update sub domain:", subDomain)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil || !strings.Contains(string(body), "good") {
|
||||||
|
log.Println("Failed to update the IP")
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
log.Print("IP updated to:", currentIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send notification
|
||||||
|
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
|
||||||
|
log.Println("Failed to send notification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
110
resolver/resolver.go
Normal file
110
resolver/resolver.go
Normal 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
45
resolver/resolver_test.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
25
settings.go
25
settings.go
@ -12,12 +12,22 @@ type Domain struct {
|
|||||||
SubDomains []string `json:"sub_domains"`
|
SubDomains []string `json:"sub_domains"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify struct for slack notification
|
||||||
|
type SlackNotify struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
BotApiToken string `json:"bot_api_token"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
MsgTemplate string `json:"message_template"`
|
||||||
|
UseProxy bool `json:"use_proxy"`
|
||||||
|
}
|
||||||
|
|
||||||
// Notify struct for telegram notification
|
// Notify struct for telegram notification
|
||||||
type TelegramNotify struct {
|
type TelegramNotify struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
BotApiKey string `json:"bot_api_key"`
|
BotApiKey string `json:"bot_api_key"`
|
||||||
ChatId string `json:"chat_id"`
|
ChatId string `json:"chat_id"`
|
||||||
MsgTemplate string `json:"message_template"`
|
MsgTemplate string `json:"message_template"`
|
||||||
|
UseProxy bool `json:"use_proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify struct for SMTP notification
|
// Notify struct for SMTP notification
|
||||||
@ -32,8 +42,9 @@ type MailNotify struct {
|
|||||||
|
|
||||||
// Notify struct
|
// Notify struct
|
||||||
type Notify struct {
|
type Notify struct {
|
||||||
Telegram TelegramNotify `json:"telegram"`
|
Telegram TelegramNotify `json:"telegram"`
|
||||||
Mail MailNotify `json:"mail"`
|
Mail MailNotify `json:"mail"`
|
||||||
|
Slack SlackNotify `json:"slack"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings struct
|
// Settings struct
|
||||||
@ -52,6 +63,8 @@ type Settings struct {
|
|||||||
Notify Notify `json:"notify"`
|
Notify Notify `json:"notify"`
|
||||||
IPInterface string `json:"ip_interface"`
|
IPInterface string `json:"ip_interface"`
|
||||||
IPType string `json:"ip_type"`
|
IPType string `json:"ip_type"`
|
||||||
|
Resolver string `json:"resolver"`
|
||||||
|
UseProxy bool `json:"use_proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadSettings -- Load settings from config file
|
// LoadSettings -- Load settings from config file
|
||||||
|
190
utils.go
190
utils.go
@ -10,10 +10,14 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
dnsResolver "github.com/TimothyYe/godns/resolver"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
gomail "gopkg.in/gomail.v2"
|
"gopkg.in/gomail.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -48,6 +52,10 @@ const (
|
|||||||
GOOGLE = "Google"
|
GOOGLE = "Google"
|
||||||
// DUCK for Duck DNS
|
// DUCK for Duck DNS
|
||||||
DUCK = "DuckDNS"
|
DUCK = "DuckDNS"
|
||||||
|
// DREAMHOST for Dreamhost
|
||||||
|
DREAMHOST = "Dreamhost"
|
||||||
|
// NOIP for NoIP
|
||||||
|
NOIP = "NoIP"
|
||||||
// IPV4 for IPV4 mode
|
// IPV4 for IPV4 mode
|
||||||
IPV4 = "IPV4"
|
IPV4 = "IPV4"
|
||||||
// IPV6 for IPV6 mode
|
// IPV6 for IPV6 mode
|
||||||
@ -91,7 +99,7 @@ func GetIPFromInterface(configuration *Settings) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isIPv4(ip.String()) {
|
if isIPv4(ip.String()) {
|
||||||
if strings.ToUpper(configuration.IPType) == IPV4 {
|
if strings.ToUpper(configuration.IPType) != IPV4 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -100,8 +108,9 @@ func GetIPFromInterface(configuration *Settings) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip.String(), nil
|
if ip.String() != "" {
|
||||||
|
return ip.String(), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
|
return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
|
||||||
}
|
}
|
||||||
@ -111,10 +120,10 @@ func isIPv4(ip string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetHttpClient creates the HTTP client and return it
|
// GetHttpClient creates the HTTP client and return it
|
||||||
func GetHttpClient(configuration *Settings) *http.Client {
|
func GetHttpClient(configuration *Settings, useProxy bool) *http.Client {
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
|
|
||||||
if configuration.Socks5Proxy != "" {
|
if useProxy && configuration.Socks5Proxy != "" {
|
||||||
log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
|
log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
|
||||||
dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
|
dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -159,19 +168,6 @@ func GetCurrentIP(configuration *Settings) (string, error) {
|
|||||||
func GetIPOnline(configuration *Settings) (string, error) {
|
func GetIPOnline(configuration *Settings) (string, error) {
|
||||||
client := &http.Client{}
|
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 {
|
|
||||||
log.Println("can't connect to the proxy:", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
httpTransport := &http.Transport{}
|
|
||||||
client.Transport = httpTransport
|
|
||||||
httpTransport.Dial = dialer.Dial
|
|
||||||
}
|
|
||||||
|
|
||||||
var response *http.Response
|
var response *http.Response
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -194,15 +190,16 @@ func GetIPOnline(configuration *Settings) (string, error) {
|
|||||||
|
|
||||||
// CheckSettings check the format of settings
|
// CheckSettings check the format of settings
|
||||||
func CheckSettings(config *Settings) error {
|
func CheckSettings(config *Settings) error {
|
||||||
if config.Provider == DNSPOD {
|
switch config.Provider {
|
||||||
|
case DNSPOD:
|
||||||
if config.Password == "" && config.LoginToken == "" {
|
if config.Password == "" && config.LoginToken == "" {
|
||||||
return errors.New("password or login token cannot be empty")
|
return errors.New("password or login token cannot be empty")
|
||||||
}
|
}
|
||||||
} else if config.Provider == HE {
|
case HE:
|
||||||
if config.Password == "" {
|
if config.Password == "" {
|
||||||
return errors.New("password cannot be empty")
|
return errors.New("password cannot be empty")
|
||||||
}
|
}
|
||||||
} else if config.Provider == CLOUDFLARE {
|
case CLOUDFLARE:
|
||||||
if config.LoginToken == "" {
|
if config.LoginToken == "" {
|
||||||
if config.Email == "" {
|
if config.Email == "" {
|
||||||
return errors.New("email cannot be empty")
|
return errors.New("email cannot be empty")
|
||||||
@ -211,34 +208,42 @@ func CheckSettings(config *Settings) error {
|
|||||||
return errors.New("password cannot be empty")
|
return errors.New("password cannot be empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if config.Provider == ALIDNS {
|
case ALIDNS:
|
||||||
if config.Email == "" {
|
if config.Email == "" {
|
||||||
return errors.New("email cannot be empty")
|
return errors.New("email cannot be empty")
|
||||||
}
|
}
|
||||||
if config.Password == "" {
|
if config.Password == "" {
|
||||||
return errors.New("password cannot be empty")
|
return errors.New("password cannot be empty")
|
||||||
}
|
}
|
||||||
} else if config.Provider == DUCK {
|
case DUCK:
|
||||||
if config.LoginToken == "" {
|
if config.LoginToken == "" {
|
||||||
return errors.New("login token cannot be empty")
|
return errors.New("login token cannot be empty")
|
||||||
}
|
}
|
||||||
} else if config.Provider == GOOGLE {
|
case GOOGLE:
|
||||||
|
fallthrough
|
||||||
|
case NOIP:
|
||||||
if config.Email == "" {
|
if config.Email == "" {
|
||||||
return errors.New("email cannot be empty")
|
return errors.New("email cannot be empty")
|
||||||
}
|
}
|
||||||
if config.Password == "" {
|
if config.Password == "" {
|
||||||
return errors.New("password cannot be empty")
|
return errors.New("password cannot be empty")
|
||||||
}
|
}
|
||||||
} else {
|
case DREAMHOST:
|
||||||
return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNotify sends notify if IP is changed
|
// SendTelegramNotify sends notify if IP is changed
|
||||||
func SendTelegramNotify(configuration *Settings, domain, currentIP string) error {
|
func SendTelegramNotify(configuration *Settings, domain, currentIP string) error {
|
||||||
if ! configuration.Notify.Telegram.Enabled {
|
if !configuration.Notify.Telegram.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,22 +255,21 @@ func SendTelegramNotify(configuration *Settings, domain, currentIP string) error
|
|||||||
return errors.New("chat id cannot be empty")
|
return errors.New("chat id cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := GetHttpClient(configuration, configuration.Notify.Telegram.UseProxy)
|
||||||
client := GetHttpClient(configuration)
|
|
||||||
tpl := configuration.Notify.Telegram.MsgTemplate
|
tpl := configuration.Notify.Telegram.MsgTemplate
|
||||||
if tpl == "" {
|
if tpl == "" {
|
||||||
tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated"
|
tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated"
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := buildTemplate(currentIP, domain, tpl)
|
msg := buildTemplate(currentIP, domain, tpl)
|
||||||
url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s",
|
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.BotApiKey,
|
||||||
configuration.Notify.Telegram.ChatId,
|
configuration.Notify.Telegram.ChatId,
|
||||||
msg)
|
msg)
|
||||||
var response *http.Response
|
var response *http.Response
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
response, err = client.Get(url)
|
response, err = client.Get(reqURL)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -286,21 +290,21 @@ func SendTelegramNotify(configuration *Settings, domain, currentIP string) error
|
|||||||
Parameters *ResponseParameters `json:"parameters"`
|
Parameters *ResponseParameters `json:"parameters"`
|
||||||
}
|
}
|
||||||
var resp APIResponse
|
var resp APIResponse
|
||||||
err = json.Unmarshal([]byte(body), &resp)
|
err = json.Unmarshal(body, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error:", err)
|
fmt.Println("error:", err)
|
||||||
return errors.New("Failed to parse response")
|
return errors.New("failed to parse response")
|
||||||
}
|
}
|
||||||
if ! resp.Ok {
|
if !resp.Ok {
|
||||||
return errors.New(resp.Description)
|
return errors.New(resp.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNotify sends mail notify if IP is changed
|
// SendMailNotify sends mail notify if IP is changed
|
||||||
func SendMailNotify(configuration *Settings, domain, currentIP string) error {
|
func SendMailNotify(configuration *Settings, domain, currentIP string) error {
|
||||||
if ! configuration.Notify.Mail.Enabled {
|
if !configuration.Notify.Mail.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Print("Sending notification to:", configuration.Notify.Mail.SendTo)
|
log.Print("Sending notification to:", configuration.Notify.Mail.SendTo)
|
||||||
@ -313,7 +317,7 @@ func SendMailNotify(configuration *Settings, domain, currentIP string) error {
|
|||||||
log.Println("domain:", domain)
|
log.Println("domain:", domain)
|
||||||
m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate))
|
m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate))
|
||||||
|
|
||||||
d := gomail.NewPlainDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword)
|
d := gomail.NewDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword)
|
||||||
|
|
||||||
// Send the email config by sendlist .
|
// Send the email config by sendlist .
|
||||||
if err := d.DialAndSend(m); err != nil {
|
if err := d.DialAndSend(m); err != nil {
|
||||||
@ -322,16 +326,83 @@ func SendMailNotify(configuration *Settings, domain, currentIP string) error {
|
|||||||
return nil
|
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
|
// SendNotify sends notify if IP is changed
|
||||||
func SendNotify(configuration *Settings, domain, currentIP string) error {
|
func SendNotify(configuration *Settings, domain, currentIP string) error {
|
||||||
err := SendTelegramNotify(configuration, domain, currentIP)
|
err := SendTelegramNotify(configuration, domain, currentIP)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
log.Println("Send telegram notification with error:", err.Error())
|
log.Println("Send telegram notification with error:", err.Error())
|
||||||
}
|
}
|
||||||
err = SendMailNotify(configuration, domain, currentIP)
|
err = SendMailNotify(configuration, domain, currentIP)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
log.Println("Send email notification with error:", err.Error())
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,3 +429,34 @@ func buildTemplate(currentIP, domain string, tplsrc string) string {
|
|||||||
|
|
||||||
return tpl.String()
|
return tpl.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveDNS will query DNS for a given hostname.
|
||||||
|
func ResolveDNS(hostname, resolver, ipType string) (string, error) {
|
||||||
|
var dnsType uint16
|
||||||
|
if ipType == "" || strings.ToUpper(ipType) == IPV4 {
|
||||||
|
dnsType = dns.TypeA
|
||||||
|
} else {
|
||||||
|
dnsType = dns.TypeAAAA
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no DNS server is set in config file, falls back to default resolver.
|
||||||
|
if resolver == "" {
|
||||||
|
dnsAdress, err := net.LookupHost(hostname)
|
||||||
|
if err != nil {
|
||||||
|
return "<nil>", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dnsAdress[0], nil
|
||||||
|
}
|
||||||
|
res := dnsResolver.New([]string{resolver})
|
||||||
|
// In case of i/o timeout
|
||||||
|
res.RetryTimes = 5
|
||||||
|
|
||||||
|
ip, err := res.LookupHost(hostname, dnsType)
|
||||||
|
if err != nil {
|
||||||
|
return "<nil>", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip[0].String(), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGetCurrentIP(t *testing.T) {
|
func TestGetCurrentIP(t *testing.T) {
|
||||||
conf := &Settings{IPUrl: "http://members.3322.org/dyndns/getip"}
|
conf := &Settings{IPUrl: "https://myip.biturl.top"}
|
||||||
ip, _ := GetCurrentIP(conf)
|
ip, _ := GetCurrentIP(conf)
|
||||||
|
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
@ -13,13 +13,6 @@ func TestGetCurrentIP(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
t.Log("IP is:" + ip)
|
t.Log("IP is:" + ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
conf = &Settings{Socks5Proxy: "localhost:8899", IPUrl: "http://members.3322.org/dyndns/getip"}
|
|
||||||
ip, err := GetCurrentIP(conf)
|
|
||||||
|
|
||||||
if ip != "" && err == nil {
|
|
||||||
t.Error("should return error")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckSettings(t *testing.T) {
|
func TestCheckSettings(t *testing.T) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user