mirror of
https://github.com/taigrr/godns
synced 2025-01-18 04:03:25 -08:00
Compare commits
No commits in common. "master" and "V1.3" have entirely different histories.
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@ -1,12 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
otechie: # Replace with a single Otechie username
|
|
||||||
custom: https://www.paypal.me/timothyye
|
|
20
.github/workflows/go.yml
vendored
20
.github/workflows/go.yml
vendored
@ -1,20 +0,0 @@
|
|||||||
name: Go
|
|
||||||
on: [push]
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Set up Go 1.15.7
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.15.7
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: cd cmd/godns && go build -v .
|
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,6 +1,3 @@
|
|||||||
# System files on macOS
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
@ -29,13 +26,9 @@ config.json
|
|||||||
*.swp
|
*.swp
|
||||||
*.gz
|
*.gz
|
||||||
godns
|
godns
|
||||||
godns.exe
|
|
||||||
config.json
|
|
||||||
cmd/godns/config.json
|
|
||||||
|
|
||||||
|
vendor/*
|
||||||
/.idea
|
/.idea
|
||||||
/godns.iml
|
/godns.iml
|
||||||
/godns.ipr
|
/godns.ipr
|
||||||
/godns.iws
|
/godns.iws
|
||||||
.current_ip
|
|
||||||
.DS_Store
|
|
||||||
|
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
||||||
|
- 1.9
|
||||||
|
|
||||||
|
install:
|
||||||
|
go get -v
|
||||||
|
|
||||||
|
script:
|
||||||
|
- cp ./config_sample.json ./config.json
|
||||||
|
- go test -v ./...
|
@ -1,76 +0,0 @@
|
|||||||
# 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
|
|
18
Dockerfile
18
Dockerfile
@ -1,9 +1,9 @@
|
|||||||
FROM golang:alpine AS builder
|
FROM timothyye/alpine:3.6-glibc
|
||||||
RUN mkdir /godns
|
MAINTAINER Timothy
|
||||||
ADD . /godns/
|
RUN apk add --update ca-certificates
|
||||||
WORKDIR /godns
|
RUN mkdir -p /usr/local/godns
|
||||||
RUN CGO_ENABLED=0 go build -o godns cmd/godns/godns.go
|
COPY godns /usr/local/godns
|
||||||
|
RUN chmod +x /usr/local/godns/godns
|
||||||
FROM gcr.io/distroless/base
|
RUN rm -rf /var/cache/apk/*
|
||||||
COPY --from=builder /godns/godns /godns
|
WORKDIR /usr/local/godns
|
||||||
ENTRYPOINT ["/godns"]
|
ENTRYPOINT ["./godns", "-c", "/usr/local/godns/config.json"]
|
||||||
|
27
Makefile
27
Makefile
@ -2,43 +2,28 @@
|
|||||||
BINARY=godns
|
BINARY=godns
|
||||||
# Builds the project
|
# Builds the project
|
||||||
build:
|
build:
|
||||||
GO111MODULE=on go build -ldflags "-X main.Version=${VERSION}" -o ${BINARY} cmd/godns/godns.go
|
go build -o ${BINARY}
|
||||||
# Installs our project: copies binaries
|
# Installs our project: copies binaries
|
||||||
install:
|
install:
|
||||||
GO111MODULE=on go install
|
go install
|
||||||
image:
|
|
||||||
# Build docker image
|
|
||||||
go clean
|
|
||||||
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t timothyye/godns:${VERSION} . --push
|
|
||||||
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t timothyye/godns:latest . --push
|
|
||||||
release:
|
release:
|
||||||
# Clean
|
# Clean
|
||||||
go clean
|
go clean
|
||||||
rm -rf *.gz
|
rm -rf *.gz
|
||||||
# Build for mac
|
# Build for mac
|
||||||
GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
|
go build
|
||||||
tar czvf ${BINARY}-mac64-${VERSION}.tar.gz ./${BINARY}
|
tar czvf ${BINARY}-mac64-${VERSION}.tar.gz ./${BINARY}
|
||||||
# Build for linux
|
# Build for linux
|
||||||
go clean
|
go clean
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
|
||||||
tar czvf ${BINARY}-linux64-${VERSION}.tar.gz ./${BINARY}
|
tar czvf ${BINARY}-linux64-${VERSION}.tar.gz ./${BINARY}
|
||||||
# Build for arm
|
|
||||||
go clean
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
|
|
||||||
tar czvf ${BINARY}-arm64-${VERSION}.tar.gz ./${BINARY}
|
|
||||||
go clean
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
|
|
||||||
tar czvf ${BINARY}-arm-${VERSION}.tar.gz ./${BINARY}
|
|
||||||
# Build for win
|
# Build for win
|
||||||
go clean
|
go clean
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 GO111MODULE=on go build -ldflags "-s -w -X main.Version=${VERSION}" cmd/godns/godns.go
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
|
||||||
tar czvf ${BINARY}-win64-${VERSION}.tar.gz ./${BINARY}.exe
|
tar czvf ${BINARY}-win64-${VERSION}.tar.gz ./${BINARY}.exe
|
||||||
make image
|
go clean
|
||||||
# Cleans our projects: deletes binaries
|
# Cleans our projects: deletes binaries
|
||||||
clean:
|
clean:
|
||||||
go clean
|
go clean
|
||||||
rm -rf ./godns
|
|
||||||
rm -rf ./godns.exe
|
|
||||||
rm -rf *.gz
|
|
||||||
|
|
||||||
.PHONY: clean build
|
.PHONY: clean build
|
||||||
|
575
README.md
575
README.md
@ -7,302 +7,84 @@
|
|||||||
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
||||||
```
|
```
|
||||||
|
|
||||||
[![Apache licensed][9]][10] [![Docker][3]][4] [![Go Report Card][11]][12] [![Cover.Run][15]][16] [![GoDoc][13]][14]
|
[![Release][7]][8] [![MIT licensed][9]][10] [![Build Status][1]][2] [![Downloads][5]][6] [![Docker][3]][4] [![Go Report Card][11]][12]
|
||||||
|
|
||||||
|
[1]: https://travis-ci.org/TimothyYe/godns.svg?branch=master
|
||||||
|
[2]: https://travis-ci.org/TimothyYe/godns
|
||||||
[3]: https://images.microbadger.com/badges/image/timothyye/godns.svg
|
[3]: https://images.microbadger.com/badges/image/timothyye/godns.svg
|
||||||
[4]: https://microbadger.com/images/timothyye/godns
|
[4]: https://microbadger.com/images/timothyye/godns
|
||||||
|
[5]: https://img.shields.io/badge/downloads-1.95MB-brightgreen.svg
|
||||||
|
[6]: https://github.com/TimothyYe/godns/releases
|
||||||
|
[7]: https://img.shields.io/badge/release-v1.2-brightgreen.svg
|
||||||
|
[8]: https://github.com/TimothyYe/godns/releases
|
||||||
[9]: https://img.shields.io/badge/license-Apache-blue.svg
|
[9]: https://img.shields.io/badge/license-Apache-blue.svg
|
||||||
[10]: LICENSE
|
[10]: LICENSE
|
||||||
[11]: https://goreportcard.com/badge/github.com/timothyye/godns
|
[11]: https://goreportcard.com/badge/github.com/timothyye/godns
|
||||||
[12]: https://goreportcard.com/report/github.com/timothyye/godns
|
[12]: https://goreportcard.com/report/github.com/timothyye/godns
|
||||||
[13]: https://godoc.org/github.com/TimothyYe/godns?status.svg
|
|
||||||
[14]: https://godoc.org/github.com/TimothyYe/godns
|
|
||||||
[15]: https://img.shields.io/badge/cover.run-88.2%25-green.svg
|
|
||||||
[16]: https://cover.run/go/github.com/timothyye/godns
|
|
||||||
|
|
||||||
[GoDNS](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.
|
GoDNS is a dynamic DNS (DDNS) client tool, it is based on my early open source project: [DynDNS](https://github.com/TimothyYe/DynDNS).
|
||||||
|
|
||||||
Currently supports updating A records for subdomains. Doesn't support updating of root domains.
|
Now I rewrite [DynDNS](https://github.com/TimothyYe/DynDNS) by Golang and call it [GoDNS](https://github.com/TimothyYe/godns).
|
||||||
|
|
||||||
---
|
## Supported DNS Provider:
|
||||||
- [Supported DNS Providers](#supported-dns-providers)
|
* DNSPod ([https://www.dnspod.cn/](https://www.dnspod.cn/))
|
||||||
- [Supported Platforms](#supported-platforms)
|
* HE.net (Hurricane Electric) ([https://dns.he.net/](https://dns.he.net/))
|
||||||
- [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)
|
|
||||||
|
|
||||||
---
|
## MIPS32 platform
|
||||||
## Supported DNS Providers
|
|
||||||
|
|
||||||
| Provider | IPv4 support | IPv6 support | Root Domain | Subdomains |
|
For MIPS32 platform, please checkout the [mips32](https://github.com/TimothyYe/godns/tree/mips32) branch, this branch is contributed by [hguandl](https://github.com/hguandl), in this branch, the support for mips32 is added, which means it could run properly on Openwrt and LEDE.
|
||||||
| ------------------------------------- | :----------------: | :----------------: | :----------------: | :----------------: |
|
|
||||||
| [Cloudflare][cloudflare] | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
|
||||||
| [Google Domains][google.domains] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
|
||||||
| [DNSPod][dnspod] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
|
||||||
| [HE.net (Hurricane Electric)][he.net] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
|
||||||
| [AliDNS][alidns] | :white_check_mark: | :x: | :x: | :white_check_mark: |
|
|
||||||
| [DuckDNS][duckdns] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
|
||||||
| [Dreamhost][dreamhost] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
|
||||||
| [No-IP][no-ip] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
|
|
||||||
|
|
||||||
[cloudflare]: https://cloudflare.com
|
## Pre-condition
|
||||||
[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.
|
* Register and own a domain.
|
||||||
|
|
||||||
## Supported Platforms
|
* Domain's nameserver points to [DNSPod](https://www.dnspod.cn/) or [HE.net](https://dns.he.net/).
|
||||||
|
|
||||||
* Linux
|
## Build it
|
||||||
* MacOS
|
|
||||||
* ARM Linux (Raspberry Pi, etc.)
|
|
||||||
* Windows
|
|
||||||
* MIPS32 platform
|
|
||||||
|
|
||||||
To compile binaries for MIPS (mips or mipsle), run:
|
### Get & build it from source code
|
||||||
|
|
||||||
|
* Get source code from Github:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GOOS=linux GOARCH=mips/mipsle GOMIPS=softfloat go build -a
|
git clone https://github.com/timothyye/godns.git
|
||||||
```
|
```
|
||||||
|
* Go into the godns directory, get related library and then build it:
|
||||||
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
|
||||||
cd cmd/godns # go to the GoDNS directory
|
cd godns
|
||||||
go get -v # get dependencies
|
go get
|
||||||
go build # build
|
go build
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also download a compiled binary from the [releases](https://github.com/TimothyYe/godns/releases).
|
## Get help
|
||||||
## Usage
|
|
||||||
|
|
||||||
Print usage/help by running:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ./godns -h
|
$ ./godns -h
|
||||||
Usage of ./godns:
|
Usage of ./godns:
|
||||||
-c string
|
-c string
|
||||||
Specify a config file (default "./config.json")
|
Specify a config file (default "./config.json")
|
||||||
|
-d Run it as docker mode
|
||||||
-h Show help
|
-h Show help
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Config it
|
||||||
|
|
||||||
### Overview
|
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
|
||||||
|
* Rename it to **config.json**.
|
||||||
|
* Configure your provider, domain/sub-domain info, username and password, etc.
|
||||||
|
* Configure log file path, max size of log file, max count of log file.
|
||||||
|
* Save it in the same directory of GoDNS, or use -c=your_conf_path command.
|
||||||
|
|
||||||
* Make a copy of [config_sample.json](./config_sample.json) and name it `config.json`
|
### Config example for DNSPod
|
||||||
* Configure your provider, domain/subdomain info, credentials, etc.
|
|
||||||
* Configure a notification medium (e.g. SMTP to receive emails) to get notified when your IP address changes
|
|
||||||
* Place the file in the same directory of GoDNS or use the `-c=path/to/your/file.json` option
|
|
||||||
|
|
||||||
### Configuration properties
|
For DNSPod, you need to provide email & password, and config all the domains & subdomains.
|
||||||
|
|
||||||
* `provider` — One of the [supported provider to use](#supported-dns-providers): `Cloudflare`, `Google`, `DNSPod`, `AliDNS`, `HE`, `DuckDNS` or `Dreamhost`.
|
|
||||||
* `email` — Email or account name of the DNS provider.
|
|
||||||
* `password` — Password of the DNS provider.
|
|
||||||
* `login_token` — API token of the DNS provider.
|
|
||||||
* `domains` — Domains list, with your sub domains.
|
|
||||||
* `ip_url` — A URL for fetching one's public IPv4 address.
|
|
||||||
* `ipv6_url` — A URL for fetching one's public IPv6 address.
|
|
||||||
* `ip_type` — Switch deciding if IPv4 or IPv6 should be used (when [supported](#supported-dns-providers)). Available values: `IPv4` or `IPv6`.
|
|
||||||
* `interval` — How often (in seconds) the public IP should be updated.
|
|
||||||
* `socks5_proxy` — Socks5 proxy server.
|
|
||||||
* `resolver` — Address of a public DNS server to use. For instance to use [Google's public DNS](https://developers.google.com/speed/public-dns/docs/using), you can set `8.8.8.8` when using GoDNS in IPv4 mode or `2001:4860:4860::8888` in IPv6 mode.
|
|
||||||
|
|
||||||
### Configuration examples
|
|
||||||
|
|
||||||
#### Cloudflare
|
|
||||||
|
|
||||||
For Cloudflare, you need to provide the email & Global API Key as password (or to use the API token) and config all the domains & subdomains.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Using email & Global API Key</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "Cloudflare",
|
|
||||||
"email": "you@example.com",
|
|
||||||
"password": "Global API Key",
|
|
||||||
"domains": [{
|
|
||||||
"domain_name": "example.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
},{
|
|
||||||
"domain_name": "example2.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resolver": "8.8.8.8",
|
|
||||||
"ip_url": "https://myip.biturl.top",
|
|
||||||
"interval": 300,
|
|
||||||
"socks5_proxy": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Using the API Token</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "Cloudflare",
|
|
||||||
"login_token": "API Token",
|
|
||||||
"domains": [{
|
|
||||||
"domain_name": "example.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
},{
|
|
||||||
"domain_name": "example2.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resolver": "8.8.8.8",
|
|
||||||
"ip_url": "https://myip.biturl.top",
|
|
||||||
"interval": 300,
|
|
||||||
"socks5_proxy": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
#### DNSPod
|
|
||||||
|
|
||||||
For DNSPod, you need to provide your API Token(you can create it [here](https://www.dnspod.cn/console/user/security)), and config all the domains & subdomains.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example</summary>
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"provider": "DNSPod",
|
"provider": "DNSPod",
|
||||||
"login_token": "your_id,your_token",
|
"email": "example@gmail.com",
|
||||||
"domains": [{
|
"password": "YourPassword",
|
||||||
"domain_name": "example.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
},{
|
|
||||||
"domain_name": "example2.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resolver": "8.8.8.8",
|
|
||||||
"ip_url": "https://myip.biturl.top",
|
|
||||||
"ip_type": "IPV4",
|
|
||||||
"interval": 300,
|
|
||||||
"socks5_proxy": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
#### Dreamhost
|
|
||||||
|
|
||||||
For Dreamhost, you need to provide your API Token(you can create it [here](https://panel.dreamhost.com/?tree=home.api)), and config all the domains & subdomains.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "Dreamhost",
|
|
||||||
"login_token": "your_api_key",
|
|
||||||
"domains": [{
|
|
||||||
"domain_name": "example.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
},{
|
|
||||||
"domain_name": "example2.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resolver": "8.8.8.8",
|
|
||||||
"ip_url": "https://myip.biturl.top",
|
|
||||||
"ip_type": "IPV4",
|
|
||||||
"interval": 300,
|
|
||||||
"resolver": "ns1.dreamhost.com",
|
|
||||||
"socks5_proxy": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
#### Google Domains
|
|
||||||
|
|
||||||
For Google Domains, you need to provide email & password, and config all the domains & subdomains.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "Google",
|
|
||||||
"email": "Your_Username",
|
|
||||||
"password": "Your_Password",
|
|
||||||
"domains": [{
|
|
||||||
"domain_name": "example.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
},{
|
|
||||||
"domain_name": "example2.com",
|
|
||||||
"sub_domains": ["www","test"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resolver": "8.8.8.8",
|
|
||||||
"ip_url": "https://myip.biturl.top",
|
|
||||||
"interval": 300,
|
|
||||||
"socks5_proxy": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
#### AliDNS
|
|
||||||
|
|
||||||
For AliDNS, you need to provide `AccessKeyID` & `AccessKeySecret` as `email` & `password`, and config all the domains & subdomains.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "AliDNS",
|
|
||||||
"email": "AccessKeyID",
|
|
||||||
"password": "AccessKeySecret",
|
|
||||||
"login_token": "",
|
"login_token": "",
|
||||||
"domains": [{
|
"domains": [{
|
||||||
"domain_name": "example.com",
|
"domain_name": "example.com",
|
||||||
@ -312,77 +94,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": "http://members.3322.org/dyndns/getip",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"log_path": "./godns.log",
|
||||||
"interval": 300,
|
"log_size": 16,
|
||||||
|
"log_num": 3,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</details>
|
### Config example for HE.net
|
||||||
|
|
||||||
#### DuckDNS
|
|
||||||
|
|
||||||
For DuckDNS, only need to provide the `token`, config 1 default domain & subdomains.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "DuckDNS",
|
|
||||||
"password": "",
|
|
||||||
"login_token": "3aaaaaaaa-f411-4198-a5dc-8381cac61b87",
|
|
||||||
"domains": [
|
|
||||||
{
|
|
||||||
"domain_name": "www.duckdns.org",
|
|
||||||
"sub_domains": [
|
|
||||||
"myname"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resolver": "8.8.8.8",
|
|
||||||
"ip_url": "https://myip.biturl.top",
|
|
||||||
"interval": 300,
|
|
||||||
"socks5_proxy": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
#### No-IP
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "NoIP",
|
|
||||||
"email": "mail@example.com",
|
|
||||||
"password": "YourPassword",
|
|
||||||
"domains": [
|
|
||||||
{
|
|
||||||
"domain_name": "ddns.net",
|
|
||||||
"sub_domains": ["timothyye6"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ip_type": "IPv4",
|
|
||||||
"ip_url": "https://myip.biturl.top",
|
|
||||||
"resolver": "8.8.8.8",
|
|
||||||
"interval": 300,
|
|
||||||
"socks5_proxy": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
#### HE.net
|
|
||||||
|
|
||||||
For HE, email is not needed, just fill DDNS key to password, and config all the domains & subdomains.
|
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",
|
||||||
|
"email": "",
|
||||||
"password": "YourPassword",
|
"password": "YourPassword",
|
||||||
"login_token": "",
|
"login_token": "",
|
||||||
"domains": [{
|
"domains": [{
|
||||||
@ -393,227 +119,76 @@ 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": "http://members.3322.org/dyndns/getip",
|
||||||
"ip_url": "https://myip.biturl.top",
|
"log_path":"/users/timothy/workspace/src/godns/godns.log",
|
||||||
"interval": 300,
|
"log_size":16,
|
||||||
|
"log_num":3,
|
||||||
"socks5_proxy": ""
|
"socks5_proxy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
### HE.net DDNS configuration
|
||||||
<summary>Provider configuration</summary>
|
|
||||||
|
|
||||||
Add a new "A record" and make sure that "Enable entry for dynamic dns" is checked:
|
Add a new "A record", make sure that "Enable entry for dynamic dns" is checked:
|
||||||
|
|
||||||
<img src="./snapshots/he1.png" width="640" />
|
<img src="https://github.com/TimothyYe/godns/blob/he/snapshots/he1.png?raw=true" width="640" />
|
||||||
|
|
||||||
Fill in your own DDNS key or generate a random DDNS key for this new created "A record":
|
Fill your own DDNS key or generate a random DDNS key for this new created "A record":
|
||||||
|
|
||||||
<img src="./snapshots/he2.png" width="640" />
|
<img src="https://github.com/TimothyYe/godns/blob/he/snapshots/he2.png?raw=true" width="640" />
|
||||||
|
|
||||||
Remember the DDNS key and set it in the `password` property in the configuration file.
|
Remember the DDNS key and fill it as password to the config.json.
|
||||||
|
|
||||||
__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>
|
|
||||||
|
|
||||||
### Notifications
|
### SOCKS5 proxy support
|
||||||
|
|
||||||
GoDNS can send a notification each time the IP changes.
|
You can also use SOCKS5 proxy, just fill SOCKS5 address to the ```socks5_proxy``` item:
|
||||||
|
|
||||||
#### Email
|
|
||||||
|
|
||||||
Emails are sent over [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol). Update your configuration with the following snippet:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"notify": {
|
|
||||||
"mail": {
|
|
||||||
"enabled": true,
|
|
||||||
"smtp_server": "smtp.example.com",
|
|
||||||
"smtp_username": "user",
|
|
||||||
"smtp_password": "password",
|
|
||||||
"smtp_port": 25,
|
|
||||||
"send_to": "my_mail@example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each time the IP changes, you will receive an email like that:
|
|
||||||
|
|
||||||
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/mail.png?raw=true" />
|
|
||||||
|
|
||||||
#### Telegram
|
|
||||||
|
|
||||||
To receive a [Telegram](https://telegram.org/) message each time the IP changes, update your configuration with the following snippet:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"notify": {
|
|
||||||
"telegram": {
|
|
||||||
"enabled": true,
|
|
||||||
"bot_api_key": "11111:aaaa-bbbb",
|
|
||||||
"chat_id": "-123456",
|
|
||||||
"message_template": "Domain *{{ .Domain }}* is updated to %0A{{ .CurrentIP }}",
|
|
||||||
"use_proxy": false
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `message_template` property supports [markdown](https://www.markdownguide.org). New lines needs to be escaped with `%0A`.
|
|
||||||
|
|
||||||
#### Slack
|
|
||||||
|
|
||||||
To receive a [Slack](https://slack.com) message each time the IP changes, update your configuration with the following snippet:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"notify": {
|
|
||||||
"slack": {
|
|
||||||
"enabled": true,
|
|
||||||
"bot_api_token": "xoxb-xxx",
|
|
||||||
"channel": "your_channel",
|
|
||||||
"message_template": "Domain *{{ .Domain }}* is updated to \n{{ .CurrentIP }}",
|
|
||||||
"use_proxy": false
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `message_template` property supports [markdown](https://www.markdownguide.org). New lines needs to be escaped with `\n`.
|
|
||||||
|
|
||||||
### Miscellaneous topics
|
|
||||||
|
|
||||||
#### IPv6 support
|
|
||||||
|
|
||||||
Most of the [providers](#supported-dns-providers) support IPv6.
|
|
||||||
|
|
||||||
To enable the `IPv6` support of GoDNS, there are two solutions to choose from:
|
|
||||||
|
|
||||||
1. Use an online service to lookup the external IPv6
|
|
||||||
|
|
||||||
For that:
|
|
||||||
|
|
||||||
- Set the `ip_type` as `IPv6`, and make sure the `ipv6_url` is configured
|
|
||||||
- Create an `AAAA` record instead of an `A` record in your DNS provider
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configuration example</summary>
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"domains": [
|
|
||||||
{
|
|
||||||
"domain_name": "example.com",
|
|
||||||
"sub_domains": [
|
|
||||||
"ipv6"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resolver": "2001:4860:4860::8888",
|
|
||||||
"ipv6_url": "https://api-ipv6.ip.sb/ip",
|
|
||||||
"ip_type": "IPv6"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
2. Let GoDNS find the IPv6 of the network interface of the machine it is running on (more on that [later](#network-interface-ip-address)).
|
|
||||||
|
|
||||||
For this to happen, just leave `ip_url` and `ipv6_url` empty.
|
|
||||||
|
|
||||||
Note that the network interface must be configured with an IPv6 for this to work.
|
|
||||||
|
|
||||||
#### Network interface IP address
|
|
||||||
|
|
||||||
For some reasons if you want to get the IP address associated to a network interface (instead of performing an online lookup), you can specify it in the configuration file this way:
|
|
||||||
|
|
||||||
```json
|
|
||||||
...
|
|
||||||
"ip_url": "",
|
|
||||||
"ip_interface": "interface-name",
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
With `interface-name` replaced by the name of the network interface, e.g. `eth0` on Linux or `Local Area Connection` on Windows.
|
|
||||||
|
|
||||||
Note: If `ip_url` is also specified, it will be used to perform an online lookup first and the network interface IP will be used as a fallback in case of failure.
|
|
||||||
|
|
||||||
#### SOCKS5 proxy support
|
|
||||||
|
|
||||||
You can make all remote calls go through a [SOCKS5 proxy](https://en.wikipedia.org/wiki/SOCKS#SOCKS5) by specifying it in the configuration file this way:
|
|
||||||
|
|
||||||
```json
|
|
||||||
...
|
|
||||||
"socks5_proxy": "127.0.0.1:7070"
|
"socks5_proxy": "127.0.0.1:7070"
|
||||||
"use_proxy": true
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running GoDNS
|
Now all the queries will go through the specified SOCKS5 proxy.
|
||||||
|
|
||||||
There are few ways to run GoDNS.
|
## Run it as a daemon manually
|
||||||
|
|
||||||
### As a manual daemon
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nohup ./godns &
|
nohup ./godns &
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: when the program stops, it will not be restarted.
|
## Run it as a daemon, manage it via Upstart
|
||||||
|
|
||||||
### As a managed daemon (with upstart)
|
* Install `upstart` first
|
||||||
|
* Copy `./upstart/godns.conf` to `/etc/init`
|
||||||
1. Install `upstart` first (if not available already)
|
* Start it as a system service:
|
||||||
2. Copy `./upstart/godns.conf` to `/etc/init` (and tweak it to your needs)
|
|
||||||
3. Start the service:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo start godns
|
sudo start godns
|
||||||
```
|
```
|
||||||
|
|
||||||
### As a managed daemon (with systemd)
|
## Run it as a daemon, manage it via Systemd
|
||||||
|
|
||||||
1. Install `systemd` first (it not available already)
|
* Modify `./systemd/godns.service` and config it.
|
||||||
2. Copy `./systemd/godns.service` to `/lib/systemd/system` (and tweak it to your needs)
|
* Copy `./systemd/godns.service` to `/lib/systemd/system`
|
||||||
3. Start the service:
|
* Start it as a systemd service:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl enable godns
|
sudo systemctl enable godns
|
||||||
sudo systemctl start godns
|
sudo systemctl start godns
|
||||||
```
|
```
|
||||||
|
|
||||||
### As a Docker container
|
## Run it with docker
|
||||||
|
|
||||||
With `/path/to/config.json` your local configuration file, run:
|
Now godns supports to run in docker.
|
||||||
|
|
||||||
|
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
|
||||||
|
* Rename it to **config.json**.
|
||||||
|
* Run GoDNS with docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run \
|
docker run -d --name godns --restart=always \
|
||||||
-d --name godns --restart=always \
|
-v /path/to/config.json:/usr/local/godns/config.json timothyye/godns:1.2
|
||||||
-v /path/to/config.json:/config.json \
|
|
||||||
timothyye/godns:latest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### As a Windows service
|
## Enjoy it!
|
||||||
|
|
||||||
1. Download the latest version of [NSSM](https://nssm.cc/download)
|
|
||||||
|
|
||||||
2. In an administrative prompt, from the folder where NSSM was downloaded, e.g. `C:\Downloads\nssm\` **win64**, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
nssm install YOURSERVICENAME
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Follow the interface to configure the service. In the "Application" tab just indicate where the `godns.exe` file is. Optionally you can also define a description on the "Details" tab and define a log file on the "I/O" tab. Finish by clicking on the "Install service" button.
|
|
||||||
|
|
||||||
4. The service will now start along Windows.
|
|
||||||
|
|
||||||
Note: you can uninstall the service by running:
|
|
||||||
|
|
||||||
```
|
|
||||||
nssm remove YOURSERVICENAME
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
theme: jekyll-theme-cayman
|
|
@ -1,70 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
"github.com/TimothyYe/godns/handler"
|
|
||||||
"github.com/fatih/color"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
configuration godns.Settings
|
|
||||||
optConf = flag.String("c", "./config.json", "Specify a config file")
|
|
||||||
optHelp = flag.Bool("h", false, "Show help")
|
|
||||||
|
|
||||||
// Version is current version of GoDNS
|
|
||||||
Version = "0.1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
if *optHelp {
|
|
||||||
color.Cyan(godns.Logo, Version)
|
|
||||||
flag.Usage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load settings from configurations file
|
|
||||||
if err := godns.LoadSettings(*optConf, &configuration); err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := godns.CheckSettings(&configuration); err != nil {
|
|
||||||
fmt.Println("Settings is invalid! ", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init log settings
|
|
||||||
log.SetPrefix("[GoDNS] ")
|
|
||||||
log.Println("GoDNS started, entering main loop...")
|
|
||||||
dnsLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func dnsLoop() {
|
|
||||||
panicChan := make(chan godns.Domain)
|
|
||||||
|
|
||||||
log.Println("Creating DNS handler with provider:", configuration.Provider)
|
|
||||||
h := handler.CreateHandler(configuration.Provider)
|
|
||||||
h.SetConfiguration(&configuration)
|
|
||||||
for i := range configuration.Domains {
|
|
||||||
go h.DomainLoop(&configuration.Domains[i], panicChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
panicCount := 0
|
|
||||||
for {
|
|
||||||
failDomain := <-panicChan
|
|
||||||
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
|
|
||||||
go h.DomainLoop(&failDomain, panicChan)
|
|
||||||
|
|
||||||
panicCount++
|
|
||||||
if panicCount >= godns.PanicMax {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +1,19 @@
|
|||||||
{
|
{
|
||||||
"provider": "DNSPod",
|
"provider": "DNSPod",
|
||||||
|
"email": "example@gmail.com",
|
||||||
"password": "",
|
"password": "",
|
||||||
"login_token": "",
|
"login_token": "",
|
||||||
"domains": [
|
"domains": [{
|
||||||
{
|
|
||||||
"domain_name": "example.com",
|
"domain_name": "example.com",
|
||||||
"sub_domains": [
|
"sub_domains": ["www","test"]
|
||||||
"www",
|
},{
|
||||||
"test"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain_name": "example2.com",
|
"domain_name": "example2.com",
|
||||||
"sub_domains": [
|
"sub_domains": ["www","test"]
|
||||||
"www",
|
|
||||||
"test"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ip_url": "https://myip.biturl.top",
|
"ip_url": "http://members.3322.org/dyndns/getip",
|
||||||
"ipv6_url": "https://api-ipv6.ip.sb/ip",
|
"log_path": "./godns.log",
|
||||||
"ip_type": "IPv4",
|
"log_size": 16,
|
||||||
"interval": 300,
|
"log_num": 3,
|
||||||
"resolver": "8.8.8.8",
|
"socks5_proxy": ""
|
||||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
|
|
||||||
"ip_interface": "eth0",
|
|
||||||
"socks5_proxy": "",
|
|
||||||
"use_proxy": false,
|
|
||||||
"notify": {
|
|
||||||
"telegram": {
|
|
||||||
"enabled": false,
|
|
||||||
"bot_api_key": "",
|
|
||||||
"chat_id": "",
|
|
||||||
"message_template": "",
|
|
||||||
"use_proxy": false
|
|
||||||
},
|
|
||||||
"mail": {
|
|
||||||
"enabled": false,
|
|
||||||
"smtp_server": "",
|
|
||||||
"smtp_username": "",
|
|
||||||
"smtp_password": "",
|
|
||||||
"smtp_port": 25,
|
|
||||||
"send_to": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
256
dnspod_handler.go
Normal file
256
dnspod_handler.go
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSPodHandler struct{}
|
||||||
|
|
||||||
|
func (handler *DNSPodHandler) DomainLoop(domain *Domain) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
panicCount++
|
||||||
|
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
||||||
|
fmt.Println(identifyPanic())
|
||||||
|
log.Print(identifyPanic())
|
||||||
|
if panicCount < PANIC_MAX {
|
||||||
|
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
|
||||||
|
go handler.DomainLoop(domain)
|
||||||
|
} else {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
domainID := handler.GetDomain(domain.DomainName)
|
||||||
|
|
||||||
|
if domainID == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIP, err := getCurrentIP(configuration.IPUrl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("get_currentIP:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Println("currentIp is:", currentIP)
|
||||||
|
|
||||||
|
for _, subDomain := range domain.SubDomains {
|
||||||
|
|
||||||
|
subDomainID, ip := handler.GetSubDomain(domainID, subDomain)
|
||||||
|
|
||||||
|
if subDomainID == "" || ip == "" {
|
||||||
|
log.Printf("domain: %s.%s subDomainID: %s ip: %s\n", subDomain, domain.DomainName, subDomainID, ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Continue to check the IP of sub-domain
|
||||||
|
if len(ip) > 0 && !strings.Contains(currentIP, ip) {
|
||||||
|
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
||||||
|
handler.UpdateIP(domainID, subDomainID, subDomain, currentIP)
|
||||||
|
} else {
|
||||||
|
log.Printf("%s.%s Current IP is same as domain IP, no need to update...\n", subDomain, domain.DomainName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Interval is 5 minutes
|
||||||
|
time.Sleep(time.Minute * INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *DNSPodHandler) GenerateHeader(content url.Values) url.Values {
|
||||||
|
header := url.Values{}
|
||||||
|
if configuration.LoginToken != "" {
|
||||||
|
header.Add("login_token", configuration.LoginToken)
|
||||||
|
} else {
|
||||||
|
header.Add("login_email", configuration.Email)
|
||||||
|
header.Add("login_password", configuration.Password)
|
||||||
|
}
|
||||||
|
header.Add("format", "json")
|
||||||
|
header.Add("lang", "en")
|
||||||
|
header.Add("error_on_empty", "no")
|
||||||
|
|
||||||
|
if content != nil {
|
||||||
|
for k := range content {
|
||||||
|
header.Add(k, content.Get(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *DNSPodHandler) GetDomain(name string) int64 {
|
||||||
|
|
||||||
|
var ret int64
|
||||||
|
values := url.Values{}
|
||||||
|
values.Add("type", "all")
|
||||||
|
values.Add("offset", "0")
|
||||||
|
values.Add("length", "20")
|
||||||
|
|
||||||
|
response, err := handler.PostData("/Domain.List", values)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to get domain list...")
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
sjson, parseErr := simplejson.NewJson([]byte(response))
|
||||||
|
|
||||||
|
if parseErr != nil {
|
||||||
|
log.Println(parseErr)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if sjson.Get("status").Get("code").MustString() == "1" {
|
||||||
|
domains, _ := sjson.Get("domains").Array()
|
||||||
|
|
||||||
|
for _, d := range domains {
|
||||||
|
m := d.(map[string]interface{})
|
||||||
|
if m["name"] == name {
|
||||||
|
id := m["id"]
|
||||||
|
|
||||||
|
switch t := id.(type) {
|
||||||
|
case json.Number:
|
||||||
|
ret, _ = t.Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(domains) == 0 {
|
||||||
|
log.Println("domains slice is empty.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println("get_domain:status code:", sjson.Get("status").Get("code").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *DNSPodHandler) GetSubDomain(domainID int64, name string) (string, string) {
|
||||||
|
log.Println("debug:", domainID, name)
|
||||||
|
var ret, ip string
|
||||||
|
value := url.Values{}
|
||||||
|
value.Add("domain_id", strconv.FormatInt(domainID, 10))
|
||||||
|
value.Add("offset", "0")
|
||||||
|
value.Add("length", "1")
|
||||||
|
value.Add("sub_domain", name)
|
||||||
|
|
||||||
|
response, err := handler.PostData("/Record.List", value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to get domain list")
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
sjson, parseErr := simplejson.NewJson([]byte(response))
|
||||||
|
|
||||||
|
if parseErr != nil {
|
||||||
|
log.Println(parseErr)
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if sjson.Get("status").Get("code").MustString() == "1" {
|
||||||
|
records, _ := sjson.Get("records").Array()
|
||||||
|
|
||||||
|
for _, d := range records {
|
||||||
|
m := d.(map[string]interface{})
|
||||||
|
if m["name"] == name {
|
||||||
|
ret = m["id"].(string)
|
||||||
|
ip = m["value"].(string)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(records) == 0 {
|
||||||
|
log.Println("records slice is empty.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println("get_subdomain:status code:", sjson.Get("status").Get("code").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, ip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *DNSPodHandler) UpdateIP(domainID int64, subDomainID string, subDomainName string, ip string) {
|
||||||
|
value := url.Values{}
|
||||||
|
value.Add("domain_id", strconv.FormatInt(domainID, 10))
|
||||||
|
value.Add("record_id", subDomainID)
|
||||||
|
value.Add("sub_domain", subDomainName)
|
||||||
|
value.Add("record_type", "A")
|
||||||
|
value.Add("record_line", "默认")
|
||||||
|
value.Add("value", ip)
|
||||||
|
|
||||||
|
response, err := handler.PostData("/Record.Modify", value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to update record to new IP!")
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sjson, parseErr := simplejson.NewJson([]byte(response))
|
||||||
|
|
||||||
|
if parseErr != nil {
|
||||||
|
log.Println(parseErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sjson.Get("status").Get("code").MustString() == "1" {
|
||||||
|
log.Println("New IP updated!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *DNSPodHandler) PostData(url string, content url.Values) (string, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
if configuration.Socks5Proxy != "" {
|
||||||
|
|
||||||
|
log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
|
||||||
|
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("can't connect to the proxy:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpTransport := &http.Transport{}
|
||||||
|
client.Transport = httpTransport
|
||||||
|
httpTransport.Dial = dialer.Dial
|
||||||
|
}
|
||||||
|
|
||||||
|
values := handler.GenerateHeader(content)
|
||||||
|
req, _ := http.NewRequest("POST", "https://dnsapi.cn"+url, strings.NewReader(values.Encode()))
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Set("User-Agent", fmt.Sprintf("GoDNS/0.1 (%s)", configuration.Email))
|
||||||
|
|
||||||
|
response, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Post failed...")
|
||||||
|
log.Println(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
resp, _ := ioutil.ReadAll(response.Body)
|
||||||
|
|
||||||
|
return string(resp), nil
|
||||||
|
}
|
6
glide.lock
generated
Normal file
6
glide.lock
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
hash: 1da678bb0eda2c40266e5014e089aa36ebfa0a360d20552a77ef77a00b2d1bfb
|
||||||
|
updated: 2016-05-25T15:08:31.725668117+08:00
|
||||||
|
imports:
|
||||||
|
- name: github.com/bitly/go-simplejson
|
||||||
|
version: aabad6e819789e569bd6aabf444c935aa9ba1e44
|
||||||
|
devImports: []
|
3
glide.yaml
Normal file
3
glide.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package: github.com/timothyye/godns
|
||||||
|
import:
|
||||||
|
- package: github.com/bitly/go-simplejson
|
18
go.mod
18
go.mod
@ -1,18 +0,0 @@
|
|||||||
module github.com/TimothyYe/godns
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/bitly/go-simplejson v0.5.0
|
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
|
||||||
github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6
|
|
||||||
github.com/fatih/color v1.7.0
|
|
||||||
github.com/google/uuid v1.1.1
|
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.4 // indirect
|
|
||||||
github.com/miekg/dns v1.1.29
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
|
||||||
)
|
|
||||||
|
|
||||||
go 1.13
|
|
46
go.sum
46
go.sum
@ -1,46 +0,0 @@
|
|||||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
|
||||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
|
||||||
github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6 h1:oV1V+uwP+sjmdSkvMxsl/l+HE+N8wbL49wCXZPel25M=
|
|
||||||
github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6/go.mod h1:txOV61Nn+21z77KUMkNsp8lTHoOFTtqotltQAFenS9I=
|
|
||||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
|
||||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
|
||||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
|
|
||||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190226215855-775f8194d0f9 h1:N26gncmS+iqc/W/SKhX3ElI5pkt72XYoRLgi5Z70LSc=
|
|
||||||
golang.org/x/sys v0.0.0-20190226215855-775f8194d0f9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
|
62
godns.go
Normal file
62
godns.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PANIC_MAX is the max allowed panic times
|
||||||
|
PANIC_MAX = 5
|
||||||
|
// INTERVAL is minute
|
||||||
|
INTERVAL = 5
|
||||||
|
// DNSPOD
|
||||||
|
DNSPOD = "DNSPod"
|
||||||
|
// HE
|
||||||
|
HE = "HE"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configuration Settings
|
||||||
|
optConf = flag.String("c", "./config.json", "Specify a config file")
|
||||||
|
optHelp = flag.Bool("h", false, "Show help")
|
||||||
|
panicCount = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if *optHelp {
|
||||||
|
flag.Usage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load settings from configurations file
|
||||||
|
if err := LoadSettings(*optConf, &configuration); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
log.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkSettings(&configuration); err != nil {
|
||||||
|
log.Println("Settings is invalid! ", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := InitLogger(configuration.LogPath, configuration.LogSize, configuration.LogNum); err != nil {
|
||||||
|
log.Println("InitLogger error:", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsLoop() {
|
||||||
|
handler := createHandler(configuration.Provider)
|
||||||
|
for _, domain := range configuration.Domains {
|
||||||
|
go handler.DomainLoop(&domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"Deadline": "2m",
|
|
||||||
"Enable": [
|
|
||||||
"goimports",
|
|
||||||
"golint",
|
|
||||||
"ineffassign",
|
|
||||||
"misspell",
|
|
||||||
"vet",
|
|
||||||
"varcheck",
|
|
||||||
"deadcode",
|
|
||||||
"structcheck"
|
|
||||||
],
|
|
||||||
"Exclude": [
|
|
||||||
"snapshots/",
|
|
||||||
"upstart/",
|
|
||||||
"systemd/"
|
|
||||||
],
|
|
||||||
"LineLength": 100
|
|
||||||
}
|
|
18
handler.go
Normal file
18
handler.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type IHandler interface {
|
||||||
|
DomainLoop(domain *Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createHandler(provider string) IHandler {
|
||||||
|
var handler IHandler
|
||||||
|
|
||||||
|
switch provider {
|
||||||
|
case DNSPOD:
|
||||||
|
handler = IHandler(&DNSPodHandler{})
|
||||||
|
case HE:
|
||||||
|
handler = IHandler(&HEHandler{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
@ -1,166 +0,0 @@
|
|||||||
package alidns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AliDNS token
|
|
||||||
type AliDNS struct {
|
|
||||||
AccessKeyID string
|
|
||||||
AccessKeySecret string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
publicParm = map[string]string{
|
|
||||||
"AccessKeyId": "",
|
|
||||||
"Format": "JSON",
|
|
||||||
"Version": "2015-01-09",
|
|
||||||
"SignatureMethod": "HMAC-SHA1",
|
|
||||||
"Timestamp": "",
|
|
||||||
"SignatureVersion": "1.0",
|
|
||||||
"SignatureNonce": "",
|
|
||||||
}
|
|
||||||
baseURL = "http://alidns.aliyuncs.com/"
|
|
||||||
instance *AliDNS
|
|
||||||
once sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
type domainRecordsResp struct {
|
|
||||||
RequestID string `json:"RequestId"`
|
|
||||||
TotalCount int
|
|
||||||
PageNumber int
|
|
||||||
PageSize int
|
|
||||||
DomainRecords domainRecords
|
|
||||||
}
|
|
||||||
|
|
||||||
type domainRecords struct {
|
|
||||||
Record []DomainRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainRecord struct
|
|
||||||
type DomainRecord struct {
|
|
||||||
DomainName string
|
|
||||||
RecordID string `json:"RecordId"`
|
|
||||||
RR string
|
|
||||||
Type string
|
|
||||||
Value string
|
|
||||||
Line string
|
|
||||||
Priority int
|
|
||||||
TTL int
|
|
||||||
Status string
|
|
||||||
Locked bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHTTPBody(url string) ([]byte, error) {
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
return body, err
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("status %d, Error:%s", resp.StatusCode, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAliDNS function creates instance of AliDNS and return
|
|
||||||
func NewAliDNS(key, secret string) *AliDNS {
|
|
||||||
once.Do(func() {
|
|
||||||
instance = &AliDNS{
|
|
||||||
AccessKeyID: key,
|
|
||||||
AccessKeySecret: secret,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDomainRecords gets all the doamin records according to input subdomain key
|
|
||||||
func (d *AliDNS) GetDomainRecords(domain, rr string) []DomainRecord {
|
|
||||||
resp := &domainRecordsResp{}
|
|
||||||
parms := map[string]string{
|
|
||||||
"Action": "DescribeSubDomainRecords",
|
|
||||||
"SubDomain": fmt.Sprintf("%s.%s", rr, domain),
|
|
||||||
}
|
|
||||||
urlPath := d.genRequestURL(parms)
|
|
||||||
body, err := getHTTPBody(urlPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("GetDomainRecords error.%+v\n", err)
|
|
||||||
} else {
|
|
||||||
if err := json.Unmarshal(body, resp); err != nil {
|
|
||||||
fmt.Printf("GetDomainRecords error. %+v\n", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return resp.DomainRecords.Record
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateDomainRecord updates domain record
|
|
||||||
func (d *AliDNS) UpdateDomainRecord(r DomainRecord) error {
|
|
||||||
parms := map[string]string{
|
|
||||||
"Action": "UpdateDomainRecord",
|
|
||||||
"RecordId": r.RecordID,
|
|
||||||
"RR": r.RR,
|
|
||||||
"Type": r.Type,
|
|
||||||
"Value": r.Value,
|
|
||||||
"TTL": strconv.Itoa(r.TTL),
|
|
||||||
"Line": r.Line,
|
|
||||||
}
|
|
||||||
|
|
||||||
urlPath := d.genRequestURL(parms)
|
|
||||||
if urlPath == "" {
|
|
||||||
return errors.New("failed to generate request URL")
|
|
||||||
}
|
|
||||||
_, err := getHTTPBody(urlPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("UpdateDomainRecord error.%+v\n", err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliDNS) genRequestURL(parms map[string]string) string {
|
|
||||||
var pArr []string
|
|
||||||
ps := map[string]string{}
|
|
||||||
for k, v := range publicParm {
|
|
||||||
ps[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range parms {
|
|
||||||
ps[k] = v
|
|
||||||
}
|
|
||||||
now := time.Now().UTC()
|
|
||||||
ps["AccessKeyId"] = d.AccessKeyID
|
|
||||||
ps["SignatureNonce"] = strconv.Itoa(int(now.UnixNano()) + rand.Intn(99999))
|
|
||||||
ps["Timestamp"] = now.Format("2006-01-02T15:04:05Z")
|
|
||||||
|
|
||||||
for k, v := range ps {
|
|
||||||
pArr = append(pArr, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
sort.Strings(pArr)
|
|
||||||
path := strings.Join(pArr, "&")
|
|
||||||
|
|
||||||
s := "GET&%2F&" + url.QueryEscape(path)
|
|
||||||
s = strings.Replace(s, "%3A", "%253A", -1)
|
|
||||||
s = strings.Replace(s, "%40", "%2540", -1)
|
|
||||||
s = strings.Replace(s, "%2A", "%252A", -1)
|
|
||||||
mac := hmac.New(sha1.New, []byte(d.AccessKeySecret+"&"))
|
|
||||||
|
|
||||||
if _, err := mac.Write([]byte(s)); err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
|
||||||
return fmt.Sprintf("%s?%s&Signature=%s", baseURL, path, url.QueryEscape(sign))
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
package alidns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"runtime/debug"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler struct
|
|
||||||
type Handler struct {
|
|
||||||
Configuration *godns.Settings
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfiguration pass dns settings and store it to handler instance
|
|
||||||
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
|
|
||||||
handler.Configuration = conf
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainLoop the main logic loop
|
|
||||||
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
|
||||||
panicChan <- *domain
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
looping := false
|
|
||||||
aliDNS := NewAliDNS(handler.Configuration.Email, handler.Configuration.Password)
|
|
||||||
|
|
||||||
for {
|
|
||||||
if looping {
|
|
||||||
// Sleep with interval
|
|
||||||
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
|
||||||
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
looping = true
|
|
||||||
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to get current IP:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Println("currentIP is:", currentIP)
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
hostname := subDomain + "." + domain.DomainName
|
|
||||||
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
//check against currently known IP, if no change, skip update
|
|
||||||
if currentIP == lastIP {
|
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
|
||||||
} else {
|
|
||||||
lastIP = currentIP
|
|
||||||
|
|
||||||
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
|
||||||
records := aliDNS.GetDomainRecords(domain.DomainName, subDomain)
|
|
||||||
if records == nil || len(records) == 0 {
|
|
||||||
log.Printf("Cannot get subdomain %s from AliDNS.\r\n", subDomain)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
records[0].Value = currentIP
|
|
||||||
if err := aliDNS.UpdateDomainRecord(records[0]); err != nil {
|
|
||||||
log.Printf("Failed to update IP for subdomain:%s\r\n", subDomain)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
log.Printf("IP updated for subdomain:%s\r\n", subDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send notification
|
|
||||||
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
|
|
||||||
log.Printf("Failed to send notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,266 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler struct definition
|
|
||||||
type Handler struct {
|
|
||||||
Configuration *godns.Settings
|
|
||||||
API string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSRecordResponse struct
|
|
||||||
type DNSRecordResponse struct {
|
|
||||||
Records []DNSRecord `json:"result"`
|
|
||||||
Success bool `json:"success"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSRecordUpdateResponse struct
|
|
||||||
type DNSRecordUpdateResponse struct {
|
|
||||||
Record DNSRecord `json:"result"`
|
|
||||||
Success bool `json:"success"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSRecord for Cloudflare API
|
|
||||||
type DNSRecord struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
IP string `json:"content"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Proxied bool `json:"proxied"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
ZoneID string `json:"zone_id"`
|
|
||||||
TTL int32 `json:"ttl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetIP updates DNSRecord.IP
|
|
||||||
func (r *DNSRecord) SetIP(ip string) {
|
|
||||||
r.IP = ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZoneResponse is a wrapper for Zones
|
|
||||||
type ZoneResponse struct {
|
|
||||||
Zones []Zone `json:"result"`
|
|
||||||
Success bool `json:"success"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zone object with id and name
|
|
||||||
type Zone struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfiguration pass dns settings and store it to handler instance
|
|
||||||
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
|
|
||||||
handler.Configuration = conf
|
|
||||||
handler.API = "https://api.cloudflare.com/client/v4"
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainLoop the main logic loop
|
|
||||||
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
|
||||||
panicChan <- *domain
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var lastIP string
|
|
||||||
looping := false
|
|
||||||
for {
|
|
||||||
if looping {
|
|
||||||
// Sleep with interval
|
|
||||||
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
|
||||||
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
|
||||||
}
|
|
||||||
looping = true
|
|
||||||
|
|
||||||
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error in GetCurrentIP:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Println("Current IP is:", currentIP)
|
|
||||||
//check against locally cached IP, if no change, skip update
|
|
||||||
if currentIP == lastIP {
|
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
|
||||||
} else {
|
|
||||||
log.Println("Checking IP for domain", domain.DomainName)
|
|
||||||
zoneID := handler.getZone(domain.DomainName)
|
|
||||||
if zoneID != "" {
|
|
||||||
records := handler.getDNSRecords(zoneID)
|
|
||||||
|
|
||||||
// update records
|
|
||||||
for _, rec := range records {
|
|
||||||
if !recordTracked(domain, &rec) {
|
|
||||||
log.Println("Skiping record:", rec.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if rec.IP != currentIP {
|
|
||||||
log.Printf("IP mismatch: Current(%+v) vs Cloudflare(%+v)\r\n", currentIP, rec.IP)
|
|
||||||
lastIP = handler.updateRecord(rec, currentIP)
|
|
||||||
|
|
||||||
// Send notification
|
|
||||||
if err := godns.SendNotify(handler.Configuration, rec.Name, currentIP); err != nil {
|
|
||||||
log.Println("Failed to send notification")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Record OK: %+v - %+v\r\n", rec.Name, rec.IP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Println("Failed to find zone for domain:", domain.DomainName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if record is present in domain conf
|
|
||||||
func recordTracked(domain *godns.Domain, record *DNSRecord) bool {
|
|
||||||
if record.Name == domain.DomainName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
sd := fmt.Sprintf("%s.%s", subDomain, domain.DomainName)
|
|
||||||
if record.Name == sd {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new request with auth in place and optional proxy
|
|
||||||
func (handler *Handler) newRequest(method, url string, body io.Reader) (*http.Request, *http.Client) {
|
|
||||||
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
|
||||||
if client == nil {
|
|
||||||
log.Println("cannot create HTTP client")
|
|
||||||
}
|
|
||||||
|
|
||||||
req, _ := http.NewRequest(method, handler.API+url, body)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if handler.Configuration.Email != "" && handler.Configuration.Password != "" {
|
|
||||||
req.Header.Set("X-Auth-Email", handler.Configuration.Email)
|
|
||||||
req.Header.Set("X-Auth-Key", handler.Configuration.Password)
|
|
||||||
} else if handler.Configuration.LoginToken != "" {
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", handler.Configuration.LoginToken))
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the correct zone via domain name
|
|
||||||
func (handler *Handler) getZone(domain string) string {
|
|
||||||
|
|
||||||
var z ZoneResponse
|
|
||||||
|
|
||||||
req, client := handler.newRequest("GET", fmt.Sprintf("/zones?name=%s", domain), nil)
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Request error:", err.Error())
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
err = json.Unmarshal(body, &z)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Decoder error: %+v\n", err)
|
|
||||||
log.Printf("Response body: %+v\n", string(body))
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if z.Success != true {
|
|
||||||
log.Printf("Response failed: %+v\n", string(body))
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, zone := range z.Zones {
|
|
||||||
if zone.Name == domain {
|
|
||||||
return zone.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all DNS A records for a zone
|
|
||||||
func (handler *Handler) getDNSRecords(zoneID string) []DNSRecord {
|
|
||||||
|
|
||||||
var empty []DNSRecord
|
|
||||||
var r DNSRecordResponse
|
|
||||||
var recordType string
|
|
||||||
|
|
||||||
if handler.Configuration.IPType == "" || strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
|
|
||||||
recordType = "A"
|
|
||||||
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
|
||||||
recordType = "AAAA"
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Querying records with type:", recordType)
|
|
||||||
req, client := handler.newRequest("GET", fmt.Sprintf("/zones/"+zoneID+"/dns_records?type=%s&page=1&per_page=500", recordType), nil)
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Request error:", err.Error())
|
|
||||||
return empty
|
|
||||||
}
|
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
err = json.Unmarshal(body, &r)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Decoder error: %+v\n", err)
|
|
||||||
log.Printf("Response body: %+v\n", string(body))
|
|
||||||
return empty
|
|
||||||
}
|
|
||||||
if r.Success != true {
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
log.Printf("Response failed: %+v\n", string(body))
|
|
||||||
return empty
|
|
||||||
|
|
||||||
}
|
|
||||||
return r.Records
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update DNS A Record with new IP
|
|
||||||
func (handler *Handler) updateRecord(record DNSRecord, newIP string) string {
|
|
||||||
|
|
||||||
var r DNSRecordUpdateResponse
|
|
||||||
record.SetIP(newIP)
|
|
||||||
var lastIP string
|
|
||||||
|
|
||||||
j, _ := json.Marshal(record)
|
|
||||||
req, client := handler.newRequest("PUT",
|
|
||||||
"/zones/"+record.ZoneID+"/dns_records/"+record.ID,
|
|
||||||
bytes.NewBuffer(j),
|
|
||||||
)
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Request error:", err.Error())
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
err = json.Unmarshal(body, &r)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Decoder error: %+v\n", err)
|
|
||||||
log.Printf("Response body: %+v\n", string(body))
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if r.Success != true {
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
log.Printf("Response failed: %+v\n", string(body))
|
|
||||||
} else {
|
|
||||||
log.Printf("Record updated: %+v - %+v", record.Name, record.IP)
|
|
||||||
lastIP = record.IP
|
|
||||||
}
|
|
||||||
return lastIP
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestResponseToJSON(t *testing.T) {
|
|
||||||
s := strings.NewReader(`
|
|
||||||
{
|
|
||||||
"errors": [],
|
|
||||||
"messages": [],
|
|
||||||
"result": [
|
|
||||||
{
|
|
||||||
"id": "mk2b6fa491c12445a4376666a32429e1",
|
|
||||||
"name": "example.com",
|
|
||||||
"status": "active"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"result_info": {
|
|
||||||
"count": 1,
|
|
||||||
"page": 1,
|
|
||||||
"per_page": 20,
|
|
||||||
"total_count": 1,
|
|
||||||
"total_pages": 1
|
|
||||||
},
|
|
||||||
"success": true
|
|
||||||
}`)
|
|
||||||
|
|
||||||
var resp ZoneResponse
|
|
||||||
err := json.NewDecoder(s).Decode(&resp)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err.Error())
|
|
||||||
}
|
|
||||||
if resp.Success != true {
|
|
||||||
t.Errorf("Success Error: %#v != true ", resp.Success)
|
|
||||||
}
|
|
||||||
if resp.Zones[0].ID != "mk2b6fa491c12445a4376666a32429e1" {
|
|
||||||
t.Errorf("ID Error: %#v != mk2b6fa491c12445a4376666a32429e1 ", resp.Zones[0].ID)
|
|
||||||
}
|
|
||||||
if resp.Zones[0].Name != "example.com" {
|
|
||||||
t.Errorf("Name Error: %#v != example.com", resp.Zones[0].Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDNSResponseToJSON(t *testing.T) {
|
|
||||||
s := strings.NewReader(`
|
|
||||||
{
|
|
||||||
"errors": [],
|
|
||||||
"messages": [],
|
|
||||||
"result": [
|
|
||||||
{
|
|
||||||
"content": "127.0.0.1",
|
|
||||||
"id": "F11cc63e02a42d38174b8e7c548a7b6f",
|
|
||||||
"name": "example.com",
|
|
||||||
"type": "A",
|
|
||||||
"zone_id": "mk2b6fa491c12445a4376666a32429e1",
|
|
||||||
"zone_name": "example.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"success": true
|
|
||||||
}`)
|
|
||||||
|
|
||||||
var resp DNSRecordResponse
|
|
||||||
err := json.NewDecoder(s).Decode(&resp)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err.Error())
|
|
||||||
}
|
|
||||||
if resp.Success != true {
|
|
||||||
t.Errorf("Success Error: %#v != true ", resp.Success)
|
|
||||||
}
|
|
||||||
if resp.Records[0].ID != "F11cc63e02a42d38174b8e7c548a7b6f" {
|
|
||||||
t.Errorf("ID Error: %#v != F11cc63e02a42d38174b8e7c548a7b6f ", resp.Records[0].ID)
|
|
||||||
}
|
|
||||||
if resp.Records[0].Name != "example.com" {
|
|
||||||
t.Errorf("Name Error: %#v != example.com", resp.Records[0].Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestDNSUpdateResponseToJSON(t *testing.T) {
|
|
||||||
s := strings.NewReader(`
|
|
||||||
{
|
|
||||||
"result": {
|
|
||||||
"id": "F11cc63e02a42d38174b8e7c548a7b6f",
|
|
||||||
"type": "A",
|
|
||||||
"name": "example.com",
|
|
||||||
"content": "127.0.0.1",
|
|
||||||
"proxiable": true,
|
|
||||||
"proxied": true,
|
|
||||||
"ttl": 1,
|
|
||||||
"locked": false,
|
|
||||||
"zone_id": "mk2b6fa491c12445a4376666a32429e1",
|
|
||||||
"zone_name": "example.com",
|
|
||||||
"modified_on": "2018-10-12T14:29:53.205191Z",
|
|
||||||
"created_on": "2018-10-12T14:29:53.205191Z",
|
|
||||||
"meta": {
|
|
||||||
"auto_added": false,
|
|
||||||
"managed_by_apps": false,
|
|
||||||
"managed_by_argo_tunnel": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"success": true,
|
|
||||||
"errors": [],
|
|
||||||
"messages": []
|
|
||||||
}`)
|
|
||||||
|
|
||||||
var resp DNSRecordUpdateResponse
|
|
||||||
err := json.NewDecoder(s).Decode(&resp)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err.Error())
|
|
||||||
}
|
|
||||||
if resp.Success != true {
|
|
||||||
t.Errorf("Success Error: %#v != true ", resp.Success)
|
|
||||||
}
|
|
||||||
if resp.Record.ID != "F11cc63e02a42d38174b8e7c548a7b6f" {
|
|
||||||
t.Errorf("ID Error: %#v != F11cc63e02a42d38174b8e7c548a7b6f ", resp.Record.ID)
|
|
||||||
}
|
|
||||||
if resp.Record.Name != "example.com" {
|
|
||||||
t.Errorf("Name Error: %#v != example.com", resp.Record.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecordTracked(t *testing.T) {
|
|
||||||
s := strings.NewReader(`
|
|
||||||
{
|
|
||||||
"errors": [],
|
|
||||||
"messages": [],
|
|
||||||
"result": [
|
|
||||||
{
|
|
||||||
"content": "127.0.0.1",
|
|
||||||
"id": "F11cc63e02a42d38174b8e7c548a7b6f",
|
|
||||||
"name": "example.com",
|
|
||||||
"type": "A",
|
|
||||||
"zone_id": "mk2b6fa491c12445a4376666a32429e1",
|
|
||||||
"zone_name": "example.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": "127.0.0.1",
|
|
||||||
"id": "G00cc63e02a42d38174b8e7c548a7b6f",
|
|
||||||
"name": "www.example.com",
|
|
||||||
"type": "A",
|
|
||||||
"zone_id": "mk2b6fa491c12445a4376666a32429e1",
|
|
||||||
"zone_name": "www.example.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"success": true
|
|
||||||
}`)
|
|
||||||
|
|
||||||
var resp DNSRecordResponse
|
|
||||||
err := json.NewDecoder(s).Decode(&resp)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := &godns.Domain{
|
|
||||||
DomainName: "example.com",
|
|
||||||
SubDomains: []string{"www"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rec := range resp.Records {
|
|
||||||
if recordTracked(domain, &rec) {
|
|
||||||
t.Logf("Record founded: %+v\r\n", rec.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,292 +0,0 @@
|
|||||||
package dnspod
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"runtime/debug"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
"github.com/bitly/go-simplejson"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler struct definition
|
|
||||||
type Handler struct {
|
|
||||||
Configuration *godns.Settings
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfiguration pass dns settings and store it to handler instance
|
|
||||||
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
|
|
||||||
handler.Configuration = conf
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainLoop the main logic loop
|
|
||||||
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
|
||||||
panicChan <- *domain
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
looping := false
|
|
||||||
for {
|
|
||||||
if looping {
|
|
||||||
// Sleep with interval
|
|
||||||
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
|
||||||
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
looping = true
|
|
||||||
|
|
||||||
log.Printf("Checking IP for domain %s \r\n", domain.DomainName)
|
|
||||||
domainID := handler.GetDomain(domain.DomainName)
|
|
||||||
|
|
||||||
if domainID == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("get_currentIP:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Println("currentIP is:", currentIP)
|
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
hostname := subDomain + "." + domain.DomainName
|
|
||||||
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//check against currently known IP, if no change, skip update
|
|
||||||
if currentIP == lastIP {
|
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
|
||||||
} else {
|
|
||||||
lastIP = currentIP
|
|
||||||
|
|
||||||
subDomainID, ip := handler.GetSubDomain(domainID, subDomain)
|
|
||||||
|
|
||||||
if subDomainID == "" || ip == "" {
|
|
||||||
log.Printf("Domain or subdomain not configured yet. domain: %s.%s subDomainID: %s ip: %s\n", subDomain, domain.DomainName, subDomainID, ip)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue to check the IP of subdomain
|
|
||||||
if len(ip) > 0 && strings.TrimRight(currentIP, "\n") != strings.TrimRight(ip, "\n") {
|
|
||||||
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
|
||||||
handler.UpdateIP(domainID, subDomainID, subDomain, currentIP)
|
|
||||||
|
|
||||||
// Send notification
|
|
||||||
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
|
|
||||||
log.Println("Failed to send notification")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("%s.%s Current IP is same as domain IP, no need to update...\n", subDomain, domain.DomainName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateHeader generates the request header for DNSPod API
|
|
||||||
func (handler *Handler) GenerateHeader(content url.Values) url.Values {
|
|
||||||
header := url.Values{}
|
|
||||||
if handler.Configuration.LoginToken != "" {
|
|
||||||
header.Add("login_token", handler.Configuration.LoginToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
header.Add("format", "json")
|
|
||||||
header.Add("lang", "en")
|
|
||||||
header.Add("error_on_empty", "no")
|
|
||||||
|
|
||||||
if content != nil {
|
|
||||||
for k := range content {
|
|
||||||
header.Add(k, content.Get(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return header
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDomain returns specific domain by name
|
|
||||||
func (handler *Handler) GetDomain(name string) int64 {
|
|
||||||
|
|
||||||
var ret int64
|
|
||||||
values := url.Values{}
|
|
||||||
values.Add("type", "all")
|
|
||||||
values.Add("offset", "0")
|
|
||||||
values.Add("length", "20")
|
|
||||||
|
|
||||||
response, err := handler.PostData("/Domain.List", values)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to get domain list...")
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
sjson, parseErr := simplejson.NewJson([]byte(response))
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
log.Println(parseErr)
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if sjson.Get("status").Get("code").MustString() == "1" {
|
|
||||||
domains, _ := sjson.Get("domains").Array()
|
|
||||||
|
|
||||||
for _, d := range domains {
|
|
||||||
m := d.(map[string]interface{})
|
|
||||||
if m["name"] == name {
|
|
||||||
id := m["id"]
|
|
||||||
|
|
||||||
switch t := id.(type) {
|
|
||||||
case json.Number:
|
|
||||||
ret, _ = t.Int64()
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(domains) == 0 {
|
|
||||||
log.Println("domains slice is empty.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Println("get_domain:status code:", sjson.Get("status").Get("code").MustString())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSubDomain returns subdomain by domain id
|
|
||||||
func (handler *Handler) GetSubDomain(domainID int64, name string) (string, string) {
|
|
||||||
var ret, ip string
|
|
||||||
value := url.Values{}
|
|
||||||
value.Add("domain_id", strconv.FormatInt(domainID, 10))
|
|
||||||
value.Add("offset", "0")
|
|
||||||
value.Add("length", "1")
|
|
||||||
value.Add("sub_domain", name)
|
|
||||||
|
|
||||||
if handler.Configuration.IPType == "" || strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
|
|
||||||
value.Add("record_type", "A")
|
|
||||||
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
|
||||||
value.Add("record_type", "AAAA")
|
|
||||||
} else {
|
|
||||||
log.Println("Error: must specify \"ip_type\" in config for DNSPod.")
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := handler.PostData("/Record.List", value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to get domain list")
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
sjson, parseErr := simplejson.NewJson([]byte(response))
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
log.Println(parseErr)
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if sjson.Get("status").Get("code").MustString() == "1" {
|
|
||||||
records, _ := sjson.Get("records").Array()
|
|
||||||
|
|
||||||
for _, d := range records {
|
|
||||||
m := d.(map[string]interface{})
|
|
||||||
if m["name"] == name {
|
|
||||||
ret = m["id"].(string)
|
|
||||||
ip = m["value"].(string)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(records) == 0 {
|
|
||||||
log.Println("records slice is empty.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Println("get_subdomain:status code:", sjson.Get("status").Get("code").MustString())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateIP update subdomain with current IP
|
|
||||||
func (handler *Handler) UpdateIP(domainID int64, subDomainID string, subDomainName string, ip string) {
|
|
||||||
value := url.Values{}
|
|
||||||
value.Add("domain_id", strconv.FormatInt(domainID, 10))
|
|
||||||
value.Add("record_id", subDomainID)
|
|
||||||
value.Add("sub_domain", subDomainName)
|
|
||||||
|
|
||||||
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
|
|
||||||
value.Add("record_type", "A")
|
|
||||||
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
|
||||||
value.Add("record_type", "AAAA")
|
|
||||||
} else {
|
|
||||||
log.Println("Error: must specify \"ip_type\" in config for DNSPod.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
value.Add("record_line", "默认")
|
|
||||||
value.Add("value", ip)
|
|
||||||
|
|
||||||
response, err := handler.PostData("/Record.Modify", value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to update record to new IP!")
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sjson, parseErr := simplejson.NewJson([]byte(response))
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
log.Println(parseErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if sjson.Get("status").Get("code").MustString() == "1" {
|
|
||||||
log.Println("New IP updated!")
|
|
||||||
} else {
|
|
||||||
log.Println("Failed to update IP record:", sjson.Get("status").Get("message").MustString())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostData post data and invoke DNSPod API
|
|
||||||
func (handler *Handler) PostData(url string, content url.Values) (string, error) {
|
|
||||||
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
|
||||||
|
|
||||||
if client == nil {
|
|
||||||
return "", errors.New("failed to create HTTP client")
|
|
||||||
}
|
|
||||||
|
|
||||||
values := handler.GenerateHeader(content)
|
|
||||||
req, _ := http.NewRequest("POST", "https://dnsapi.cn"+url, strings.NewReader(values.Encode()))
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
req.Header.Set("User-Agent", fmt.Sprintf("GoDNS/0.1 (%s)", ""))
|
|
||||||
|
|
||||||
response, err := client.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Post failed...")
|
|
||||||
log.Println(err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
resp, _ := ioutil.ReadAll(response.Body)
|
|
||||||
|
|
||||||
return string(resp), nil
|
|
||||||
}
|
|
@ -1,138 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
package duck
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DuckUrl the API address for Duck DNS
|
|
||||||
DuckUrl = "https://www.duckdns.org/update?domains=%s&token=%s&%s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler struct
|
|
||||||
type Handler struct {
|
|
||||||
Configuration *godns.Settings
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfiguration pass dns settings and store it to handler instance
|
|
||||||
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
|
|
||||||
handler.Configuration = conf
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainLoop the main logic loop
|
|
||||||
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
|
||||||
panicChan <- *domain
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
looping := false
|
|
||||||
|
|
||||||
for {
|
|
||||||
if looping {
|
|
||||||
// Sleep with interval
|
|
||||||
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
|
||||||
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
looping = true
|
|
||||||
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("get_currentIP:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("currentIP is:", currentIP)
|
|
||||||
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
|
||||||
var ip string
|
|
||||||
|
|
||||||
if strings.ToUpper(handler.Configuration.IPType) == godns.IPV4 {
|
|
||||||
ip = fmt.Sprintf("ip=%s", currentIP)
|
|
||||||
} else if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 {
|
|
||||||
ip = fmt.Sprintf("ipv6=%s", currentIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
hostname := subDomain + "." + domain.DomainName
|
|
||||||
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//check against currently known IP, if no change, skip update
|
|
||||||
if currentIP == lastIP {
|
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
|
||||||
} else {
|
|
||||||
// update IP with HTTP GET request
|
|
||||||
resp, err := client.Get(fmt.Sprintf(DuckUrl, subDomain, handler.Configuration.LoginToken, ip))
|
|
||||||
if err != nil {
|
|
||||||
// handle error
|
|
||||||
log.Print("Failed to update sub domain:", subDomain)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil || string(body) != "OK" {
|
|
||||||
log.Println("Failed to update the IP")
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
log.Print("IP updated to:", currentIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send notification
|
|
||||||
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
|
|
||||||
log.Println("Failed to send notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package google
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// GoogleURL the API address for Google Domains
|
|
||||||
GoogleURL = "https://%s:%s@domains.google.com/nic/update?hostname=%s.%s&myip=%s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler struct
|
|
||||||
type Handler struct {
|
|
||||||
Configuration *godns.Settings
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfiguration pass dns settings and store it to handler instance
|
|
||||||
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
|
|
||||||
handler.Configuration = conf
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainLoop the main logic loop
|
|
||||||
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
|
||||||
panicChan <- *domain
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
looping := false
|
|
||||||
|
|
||||||
for {
|
|
||||||
if looping {
|
|
||||||
// Sleep with interval
|
|
||||||
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
|
||||||
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
looping = true
|
|
||||||
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("get_currentIP:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Println("currentIP is:", currentIP)
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
hostname := subDomain + "." + domain.DomainName
|
|
||||||
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//check against currently known IP, if no change, skip update
|
|
||||||
if currentIP == lastIP {
|
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
|
||||||
} else {
|
|
||||||
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
|
||||||
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
|
|
||||||
|
|
||||||
// Send notification
|
|
||||||
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
|
|
||||||
log.Println("Failed to send notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateIP update subdomain with current IP
|
|
||||||
func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
|
|
||||||
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
|
||||||
resp, err := client.Get(fmt.Sprintf(GoogleURL,
|
|
||||||
handler.Configuration.Email,
|
|
||||||
handler.Configuration.Password,
|
|
||||||
subDomain,
|
|
||||||
domain,
|
|
||||||
currentIP))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// handle error
|
|
||||||
log.Print("Failed to update sub domain:", subDomain)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Request error...")
|
|
||||||
log.Println("Err:", err.Error())
|
|
||||||
} else {
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
if strings.Contains(string(body), "good") {
|
|
||||||
log.Println("Update IP success:", string(body))
|
|
||||||
} else if strings.Contains(string(body), "nochg") {
|
|
||||||
log.Println("IP not changed:", string(body))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Println("Update IP failed:", string(body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
"github.com/TimothyYe/godns/handler/alidns"
|
|
||||||
"github.com/TimothyYe/godns/handler/cloudflare"
|
|
||||||
"github.com/TimothyYe/godns/handler/dnspod"
|
|
||||||
"github.com/TimothyYe/godns/handler/dreamhost"
|
|
||||||
"github.com/TimothyYe/godns/handler/duck"
|
|
||||||
"github.com/TimothyYe/godns/handler/google"
|
|
||||||
"github.com/TimothyYe/godns/handler/he"
|
|
||||||
"github.com/TimothyYe/godns/handler/noip"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IHandler is the interface for all DNS handlers
|
|
||||||
type IHandler interface {
|
|
||||||
SetConfiguration(*godns.Settings)
|
|
||||||
DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHandler creates DNS handler by different providers
|
|
||||||
func CreateHandler(provider string) IHandler {
|
|
||||||
var handler IHandler
|
|
||||||
|
|
||||||
switch provider {
|
|
||||||
case godns.CLOUDFLARE:
|
|
||||||
handler = IHandler(&cloudflare.Handler{})
|
|
||||||
case godns.DNSPOD:
|
|
||||||
handler = IHandler(&dnspod.Handler{})
|
|
||||||
case godns.DREAMHOST:
|
|
||||||
handler = IHandler(&dreamhost.Handler{})
|
|
||||||
case godns.HE:
|
|
||||||
handler = IHandler(&he.Handler{})
|
|
||||||
case godns.ALIDNS:
|
|
||||||
handler = IHandler(&alidns.Handler{})
|
|
||||||
case godns.GOOGLE:
|
|
||||||
handler = IHandler(&google.Handler{})
|
|
||||||
case godns.DUCK:
|
|
||||||
handler = IHandler(&duck.Handler{})
|
|
||||||
case godns.NOIP:
|
|
||||||
handler = IHandler(&noip.Handler{})
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
package he
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/TimothyYe/godns"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// HEUrl the API address for he.net
|
|
||||||
HEUrl = "https://dyn.dns.he.net/nic/update"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler struct
|
|
||||||
type Handler struct {
|
|
||||||
Configuration *godns.Settings
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfiguration pass dns settings and store it to handler instance
|
|
||||||
func (handler *Handler) SetConfiguration(conf *godns.Settings) {
|
|
||||||
handler.Configuration = conf
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainLoop the main logic loop
|
|
||||||
func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
|
||||||
panicChan <- *domain
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
looping := false
|
|
||||||
for {
|
|
||||||
if looping {
|
|
||||||
// Sleep with interval
|
|
||||||
log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval)
|
|
||||||
time.Sleep(time.Second * time.Duration(handler.Configuration.Interval))
|
|
||||||
}
|
|
||||||
looping = true
|
|
||||||
|
|
||||||
currentIP, err := godns.GetCurrentIP(handler.Configuration)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("get_currentIP:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Println("currentIP is:", currentIP)
|
|
||||||
|
|
||||||
//check against locally cached IP, if no change, skip update
|
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
hostname := subDomain + "." + domain.DomainName
|
|
||||||
lastIP, err := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//check against currently known IP, if no change, skip update
|
|
||||||
if currentIP == lastIP {
|
|
||||||
log.Printf("IP is the same as cached one. Skip update.\n")
|
|
||||||
} else {
|
|
||||||
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
|
||||||
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
|
|
||||||
|
|
||||||
// Send notification
|
|
||||||
if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil {
|
|
||||||
log.Println("Failed to send notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateIP update subdomain with current IP
|
|
||||||
func (handler *Handler) UpdateIP(domain, subDomain, currentIP string) {
|
|
||||||
values := url.Values{}
|
|
||||||
values.Add("hostname", fmt.Sprintf("%s.%s", subDomain, domain))
|
|
||||||
values.Add("password", handler.Configuration.Password)
|
|
||||||
values.Add("myip", currentIP)
|
|
||||||
|
|
||||||
client := godns.GetHttpClient(handler.Configuration, handler.Configuration.UseProxy)
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", HEUrl, strings.NewReader(values.Encode()))
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Request error...")
|
|
||||||
log.Println("Err:", err.Error())
|
|
||||||
} else {
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
log.Println("Update IP success:", string(body))
|
|
||||||
} else {
|
|
||||||
log.Println("Update IP failed:", string(body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
93
he_handler.go
Normal file
93
he_handler.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
HEUrl = "https://dyn.dns.he.net/nic/update"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HEHandler struct{}
|
||||||
|
|
||||||
|
func (handler *HEHandler) DomainLoop(domain *Domain) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
panicCount++
|
||||||
|
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
||||||
|
fmt.Println(identifyPanic())
|
||||||
|
log.Print(identifyPanic())
|
||||||
|
if panicCount < PANIC_MAX {
|
||||||
|
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
|
||||||
|
go handler.DomainLoop(domain)
|
||||||
|
} else {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
currentIP, err := getCurrentIP(configuration.IPUrl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("get_currentIP:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Println("currentIp is:", currentIP)
|
||||||
|
|
||||||
|
for _, subDomain := range domain.SubDomains {
|
||||||
|
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
||||||
|
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Interval is 5 minutes
|
||||||
|
time.Sleep(time.Minute * INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *HEHandler) UpdateIP(domain, subDomain, currentIP string) {
|
||||||
|
values := url.Values{}
|
||||||
|
values.Add("hostname", fmt.Sprintf("%s.%s", subDomain, domain))
|
||||||
|
values.Add("password", configuration.Password)
|
||||||
|
values.Add("myip", currentIP)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
httpTransport := &http.Transport{}
|
||||||
|
client.Transport = httpTransport
|
||||||
|
httpTransport.Dial = dialer.Dial
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("POST", HEUrl, strings.NewReader(values.Encode()))
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Request error...")
|
||||||
|
log.Println("Err:", err.Error())
|
||||||
|
} else {
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
log.Println("Update IP success:", string(body))
|
||||||
|
} else {
|
||||||
|
log.Println("Update IP failed:", string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
310
logger.go
Normal file
310
logger.go
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// L_INFO log level
|
||||||
|
L_INFO int = iota
|
||||||
|
// L_WARNING log level
|
||||||
|
L_WARNING
|
||||||
|
// L_DEBUG log level
|
||||||
|
L_DEBUG
|
||||||
|
// PRE_INFO log level
|
||||||
|
PRE_INFO = "[ INFO]"
|
||||||
|
// PRE_WARNING log level
|
||||||
|
PRE_WARNING = "[WARNING]"
|
||||||
|
// PRE_DEBUG log level
|
||||||
|
PRE_DEBUG = "[ DEBUG]"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger struct
|
||||||
|
type Logger struct {
|
||||||
|
DEV_MODE bool
|
||||||
|
fd *os.File
|
||||||
|
size int
|
||||||
|
num int
|
||||||
|
level int
|
||||||
|
mu sync.Mutex
|
||||||
|
muSplit sync.Mutex
|
||||||
|
flushInterval int64 //Second
|
||||||
|
flushSize int
|
||||||
|
buf *bytes.Buffer
|
||||||
|
log *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogger returns a new created logger
|
||||||
|
func NewLogger(logfile string, size, num int, level int, flushInterval int64, flushSize int) (logger *Logger, err error) {
|
||||||
|
if size < 1 || num < 1 || level < L_INFO || len(logfile) < 1 {
|
||||||
|
err = errors.New("NewLogWriter:param error.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger = &Logger{size: size * 1024, num: num, level: level, DEV_MODE: false}
|
||||||
|
logger.fd, err = os.OpenFile(logfile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModeAppend|0666)
|
||||||
|
if err != nil {
|
||||||
|
logger = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.SetOutput(logger)
|
||||||
|
if flushInterval > 0 && flushSize > 0 {
|
||||||
|
logger.buf = new(bytes.Buffer)
|
||||||
|
logger.log = log.New(logger.buf, "", log.LstdFlags)
|
||||||
|
|
||||||
|
go func(interval int64, logger *Logger) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("logger Tick, Recovered in %v:\n %s", r, debug.Stack())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c := time.Tick(time.Duration(interval) * time.Second)
|
||||||
|
for _ = range c {
|
||||||
|
logger.Flush()
|
||||||
|
}
|
||||||
|
}(flushInterval, logger)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitLogger initialize logger with specified log filename & size
|
||||||
|
func InitLogger(logfile string, size, num int) (err error) {
|
||||||
|
logger, err := NewLogger(logfile, size, num, L_INFO, -1, -1)
|
||||||
|
if logger != nil {
|
||||||
|
logger.level = L_INFO - 1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// immplement write
|
||||||
|
func (logger *Logger) Write(p []byte) (n int, err error) {
|
||||||
|
if logger.DEV_MODE {
|
||||||
|
n, err = os.Stdout.Write(p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = logger.fd.Write(p)
|
||||||
|
if err == nil {
|
||||||
|
fi, e := logger.fd.Stat()
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fi.Size() > int64(logger.size) {
|
||||||
|
logger.muSplit.Lock()
|
||||||
|
defer logger.muSplit.Unlock()
|
||||||
|
|
||||||
|
fname := fi.Name()
|
||||||
|
strings.HasSuffix(fname, ".log")
|
||||||
|
fbase := fname[:len(fname)-3]
|
||||||
|
|
||||||
|
oldBs := make([]byte, 0, logger.size)
|
||||||
|
newBs := []byte{}
|
||||||
|
fd, e := os.Open(fname)
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rd := bufio.NewReader(fd)
|
||||||
|
for {
|
||||||
|
line, e := rd.ReadBytes('\n')
|
||||||
|
if e == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(oldBs)+len(line) > logger.size {
|
||||||
|
newBs = append(newBs, line...)
|
||||||
|
} else {
|
||||||
|
oldBs = append(oldBs, line...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
|
||||||
|
_, err = logger.saveLog(1, fbase, oldBs)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = logger.fd.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.Remove(fname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.fd, err = os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModeAppend|0666)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = logger.fd.Write(newBs)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) saveLog(index int, fbase string, data []byte) (n int, err error) {
|
||||||
|
fn := fbase + strconv.Itoa(index) + ".log"
|
||||||
|
_, err = os.Stat(fn)
|
||||||
|
if index < logger.num && err == nil {
|
||||||
|
var b []byte
|
||||||
|
b, err = ioutil.ReadFile(fn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = logger.saveLog(index+1, fbase, b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.OpenFile(fn, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm|0666)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
n, err = fd.Write(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush buf data to std log
|
||||||
|
func (logger *Logger) Flush() {
|
||||||
|
if logger.buf.Len() > 0 {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.Print(logger.buf)
|
||||||
|
log.SetFlags(log.LstdFlags)
|
||||||
|
logger.buf.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean prefix and check buf size
|
||||||
|
func (logger *Logger) clean() {
|
||||||
|
logger.log.SetPrefix("")
|
||||||
|
if logger.buf.Len()/1024 > logger.flushSize {
|
||||||
|
go logger.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) setPrefix(lv int) bool {
|
||||||
|
if lv > logger.level {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch lv {
|
||||||
|
case L_INFO:
|
||||||
|
logger.log.SetPrefix(PRE_INFO)
|
||||||
|
case L_WARNING:
|
||||||
|
logger.log.SetPrefix(PRE_WARNING)
|
||||||
|
case L_DEBUG:
|
||||||
|
logger.log.SetPrefix(PRE_DEBUG)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) logPrint(lv int, args ...interface{}) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
|
||||||
|
if !logger.setPrefix(lv) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.log.Print(args...)
|
||||||
|
logger.clean()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) logPrintln(lv int, args ...interface{}) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
|
||||||
|
if !logger.setPrefix(lv) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.log.Println(args...)
|
||||||
|
logger.clean()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) logPrintf(lv int, format string, args ...interface{}) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
|
||||||
|
if !logger.setPrefix(lv) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.log.Printf(format, args...)
|
||||||
|
logger.clean()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close fd
|
||||||
|
func (logger *Logger) Close() {
|
||||||
|
if logger.fd != nil {
|
||||||
|
logger.Flush()
|
||||||
|
logger.fd.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info output info log
|
||||||
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
|
logger.logPrint(L_INFO, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln output info log with newline
|
||||||
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
|
logger.logPrintln(L_INFO, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof output formatted info log
|
||||||
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
logger.logPrintf(L_INFO, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning output warning log
|
||||||
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
|
logger.logPrint(L_WARNING, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Warningln output warning log with newline
|
||||||
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
|
logger.logPrintln(L_WARNING, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf output formatted warning log
|
||||||
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
|
logger.logPrintf(L_WARNING, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug output debug log
|
||||||
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
|
logger.logPrint(L_DEBUG, args...)
|
||||||
|
logger.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugln output debug log with newline
|
||||||
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
|
logger.logPrintln(L_DEBUG, args...)
|
||||||
|
logger.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf output formatted debug log
|
||||||
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
logger.logPrintf(L_DEBUG, format, args...)
|
||||||
|
logger.Flush()
|
||||||
|
}
|
@ -1,110 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
81
settings.go
81
settings.go
@ -1,9 +1,10 @@
|
|||||||
package godns
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Domain struct
|
//Domain struct
|
||||||
@ -12,41 +13,6 @@ 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
|
|
||||||
type TelegramNotify struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
BotApiKey string `json:"bot_api_key"`
|
|
||||||
ChatId string `json:"chat_id"`
|
|
||||||
MsgTemplate string `json:"message_template"`
|
|
||||||
UseProxy bool `json:"use_proxy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify struct for SMTP notification
|
|
||||||
type MailNotify struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
SMTPServer string `json:"smtp_server"`
|
|
||||||
SMTPUsername string `json:"smtp_username"`
|
|
||||||
SMTPPassword string `json:"smtp_password"`
|
|
||||||
SMTPPort int `json:"smtp_port"`
|
|
||||||
SendTo string `json:"send_to"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify struct
|
|
||||||
type Notify struct {
|
|
||||||
Telegram TelegramNotify `json:"telegram"`
|
|
||||||
Mail MailNotify `json:"mail"`
|
|
||||||
Slack SlackNotify `json:"slack"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//Settings struct
|
//Settings struct
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
@ -55,16 +21,10 @@ type Settings struct {
|
|||||||
LoginToken string `json:"login_token"`
|
LoginToken string `json:"login_token"`
|
||||||
Domains []Domain `json:"domains"`
|
Domains []Domain `json:"domains"`
|
||||||
IPUrl string `json:"ip_url"`
|
IPUrl string `json:"ip_url"`
|
||||||
IPV6Url string `json:"ipv6_url"`
|
|
||||||
Interval int `json:"interval"`
|
|
||||||
UserAgent string `json:"user_agent,omitempty"`
|
|
||||||
LogPath string `json:"log_path"`
|
LogPath string `json:"log_path"`
|
||||||
|
LogSize int `json:"log_size"`
|
||||||
|
LogNum int `json:"log_num"`
|
||||||
Socks5Proxy string `json:"socks5_proxy"`
|
Socks5Proxy string `json:"socks5_proxy"`
|
||||||
Notify Notify `json:"notify"`
|
|
||||||
IPInterface string `json:"ip_interface"`
|
|
||||||
IPType string `json:"ip_type"`
|
|
||||||
Resolver string `json:"resolver"`
|
|
||||||
UseProxy bool `json:"use_proxy"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//LoadSettings -- Load settings from config file
|
//LoadSettings -- Load settings from config file
|
||||||
@ -82,9 +42,36 @@ func LoadSettings(configPath string, settings *Settings) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.Interval == 0 {
|
return nil
|
||||||
// set default interval as 5 minutes if interval is 0
|
}
|
||||||
settings.Interval = 5 * 60
|
|
||||||
|
//LoadDomains -- Load domains from domains string
|
||||||
|
func LoadDomains(domainsOrginStr string, domains *[]Domain) error {
|
||||||
|
|
||||||
|
domainsMap := make(map[string]*Domain)
|
||||||
|
domainsArray := strings.Split(domainsOrginStr, ",")
|
||||||
|
for _, host := range domainsArray {
|
||||||
|
dotCount := strings.Count(host, ".")
|
||||||
|
if dotCount < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
len := len(host)
|
||||||
|
pos := strings.Index(host, ".")
|
||||||
|
subDomain := host[0:pos]
|
||||||
|
domainName := host[pos+1 : len]
|
||||||
|
|
||||||
|
if d, exist := domainsMap[domainName]; exist {
|
||||||
|
d.SubDomains = append(d.SubDomains, subDomain)
|
||||||
|
} else {
|
||||||
|
d := new(Domain)
|
||||||
|
d.DomainName = domainName
|
||||||
|
d.SubDomains = append(d.SubDomains, subDomain)
|
||||||
|
domainsMap[domainName] = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range domainsMap {
|
||||||
|
*domains = append(*domains, *d)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package godns
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -13,11 +13,6 @@ func TestLoadSetting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if settings.IPUrl == "" {
|
if settings.IPUrl == "" {
|
||||||
t.Error("cannot load ip_url from config file")
|
t.Error("Cannot load ip_url from config file")
|
||||||
}
|
|
||||||
|
|
||||||
err = LoadSettings("./file/does/not/exists", &settings)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("file doesn't exist, should return error")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
Before Width: | Height: | Size: 51 KiB |
105
template.go
105
template.go
@ -1,105 +0,0 @@
|
|||||||
package godns
|
|
||||||
|
|
||||||
var mailTemplate = `
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<div role="section">
|
|
||||||
<div style="background-color: #281557;">
|
|
||||||
<div class="layout one-col" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
|
|
||||||
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
|
|
||||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-full-width" style="background-color: #281557;"><td class="layout__edges"> </td><td style="width: 600px" class="w560"><![endif]-->
|
|
||||||
<div class="column" style="max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);text-align: left;color: #8e959c;font-size: 14px;line-height: 21px;font-family: sans-serif;">
|
|
||||||
|
|
||||||
<div style="Margin-left: 20px;Margin-right: 20px;">
|
|
||||||
<div style="mso-line-height-rule: exactly;line-height: 10px;font-size: 1px;"> </div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!--[if (mso)|(IE)]></td><td class="layout__edges"> </td></tr></table><![endif]-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="background-color: #281557;">
|
|
||||||
<div class="layout one-col" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
|
|
||||||
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
|
|
||||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-full-width" style="background-color: #281557;"><td class="layout__edges"> </td><td style="width: 600px" class="w560"><![endif]-->
|
|
||||||
<div class="column" style="max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);text-align: left;color: #8e959c;font-size: 14px;line-height: 21px;font-family: sans-serif;">
|
|
||||||
|
|
||||||
<div style="Margin-left: 20px;Margin-right: 20px;">
|
|
||||||
<div style="mso-line-height-rule: exactly;line-height: 50px;font-size: 1px;"> </div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="Margin-left: 20px;Margin-right: 20px;">
|
|
||||||
<div style="mso-line-height-rule: exactly;mso-text-raise: 4px;">
|
|
||||||
<h1 class="size-28" style="Margin-top: 0;Margin-bottom: 0;font-style: normal;font-weight: normal;color: #000;font-size: 24px;line-height: 32px;font-family: avenir,sans-serif;text-align: center;"
|
|
||||||
lang="x-size-28">
|
|
||||||
<span class="font-avenir">
|
|
||||||
<span style="color:#ffffff">Your IP address has been changed to</span>
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
<h1 class="size-48" style="Margin-top: 20px;Margin-bottom: 0;font-style: normal;font-weight: normal;color: #000;font-size: 36px;line-height: 43px;font-family: avenir,sans-serif;text-align: center;"
|
|
||||||
lang="x-size-48">
|
|
||||||
<span class="font-avenir">
|
|
||||||
<strong>
|
|
||||||
<span style="color:#ffffff">{{ .CurrentIP }}</span>
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
<h2 class="size-28" style="Margin-top: 20px;Margin-bottom: 16px;font-style: normal;font-weight: normal;color: #e31212;font-size: 24px;line-height: 32px;font-family: Avenir,sans-serif;text-align: center;"
|
|
||||||
lang="x-size-28">
|
|
||||||
<font color="#ffffff">
|
|
||||||
<strong>Domain {{ .Domain }} is updated</strong>
|
|
||||||
</font>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="Margin-left: 20px;Margin-right: 20px;">
|
|
||||||
<div style="mso-line-height-rule: exactly;line-height: 15px;font-size: 1px;"> </div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="Margin-left: 20px;Margin-right: 20px;">
|
|
||||||
<div style="mso-line-height-rule: exactly;line-height: 35px;font-size: 1px;"> </div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!--[if (mso)|(IE)]></td><td class="layout__edges"> </td></tr></table><![endif]-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="mso-line-height-rule: exactly;line-height: 20px;font-size: 20px;"> </div>
|
|
||||||
|
|
||||||
|
|
||||||
<div style="mso-line-height-rule: exactly;" role="contentinfo">
|
|
||||||
<div class="layout email-footer" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
|
|
||||||
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
|
|
||||||
<!--[if (mso)|(IE)]><table align="center" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-email-footer"><td style="width: 400px;" valign="top" class="w360"><![endif]-->
|
|
||||||
<div class="column wide" style="text-align: left;font-size: 12px;line-height: 19px;color: #adb3b9;font-family: sans-serif;Float: left;max-width: 400px;min-width: 320px; width: 320px;width: calc(8000% - 47600px);">
|
|
||||||
<div style="Margin-left: 20px;Margin-right: 20px;Margin-top: 10px;Margin-bottom: 10px;">
|
|
||||||
|
|
||||||
<div style="font-size: 12px;line-height: 19px;">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div style="font-size: 12px;line-height: 19px;Margin-top: 18px;">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!--[if mso]> <![endif]-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--[if (mso)|(IE)]></td><td style="width: 200px;" valign="top" class="w160"><![endif]-->
|
|
||||||
<div class="column narrow" style="text-align: left;font-size: 12px;line-height: 19px;color: #adb3b9;font-family: sans-serif;Float: left;max-width: 320px;min-width: 200px; width: 320px;width: calc(72200px - 12000%);">
|
|
||||||
<div style="Margin-left: 20px;Margin-right: 20px;Margin-top: 10px;Margin-bottom: 10px;">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="mso-line-height-rule: exactly;line-height: 40px;font-size: 40px;"> </div>
|
|
||||||
</body>
|
|
||||||
</div>
|
|
||||||
</html>
|
|
||||||
`
|
|
473
utils.go
473
utils.go
@ -1,134 +1,28 @@
|
|||||||
package godns
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
dnsResolver "github.com/TimothyYe/godns/resolver"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
"gopkg.in/gomail.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func getCurrentIP(url string) (string, error) {
|
||||||
// Logo for GoDNS
|
|
||||||
Logo = `
|
|
||||||
|
|
||||||
██████╗ ██████╗ ██████╗ ███╗ ██╗███████╗
|
|
||||||
██╔════╝ ██╔═══██╗██╔══██╗████╗ ██║██╔════╝
|
|
||||||
██║ ███╗██║ ██║██║ ██║██╔██╗ ██║███████╗
|
|
||||||
██║ ██║██║ ██║██║ ██║██║╚██╗██║╚════██║
|
|
||||||
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
|
|
||||||
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
|
||||||
|
|
||||||
GoDNS V%s
|
|
||||||
https://github.com/TimothyYe/godns
|
|
||||||
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PanicMax is the max allowed panic times
|
|
||||||
PanicMax = 5
|
|
||||||
// DNSPOD for dnspod.cn
|
|
||||||
DNSPOD = "DNSPod"
|
|
||||||
// HE for he.net
|
|
||||||
HE = "HE"
|
|
||||||
// CLOUDFLARE for cloudflare.com
|
|
||||||
CLOUDFLARE = "Cloudflare"
|
|
||||||
// ALIDNS for AliDNS
|
|
||||||
ALIDNS = "AliDNS"
|
|
||||||
// GOOGLE for Google Domains
|
|
||||||
GOOGLE = "Google"
|
|
||||||
// DUCK for Duck DNS
|
|
||||||
DUCK = "DuckDNS"
|
|
||||||
// DREAMHOST for Dreamhost
|
|
||||||
DREAMHOST = "Dreamhost"
|
|
||||||
// NOIP for NoIP
|
|
||||||
NOIP = "NoIP"
|
|
||||||
// IPV4 for IPV4 mode
|
|
||||||
IPV4 = "IPV4"
|
|
||||||
// IPV6 for IPV6 mode
|
|
||||||
IPV6 = "IPV6"
|
|
||||||
)
|
|
||||||
|
|
||||||
//GetIPFromInterface gets IP address from the specific interface
|
|
||||||
func GetIPFromInterface(configuration *Settings) (string, error) {
|
|
||||||
ifaces, err := net.InterfaceByName(configuration.IPInterface)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("can't get network device "+configuration.IPInterface+":", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := ifaces.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("can't get address from "+configuration.IPInterface+":", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
var ip net.IP
|
|
||||||
switch v := addr.(type) {
|
|
||||||
case *net.IPNet:
|
|
||||||
ip = v.IP
|
|
||||||
case *net.IPAddr:
|
|
||||||
ip = v.IP
|
|
||||||
}
|
|
||||||
if ip == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(ip.IsGlobalUnicast() &&
|
|
||||||
!(ip.IsUnspecified() ||
|
|
||||||
ip.IsMulticast() ||
|
|
||||||
ip.IsLoopback() ||
|
|
||||||
ip.IsLinkLocalUnicast() ||
|
|
||||||
ip.IsLinkLocalMulticast() ||
|
|
||||||
ip.IsInterfaceLocalMulticast())) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if isIPv4(ip.String()) {
|
|
||||||
if strings.ToUpper(configuration.IPType) != IPV4 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if strings.ToUpper(configuration.IPType) != IPV6 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip.String() != "" {
|
|
||||||
return ip.String(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIPv4(ip string) bool {
|
|
||||||
return strings.Count(ip, ":") < 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHttpClient creates the HTTP client and return it
|
|
||||||
func GetHttpClient(configuration *Settings, useProxy bool) *http.Client {
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
|
|
||||||
if useProxy && configuration.Socks5Proxy != "" {
|
if 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 {
|
||||||
log.Println("can't connect to the proxy:", err)
|
fmt.Println("can't connect to the proxy:", err)
|
||||||
return nil
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
httpTransport := &http.Transport{}
|
httpTransport := &http.Transport{}
|
||||||
@ -136,46 +30,7 @@ func GetHttpClient(configuration *Settings, useProxy bool) *http.Client {
|
|||||||
httpTransport.Dial = dialer.Dial
|
httpTransport.Dial = dialer.Dial
|
||||||
}
|
}
|
||||||
|
|
||||||
return client
|
response, err := client.Get(url)
|
||||||
}
|
|
||||||
|
|
||||||
//GetCurrentIP gets an IP from either internet or specific interface, depending on configuration
|
|
||||||
func GetCurrentIP(configuration *Settings) (string, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if configuration.IPUrl != "" || configuration.IPV6Url != "" {
|
|
||||||
ip, err := GetIPOnline(configuration)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("get ip online failed. Fallback to get ip from interface if possible.")
|
|
||||||
} else {
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if configuration.IPInterface != "" {
|
|
||||||
ip, err := GetIPFromInterface(configuration)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("get ip from interface failed. There is no more ways to try.")
|
|
||||||
} else {
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIPOnline gets public IP from internet
|
|
||||||
func GetIPOnline(configuration *Settings) (string, error) {
|
|
||||||
client := &http.Client{}
|
|
||||||
|
|
||||||
var response *http.Response
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if configuration.IPType == "" || strings.ToUpper(configuration.IPType) == IPV4 {
|
|
||||||
response, err = client.Get(configuration.IPUrl)
|
|
||||||
} else {
|
|
||||||
response, err = client.Get(configuration.IPV6Url)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Cannot get IP...")
|
log.Println("Cannot get IP...")
|
||||||
@ -185,278 +40,54 @@ func GetIPOnline(configuration *Settings) (string, error) {
|
|||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(response.Body)
|
body, _ := ioutil.ReadAll(response.Body)
|
||||||
return strings.Trim(string(body), "\n"), nil
|
return string(body), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckSettings check the format of settings
|
func identifyPanic() string {
|
||||||
func CheckSettings(config *Settings) error {
|
var name, file string
|
||||||
switch config.Provider {
|
var line int
|
||||||
case DNSPOD:
|
var pc [16]uintptr
|
||||||
if config.Password == "" && config.LoginToken == "" {
|
|
||||||
return errors.New("password or login token cannot be empty")
|
n := runtime.Callers(3, pc[:])
|
||||||
|
for _, pc := range pc[:n] {
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
case HE:
|
file, line = fn.FileLine(pc)
|
||||||
|
name = fn.Name()
|
||||||
|
if !strings.HasPrefix(name, "runtime.") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case name != "":
|
||||||
|
return fmt.Sprintf("%v:%v", name, line)
|
||||||
|
case file != "":
|
||||||
|
return fmt.Sprintf("%v:%v", file, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("pc:%x", pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
log.Println("[command] -c=[config file path]")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSettings(config *Settings) error {
|
||||||
|
if config.Provider == DNSPOD {
|
||||||
|
if (config.Email == "" || config.Password == "") && config.LoginToken == "" {
|
||||||
|
return errors.New("Email/Password or login token cannot be empty!")
|
||||||
|
}
|
||||||
|
} else if config.Provider == HE {
|
||||||
if config.Password == "" {
|
if config.Password == "" {
|
||||||
return errors.New("password cannot be empty")
|
return errors.New("Password cannot be empty!")
|
||||||
}
|
}
|
||||||
case CLOUDFLARE:
|
|
||||||
if config.LoginToken == "" {
|
|
||||||
if config.Email == "" {
|
|
||||||
return errors.New("email cannot be empty")
|
|
||||||
}
|
|
||||||
if config.Password == "" {
|
|
||||||
return errors.New("password cannot be empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ALIDNS:
|
|
||||||
if config.Email == "" {
|
|
||||||
return errors.New("email cannot be empty")
|
|
||||||
}
|
|
||||||
if config.Password == "" {
|
|
||||||
return errors.New("password cannot be empty")
|
|
||||||
}
|
|
||||||
case DUCK:
|
|
||||||
if config.LoginToken == "" {
|
|
||||||
return errors.New("login token cannot be empty")
|
|
||||||
}
|
|
||||||
case GOOGLE:
|
|
||||||
fallthrough
|
|
||||||
case NOIP:
|
|
||||||
if config.Email == "" {
|
|
||||||
return errors.New("email cannot be empty")
|
|
||||||
}
|
|
||||||
if config.Password == "" {
|
|
||||||
return errors.New("password cannot be empty")
|
|
||||||
}
|
|
||||||
case DREAMHOST:
|
|
||||||
if config.LoginToken == "" {
|
|
||||||
return errors.New("login token cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS/Dreamhost")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendTelegramNotify sends notify if IP is changed
|
|
||||||
func SendTelegramNotify(configuration *Settings, domain, currentIP string) error {
|
|
||||||
if !configuration.Notify.Telegram.Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if configuration.Notify.Telegram.BotApiKey == "" {
|
|
||||||
return errors.New("bot api key cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if configuration.Notify.Telegram.ChatId == "" {
|
|
||||||
return errors.New("chat id cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := GetHttpClient(configuration, configuration.Notify.Telegram.UseProxy)
|
|
||||||
tpl := configuration.Notify.Telegram.MsgTemplate
|
|
||||||
if tpl == "" {
|
|
||||||
tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated"
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := buildTemplate(currentIP, domain, tpl)
|
|
||||||
reqURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s",
|
|
||||||
configuration.Notify.Telegram.BotApiKey,
|
|
||||||
configuration.Notify.Telegram.ChatId,
|
|
||||||
msg)
|
|
||||||
var response *http.Response
|
|
||||||
var err error
|
|
||||||
|
|
||||||
response, err = client.Get(reqURL)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(response.Body)
|
|
||||||
type ResponseParameters struct {
|
|
||||||
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
|
|
||||||
RetryAfter int `json:"retry_after"` // optional
|
|
||||||
}
|
|
||||||
type APIResponse struct {
|
|
||||||
Ok bool `json:"ok"`
|
|
||||||
Result json.RawMessage `json:"result"`
|
|
||||||
ErrorCode int `json:"error_code"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Parameters *ResponseParameters `json:"parameters"`
|
|
||||||
}
|
|
||||||
var resp APIResponse
|
|
||||||
err = json.Unmarshal(body, &resp)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("error:", err)
|
|
||||||
return errors.New("failed to parse response")
|
|
||||||
}
|
|
||||||
if !resp.Ok {
|
|
||||||
return errors.New(resp.Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMailNotify sends mail notify if IP is changed
|
|
||||||
func SendMailNotify(configuration *Settings, domain, currentIP string) error {
|
|
||||||
if !configuration.Notify.Mail.Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Print("Sending notification to:", configuration.Notify.Mail.SendTo)
|
|
||||||
m := gomail.NewMessage()
|
|
||||||
|
|
||||||
m.SetHeader("From", configuration.Notify.Mail.SMTPUsername)
|
|
||||||
m.SetHeader("To", configuration.Notify.Mail.SendTo)
|
|
||||||
m.SetHeader("Subject", "GoDNS Notification")
|
|
||||||
log.Println("currentIP:", currentIP)
|
|
||||||
log.Println("domain:", domain)
|
|
||||||
m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate))
|
|
||||||
|
|
||||||
d := gomail.NewDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword)
|
|
||||||
|
|
||||||
// Send the email config by sendlist .
|
|
||||||
if err := d.DialAndSend(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendSlack sends slack if IP is changed
|
|
||||||
func SendSlackNotify(configuration *Settings, domain, currentIP string) error {
|
|
||||||
if !configuration.Notify.Slack.Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if configuration.Notify.Slack.BotApiToken == "" {
|
|
||||||
return errors.New("bot api token cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if configuration.Notify.Slack.Channel == "" {
|
|
||||||
return errors.New("channel cannot be empty")
|
|
||||||
}
|
|
||||||
client := GetHttpClient(configuration, configuration.Notify.Slack.UseProxy)
|
|
||||||
tpl := configuration.Notify.Slack.MsgTemplate
|
|
||||||
if tpl == "" {
|
|
||||||
tpl = "_Your IP address is changed to_\n\n*{{ .CurrentIP }}*\n\nDomain *{{ .Domain }}* is updated"
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := buildTemplate(currentIP, domain, tpl)
|
|
||||||
|
|
||||||
var response *http.Response
|
|
||||||
var err error
|
|
||||||
|
|
||||||
formData := url.Values{
|
|
||||||
"token": {configuration.Notify.Slack.BotApiToken},
|
|
||||||
"channel": {configuration.Notify.Slack.Channel},
|
|
||||||
"text": {msg},
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err = client.PostForm("https://slack.com/api/chat.postMessage", formData)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(response.Body)
|
|
||||||
type ResponseParameters struct {
|
|
||||||
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
|
|
||||||
RetryAfter int `json:"retry_after"` // optional
|
|
||||||
}
|
|
||||||
type APIResponse struct {
|
|
||||||
Ok bool `json:"ok"`
|
|
||||||
Result json.RawMessage `json:"result"`
|
|
||||||
ErrorCode int `json:"error_code"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Parameters *ResponseParameters `json:"parameters"`
|
|
||||||
}
|
|
||||||
var resp APIResponse
|
|
||||||
err = json.Unmarshal(body, &resp)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("error:", err)
|
|
||||||
return errors.New("failed to parse response")
|
|
||||||
}
|
|
||||||
if !resp.Ok {
|
|
||||||
return errors.New(resp.Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendNotify sends notify if IP is changed
|
|
||||||
func SendNotify(configuration *Settings, domain, currentIP string) error {
|
|
||||||
err := SendTelegramNotify(configuration, domain, currentIP)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Send telegram notification with error:", err.Error())
|
|
||||||
}
|
|
||||||
err = SendMailNotify(configuration, domain, currentIP)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Send email notification with error:", err.Error())
|
|
||||||
}
|
|
||||||
err = SendSlackNotify(configuration, domain, currentIP)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Send slack notification with error:", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildTemplate(currentIP, domain string, tplsrc string) string {
|
|
||||||
t := template.New("notification template")
|
|
||||||
if _, err := t.Parse(tplsrc); err != nil {
|
|
||||||
log.Println("Failed to parse template")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
data := struct {
|
|
||||||
CurrentIP string
|
|
||||||
Domain string
|
|
||||||
}{
|
|
||||||
currentIP,
|
|
||||||
domain,
|
|
||||||
}
|
|
||||||
|
|
||||||
var tpl bytes.Buffer
|
|
||||||
if err := t.Execute(&tpl, data); err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return tpl.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveDNS will query DNS for a given hostname.
|
|
||||||
func ResolveDNS(hostname, resolver, ipType string) (string, error) {
|
|
||||||
var dnsType uint16
|
|
||||||
if ipType == "" || strings.ToUpper(ipType) == IPV4 {
|
|
||||||
dnsType = dns.TypeA
|
|
||||||
} else {
|
} else {
|
||||||
dnsType = dns.TypeAAAA
|
return errors.New("Please provide supported DNS provider: DNSPod/HE")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no DNS server is set in config file, falls back to default resolver.
|
return nil
|
||||||
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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package godns
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetCurrentIP(t *testing.T) {
|
func testGetCurrentIP(t *testing.T) {
|
||||||
conf := &Settings{IPUrl: "https://myip.biturl.top"}
|
ip, _ := getCurrentIP("http://members.3322.org/dyndns/getip")
|
||||||
ip, _ := GetCurrentIP(conf)
|
|
||||||
|
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
t.Log("IP is empty...")
|
t.Log("IP is empty...")
|
||||||
@ -14,29 +13,3 @@ func TestGetCurrentIP(t *testing.T) {
|
|||||||
t.Log("IP is:" + ip)
|
t.Log("IP is:" + ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckSettings(t *testing.T) {
|
|
||||||
settingError := &Settings{}
|
|
||||||
if err := CheckSettings(settingError); err == nil {
|
|
||||||
t.Error("setting is invalid, should return error")
|
|
||||||
}
|
|
||||||
|
|
||||||
settingDNSPod := &Settings{Provider: "DNSPod", LoginToken: "aaa"}
|
|
||||||
if err := CheckSettings(settingDNSPod); err == nil {
|
|
||||||
t.Log("setting with login token, passed")
|
|
||||||
} else {
|
|
||||||
t.Error("setting with login token, should be passed")
|
|
||||||
}
|
|
||||||
|
|
||||||
settingDNSPod = &Settings{Provider: "DNSPod"}
|
|
||||||
if err := CheckSettings(settingDNSPod); err == nil {
|
|
||||||
t.Error("setting with invalid parameters, should be failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
settingHE := &Settings{Provider: "HE", Password: ""}
|
|
||||||
if err := CheckSettings(settingHE); err != nil {
|
|
||||||
t.Log("HE setting without password, passed")
|
|
||||||
} else {
|
|
||||||
t.Error("HE setting without password, should be faild")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user