mirror of
https://github.com/taigrr/godns
synced 2025-01-18 04:03:25 -08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
5344c3c393
5
.gitignore
vendored
5
.gitignore
vendored
@ -28,10 +28,13 @@ _testmain.go
|
|||||||
config.json
|
config.json
|
||||||
*.log
|
*.log
|
||||||
*.swp
|
*.swp
|
||||||
godns
|
*.gz
|
||||||
|
cmd/godns/godns
|
||||||
|
|
||||||
vendor/*
|
vendor/*
|
||||||
/.idea
|
/.idea
|
||||||
/godns.iml
|
/godns.iml
|
||||||
/godns.ipr
|
/godns.ipr
|
||||||
/godns.iws
|
/godns.iws
|
||||||
|
.current_ip
|
||||||
|
.DS_Store
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
before_script:
|
|
||||||
- export GOPATH=$(pwd)
|
|
||||||
- export GOBIN=$GOPATH/bin
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- build
|
|
||||||
- test
|
|
||||||
|
|
||||||
build-my-project:
|
|
||||||
image: golang:1.6.2
|
|
||||||
stage: build
|
|
||||||
script:
|
|
||||||
- mkdir $GOPATH/bin
|
|
||||||
- go get
|
|
||||||
- go build
|
|
||||||
|
|
||||||
test-my-project:
|
|
||||||
image: golang:1.6.2
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- mkdir $GOPATH/bin
|
|
||||||
- go get
|
|
||||||
- go build
|
|
||||||
- go test
|
|
10
.travis.yml
10
.travis.yml
@ -1,7 +1,13 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.7
|
- 1.7.x
|
||||||
- 1.8
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -v
|
||||||
|
- go get -v github.com/bitly/go-simplejson
|
||||||
|
- go get -v github.com/fatih/color
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cp ./config_sample.json ./config.json
|
- cp ./config_sample.json ./config.json
|
||||||
|
163
README.md
163
README.md
@ -5,25 +5,52 @@
|
|||||||
██║ ██║██║ ██║██║ ██║██║╚██╗██║╚════██║
|
██║ ██║██║ ██║██║ ██║██║╚██╗██║╚════██║
|
||||||
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
|
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
|
||||||
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
||||||
|
|
||||||
Latest release: V1.1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[](https://travis-ci.org/TimothyYe/godns)
|
[![Release][7]][8] [![MIT licensed][9]][10] [![Build Status][1]][2] [![Docker][3]][4] [![Go Report Card][11]][12] [![Cover.Run][15]][16] [![GoDoc][13]][14]
|
||||||
|
|
||||||
GoDNS is a dynamic DNS (DDNS) tool, it is based on my early open source project: [DynDNS](https://github.com/TimothyYe/DynDNS).
|
[1]: https://travis-ci.org/TimothyYe/godns.svg?branch=master
|
||||||
|
[2]: https://travis-ci.org/TimothyYe/godns
|
||||||
|
[3]: https://images.microbadger.com/badges/image/timothyye/godns.svg
|
||||||
|
[4]: https://microbadger.com/images/timothyye/godns
|
||||||
|
[7]: http://github-release-version.herokuapp.com/github/timothyye/godns/release.svg?style=flat
|
||||||
|
[8]: https://github.com/TimothyYe/godns/releases
|
||||||
|
[9]: https://img.shields.io/badge/license-Apache-blue.svg
|
||||||
|
[10]: LICENSE
|
||||||
|
[11]: https://goreportcard.com/badge/github.com/timothyye/godns
|
||||||
|
[12]: https://goreportcard.com/report/github.com/timothyye/godns
|
||||||
|
[13]: https://godoc.org/github.com/TimothyYe/godns?status.svg
|
||||||
|
[14]: https://godoc.org/github.com/TimothyYe/godns
|
||||||
|
[15]: https://img.shields.io/badge/cover.run-88.2%25-green.svg
|
||||||
|
[16]: https://cover.run/go/github.com/timothyye/godns
|
||||||
|
|
||||||
|
GoDNS is a dynamic DNS (DDNS) client tool, it is based on my early open source project: [DynDNS](https://github.com/TimothyYe/DynDNS).
|
||||||
|
|
||||||
Now I rewrite [DynDNS](https://github.com/TimothyYe/DynDNS) by Golang and call it [GoDNS](https://github.com/TimothyYe/godns).
|
Now I rewrite [DynDNS](https://github.com/TimothyYe/DynDNS) by Golang and call it [GoDNS](https://github.com/TimothyYe/godns).
|
||||||
|
|
||||||
|
## Supported DNS Provider
|
||||||
|
* DNSPod ([https://www.dnspod.cn/](https://www.dnspod.cn/))
|
||||||
|
* HE.net (Hurricane Electric) ([https://dns.he.net/](https://dns.he.net/))
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
* Linux
|
||||||
|
* MacOS
|
||||||
|
* ARM Linux (Raspberry Pi, etc...)
|
||||||
|
* Windows
|
||||||
|
|
||||||
|
## MIPS32 platform
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Pre-condition
|
## Pre-condition
|
||||||
|
|
||||||
* GoDNS relies on [DNSPod](http://dnspod.cn) and its API.
|
* Register and own a domain.
|
||||||
|
|
||||||
* To use GoDNS, you need a domain hosted on [DNSPod](http://dnspod.cn).
|
* Domain's nameserver points to [DNSPod](https://www.dnspod.cn/) or [HE.net](https://dns.he.net/).
|
||||||
|
|
||||||
## Build it
|
## Get it
|
||||||
|
|
||||||
### Get & build it from source code
|
### Build it from source code
|
||||||
|
|
||||||
* Get source code from Github:
|
* Get source code from Github:
|
||||||
|
|
||||||
@ -33,11 +60,15 @@ git clone https://github.com/timothyye/godns.git
|
|||||||
* Go into the godns directory, get related library and then build it:
|
* Go into the godns directory, get related library and then build it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd godns
|
cd cmd/godns
|
||||||
go get
|
go get -v
|
||||||
go build
|
go build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Download from releases
|
||||||
|
|
||||||
|
Download compiled binaries from [releases](https://github.com/TimothyYe/godns/releases)
|
||||||
|
|
||||||
## Get help
|
## Get help
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -53,11 +84,100 @@ Usage of ./godns:
|
|||||||
|
|
||||||
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
|
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
|
||||||
* Rename it to **config.json**.
|
* Rename it to **config.json**.
|
||||||
* Configure your domain/sub-domain info, username and password of DNSPod account.
|
* 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.
|
* Configure the SMTP options if you want, a mail notification will sent to your mailbox once the IP is changed.
|
||||||
* Configure user id, group id for safety.
|
|
||||||
* Save it in the same directory of GoDNS, or use -c=your_conf_path command.
|
* Save it in the same directory of GoDNS, or use -c=your_conf_path command.
|
||||||
|
|
||||||
|
### Config example for DNSPod
|
||||||
|
|
||||||
|
For DNSPod, you need to provide email & password, and config all the domains & subdomains.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"provider": "DNSPod",
|
||||||
|
"email": "example@gmail.com",
|
||||||
|
"password": "YourPassword",
|
||||||
|
"login_token": "",
|
||||||
|
"domains": [{
|
||||||
|
"domain_name": "example.com",
|
||||||
|
"sub_domains": ["www","test"]
|
||||||
|
},{
|
||||||
|
"domain_name": "example2.com",
|
||||||
|
"sub_domains": ["www","test"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ip_url": "http://members.3322.org/dyndns/getip",
|
||||||
|
"log_path": "./godns.log",
|
||||||
|
"socks5_proxy": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Config example for HE.net
|
||||||
|
|
||||||
|
For HE, email is not needed, just fill DDNS key to password, and config all the domains & subdomains.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"provider": "HE",
|
||||||
|
"email": "",
|
||||||
|
"password": "YourPassword",
|
||||||
|
"login_token": "",
|
||||||
|
"domains": [{
|
||||||
|
"domain_name": "example.com",
|
||||||
|
"sub_domains": ["www","test"]
|
||||||
|
},{
|
||||||
|
"domain_name": "example2.com",
|
||||||
|
"sub_domains": ["www","test"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ip_url": "http://members.3322.org/dyndns/getip",
|
||||||
|
"log_path":"/users/timothy/workspace/src/godns/godns.log",
|
||||||
|
"socks5_proxy": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HE.net DDNS configuration
|
||||||
|
|
||||||
|
Add a new "A record", make sure that "Enable entry for dynamic dns" is checked:
|
||||||
|
|
||||||
|
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/he1.png?raw=true" width="640" />
|
||||||
|
|
||||||
|
Fill your own DDNS key or generate a random DDNS key for this new created "A record":
|
||||||
|
|
||||||
|
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/he2.png?raw=true" width="640" />
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Email notification support
|
||||||
|
|
||||||
|
Update config file and provide your SMTP options, a notification mail will be sent to your mailbox once the IP is changed and updated.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"notify": {
|
||||||
|
"enabled": true,
|
||||||
|
"smtp_server": "smtp.example.com",
|
||||||
|
"smtp_username": "user",
|
||||||
|
"smtp_password": "password",
|
||||||
|
"smtp_port": 25,
|
||||||
|
"send_to": "my_mail@example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notification mail example:
|
||||||
|
|
||||||
|
<img src="https://github.com/TimothyYe/godns/blob/master/snapshots/mail.png?raw=true" />
|
||||||
|
|
||||||
|
### SOCKS5 proxy support
|
||||||
|
|
||||||
|
You can also use SOCKS5 proxy, just fill SOCKS5 address to the ```socks5_proxy``` item:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"socks5_proxy": "127.0.0.1:7070"
|
||||||
|
```
|
||||||
|
|
||||||
|
Now all the queries will go through the specified SOCKS5 proxy.
|
||||||
|
|
||||||
## Run it as a daemon manually
|
## Run it as a daemon manually
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -85,24 +205,17 @@ sudo systemctl enable godns
|
|||||||
sudo systemctl start godns
|
sudo systemctl start godns
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run it in docker
|
## Run it with docker
|
||||||
|
|
||||||
Now godns supports to run in docker.
|
Now godns supports to run in docker.
|
||||||
|
|
||||||
* Pull godns image from docker hub:
|
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
|
||||||
```bash
|
* Rename it to **config.json**.
|
||||||
docker pull timothyye/godns:1.0
|
* Run GoDNS with docker:
|
||||||
```
|
|
||||||
|
|
||||||
* Run godns in container and pass config parameters to it via enviroment variables:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name godns --restart=always \
|
docker run -d --name godns --restart=always \
|
||||||
-e EMAIL=your_dnspod_account \
|
-v /path/to/config.json:/usr/local/godns/config.json timothyye/godns:latest
|
||||||
-e PASSWORD=your_dnspod_password \
|
|
||||||
-e DOMAINS="your_domain1,your_domain2" DOCKER_IMAGE_ID
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Enjoy it!
|
## Enjoy it!
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM alpine:latest
|
FROM timothyye/alpine:3.6-glibc
|
||||||
MAINTAINER Timothy
|
MAINTAINER Timothy
|
||||||
RUN apk add --update ca-certificates
|
RUN apk add --update ca-certificates
|
||||||
RUN mkdir -p /usr/local/godns
|
RUN mkdir -p /usr/local/godns
|
||||||
@ -6,4 +6,4 @@ COPY godns /usr/local/godns
|
|||||||
RUN chmod +x /usr/local/godns/godns
|
RUN chmod +x /usr/local/godns/godns
|
||||||
RUN rm -rf /var/cache/apk/*
|
RUN rm -rf /var/cache/apk/*
|
||||||
WORKDIR /usr/local/godns
|
WORKDIR /usr/local/godns
|
||||||
ENTRYPOINT ["./godns", "-d"]
|
ENTRYPOINT ["./godns", "-c", "/usr/local/godns/config.json"]
|
34
cmd/godns/Makefile
Normal file
34
cmd/godns/Makefile
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Binary name
|
||||||
|
BINARY=godns
|
||||||
|
# Builds the project
|
||||||
|
build:
|
||||||
|
go build -o ${BINARY} -ldflags "-X main.Version=${VERSION}"
|
||||||
|
# Installs our project: copies binaries
|
||||||
|
install:
|
||||||
|
go install
|
||||||
|
release:
|
||||||
|
# Clean
|
||||||
|
go clean
|
||||||
|
rm -rf *.gz
|
||||||
|
# Build for mac
|
||||||
|
go build -o ${BINARY} -ldflags "-X main.Version=${VERSION}"
|
||||||
|
tar czvf ${BINARY}-mac64-${VERSION}.tar.gz ./${BINARY}
|
||||||
|
# Build for linux
|
||||||
|
go clean
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} -ldflags "-X main.Version=${VERSION}"
|
||||||
|
tar czvf ${BINARY}-linux64-${VERSION}.tar.gz ./${BINARY}
|
||||||
|
# Build for arm
|
||||||
|
go clean
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o ${BINARY} -ldflags "-X main.Version=${VERSION}"
|
||||||
|
tar czvf ${BINARY}-arm64-${VERSION}.tar.gz ./${BINARY}
|
||||||
|
# Build for win
|
||||||
|
go clean
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ${BINARY}.exe -ldflags "-X main.Version=${VERSION}"
|
||||||
|
tar czvf ${BINARY}-win64-${VERSION}.tar.gz ./${BINARY}.exe
|
||||||
|
go clean
|
||||||
|
# Cleans our projects: deletes binaries
|
||||||
|
clean:
|
||||||
|
go clean
|
||||||
|
rm -rf *.gz
|
||||||
|
|
||||||
|
.PHONY: clean build
|
70
cmd/godns/godns.go
Normal file
70
cmd/godns/godns.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/TimothyYe/godns"
|
||||||
|
"github.com/TimothyYe/godns/handler"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configuration godns.Settings
|
||||||
|
optConf = flag.String("c", "./config.json", "Specify a config file")
|
||||||
|
optHelp = flag.Bool("h", false, "Show help")
|
||||||
|
|
||||||
|
// Version is current version of GoDNS
|
||||||
|
Version = "0.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if *optHelp {
|
||||||
|
color.Cyan(godns.Logo, Version)
|
||||||
|
flag.Usage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load settings from configurations file
|
||||||
|
if err := godns.LoadSettings(*optConf, &configuration); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := godns.CheckSettings(&configuration); err != nil {
|
||||||
|
fmt.Println("Settings is invalid! ", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init log settings
|
||||||
|
log.SetPrefix("【GoDNS】")
|
||||||
|
log.Println("GoDNS started, entering main loop...")
|
||||||
|
dnsLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsLoop() {
|
||||||
|
panicChan := make(chan godns.Domain)
|
||||||
|
|
||||||
|
log.Println("Creating DNS handler with provider:", configuration.Provider)
|
||||||
|
handler := handler.CreateHandler(configuration.Provider)
|
||||||
|
handler.SetConfiguration(&configuration)
|
||||||
|
for i, _ := range configuration.Domains {
|
||||||
|
go handler.DomainLoop(&configuration.Domains[i], panicChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
panicCount := 0
|
||||||
|
for {
|
||||||
|
failDomain := <-panicChan
|
||||||
|
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
|
||||||
|
go handler.DomainLoop(&failDomain, panicChan)
|
||||||
|
|
||||||
|
panicCount++
|
||||||
|
if panicCount >= godns.PanicMax {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,32 @@
|
|||||||
{
|
{
|
||||||
|
"provider": "DNSPod",
|
||||||
"email": "example@gmail.com",
|
"email": "example@gmail.com",
|
||||||
"password": "",
|
"password": "",
|
||||||
"login_token": "",
|
"login_token": "",
|
||||||
"domains": [{
|
"domains": [
|
||||||
"domain_name":"example.com",
|
{
|
||||||
"sub_domains":["www","test"]
|
"domain_name": "example.com",
|
||||||
},{
|
"sub_domains": [
|
||||||
"domain_name":"example2.com",
|
"www",
|
||||||
"sub_domains":["www","test"]
|
"test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain_name": "example2.com",
|
||||||
|
"sub_domains": [
|
||||||
|
"www",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ip_url": "http://members.3322.org/dyndns/getip",
|
"ip_url": "http://members.3322.org/dyndns/getip",
|
||||||
"log_path":"./godns.log",
|
"socks5_proxy": "",
|
||||||
"log_size":16,
|
"notify": {
|
||||||
"log_num":3
|
"enabled": false,
|
||||||
}
|
"smtp_server": "",
|
||||||
|
"smtp_username": "",
|
||||||
|
"smtp_password": "",
|
||||||
|
"smtp_port": 25,
|
||||||
|
"send_to": ""
|
||||||
|
}
|
||||||
|
}
|
213
dns_handler.go
213
dns_handler.go
@ -1,213 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"golang.org/x/net/proxy"
|
|
||||||
"github.com/bitly/go-simplejson"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getCurrentIP(url string) (string, error) {
|
|
||||||
response, err := http.Get(url)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Cannot get IP...")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(response.Body)
|
|
||||||
return string(body), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateHeader(content url.Values) url.Values {
|
|
||||||
header := url.Values{}
|
|
||||||
if configuration.LoginToken != "" {
|
|
||||||
header.Add("login_token", configuration.LoginToken)
|
|
||||||
} else {
|
|
||||||
header.Add("login_email", configuration.Email)
|
|
||||||
header.Add("login_password", configuration.Password)
|
|
||||||
}
|
|
||||||
header.Add("format", "json")
|
|
||||||
header.Add("lang", "en")
|
|
||||||
header.Add("error_on_empty", "no")
|
|
||||||
|
|
||||||
if content != nil {
|
|
||||||
for k, _ := range content {
|
|
||||||
header.Add(k, content.Get(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return header
|
|
||||||
}
|
|
||||||
|
|
||||||
func apiVersion() {
|
|
||||||
postData("/Info.Version", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDomain(name string) int64 {
|
|
||||||
|
|
||||||
var ret int64
|
|
||||||
values := url.Values{}
|
|
||||||
values.Add("type", "all")
|
|
||||||
values.Add("offset", "0")
|
|
||||||
values.Add("length", "20")
|
|
||||||
|
|
||||||
response, err := postData("/Domain.List", values)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to get domain list...")
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
sjson, parseErr := simplejson.NewJson([]byte(response))
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
log.Println(parseErr)
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if sjson.Get("status").Get("code").MustString() == "1" {
|
|
||||||
domains, _ := sjson.Get("domains").Array()
|
|
||||||
|
|
||||||
for _, d := range domains {
|
|
||||||
m := d.(map[string]interface{})
|
|
||||||
if m["name"] == name {
|
|
||||||
id := m["id"]
|
|
||||||
|
|
||||||
switch t := id.(type) {
|
|
||||||
case json.Number:
|
|
||||||
ret, _ = t.Int64()
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(domains) == 0 {
|
|
||||||
log.Println("domains slice is empty.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Println("get_domain:status code:", sjson.Get("status").Get("code").MustString())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSubDomain(domainID int64, name string) (string, string) {
|
|
||||||
log.Println("debug:", domainID, name)
|
|
||||||
var ret, ip string
|
|
||||||
value := url.Values{}
|
|
||||||
value.Add("domain_id", strconv.FormatInt(domainID, 10))
|
|
||||||
value.Add("offset", "0")
|
|
||||||
value.Add("length", "1")
|
|
||||||
value.Add("sub_domain", name)
|
|
||||||
|
|
||||||
response, err := postData("/Record.List", value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to get domain list")
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
sjson, parseErr := simplejson.NewJson([]byte(response))
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
log.Println(parseErr)
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if sjson.Get("status").Get("code").MustString() == "1" {
|
|
||||||
records, _ := sjson.Get("records").Array()
|
|
||||||
|
|
||||||
for _, d := range records {
|
|
||||||
m := d.(map[string]interface{})
|
|
||||||
if m["name"] == name {
|
|
||||||
ret = m["id"].(string)
|
|
||||||
ip = m["value"].(string)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(records) == 0 {
|
|
||||||
log.Println("records slice is empty.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Println("get_subdomain:status code:", sjson.Get("status").Get("code").MustString())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, ip
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateIP(domainID int64, subDomainID string, subDomainName string, ip string) {
|
|
||||||
value := url.Values{}
|
|
||||||
value.Add("domain_id", strconv.FormatInt(domainID, 10))
|
|
||||||
value.Add("record_id", subDomainID)
|
|
||||||
value.Add("sub_domain", subDomainName)
|
|
||||||
value.Add("record_type", "A")
|
|
||||||
value.Add("record_line", "默认")
|
|
||||||
value.Add("value", ip)
|
|
||||||
|
|
||||||
response, err := postData("/Record.Modify", value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to update record to new IP!")
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sjson, parseErr := simplejson.NewJson([]byte(response))
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
log.Println(parseErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if sjson.Get("status").Get("code").MustString() == "1" {
|
|
||||||
log.Println("New IP updated!")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func postData(url string, content url.Values) (string, error) {
|
|
||||||
client := &http.Client{}
|
|
||||||
|
|
||||||
if configuration.Socks5Proxy != "" {
|
|
||||||
|
|
||||||
log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
|
|
||||||
|
|
||||||
dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("can't connect to the proxy:", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
httpTransport := &http.Transport{}
|
|
||||||
client.Transport = httpTransport
|
|
||||||
httpTransport.Dial = dialer.Dial
|
|
||||||
}
|
|
||||||
|
|
||||||
values := generateHeader(content)
|
|
||||||
req, _ := http.NewRequest("POST", "https://dnsapi.cn" + url, strings.NewReader(values.Encode()))
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
req.Header.Set("User-Agent", fmt.Sprintf("GoDNS/0.1 (%s)", configuration.Email))
|
|
||||||
|
|
||||||
response, err := client.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Post failed...")
|
|
||||||
log.Println(err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
resp, _ := ioutil.ReadAll(response.Body)
|
|
||||||
|
|
||||||
return string(resp), nil
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testGetCurrentIP(t *testing.T) {
|
|
||||||
ip, _ := getCurrentIP("http://members.3322.org/dyndns/getip")
|
|
||||||
|
|
||||||
if ip == "" {
|
|
||||||
t.Log("IP is empty...")
|
|
||||||
} else {
|
|
||||||
t.Log("IP is:" + ip)
|
|
||||||
}
|
|
||||||
}
|
|
134
godns.go
134
godns.go
@ -1,134 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PANIC_MAX = 5
|
|
||||||
INTERVAL = 5 //Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
configuration Settings
|
|
||||||
optConf = flag.String("c", "./config.json", "Specify a config file")
|
|
||||||
optDocker = flag.Bool("d", false, "Run it as docker mode")
|
|
||||||
optHelp = flag.Bool("h", false, "Show help")
|
|
||||||
panicCount = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
if *optHelp {
|
|
||||||
flag.Usage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if *optDocker {
|
|
||||||
//Load settings from ENV
|
|
||||||
configuration = Settings{
|
|
||||||
Email: os.Getenv("EMAIL"),
|
|
||||||
Password: os.Getenv("PASSWORD"),
|
|
||||||
LoginToken: os.Getenv("TOKEN"),
|
|
||||||
IPUrl: "http://members.3322.org/dyndns/getip",
|
|
||||||
LogPath: "./godns.log",
|
|
||||||
LogSize: 16,
|
|
||||||
LogNum: 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := LoadDomains(os.Getenv("DOMAINS"), &configuration.Domains); err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
log.Println(err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//Load settings from configurations file
|
|
||||||
if err := LoadSettings(*optConf, &configuration); err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
log.Println(err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkSettings(&configuration); err != nil {
|
|
||||||
log.Println("Settings is invalid! ", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := InitLogger(configuration.LogPath, configuration.LogSize, configuration.LogNum); err != nil {
|
|
||||||
log.Println("InitLogger error:", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func dnsLoop() {
|
|
||||||
|
|
||||||
for _, domain := range configuration.Domains {
|
|
||||||
go DomainLoop(&domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DomainLoop(domain *Domain) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
panicCount++
|
|
||||||
log.Printf("Recovered in %v: %v\n", err, debug.Stack())
|
|
||||||
fmt.Println(identifyPanic())
|
|
||||||
log.Print(identifyPanic())
|
|
||||||
if panicCount < PANIC_MAX {
|
|
||||||
log.Println("Got panic in goroutine, will start a new one... :", panicCount)
|
|
||||||
go DomainLoop(domain)
|
|
||||||
} else {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
|
|
||||||
domainID := getDomain(domain.DomainName)
|
|
||||||
|
|
||||||
if domainID == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentIP, err := getCurrentIP(configuration.IPUrl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("get_currentIP:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Println("currentIp is:", currentIP)
|
|
||||||
|
|
||||||
for _, subDomain := range domain.SubDomains {
|
|
||||||
|
|
||||||
subDomainID, ip := getSubDomain(domainID, subDomain)
|
|
||||||
|
|
||||||
if subDomainID == "" || ip == "" {
|
|
||||||
log.Printf("domain: %s.%s subDomainID: %s ip: %s\n", subDomain, domain.DomainName, subDomainID, ip)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//Continue to check the IP of sub-domain
|
|
||||||
if len(ip) > 0 && !strings.Contains(currentIP, ip) {
|
|
||||||
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
|
||||||
updateIP(domainID, subDomainID, subDomain, currentIP)
|
|
||||||
} else {
|
|
||||||
log.Printf("%s.%s Current IP is same as domain IP, no need to update...\n", subDomain, domain.DomainName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Interval is 5 minutes
|
|
||||||
time.Sleep(time.Minute * INTERVAL)
|
|
||||||
}
|
|
||||||
}
|
|
270
handler/dnspod_handler.go
Normal file
270
handler/dnspod_handler.go
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TimothyYe/godns"
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSPodHandler struct definition
|
||||||
|
type DNSPodHandler struct {
|
||||||
|
Configuration *godns.Settings
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfiguration pass dns settings and store it to handler instance
|
||||||
|
func (handler *DNSPodHandler) SetConfiguration(conf *godns.Settings) {
|
||||||
|
handler.Configuration = conf
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainLoop the main logic loop
|
||||||
|
func (handler *DNSPodHandler) 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
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
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 {
|
||||||
|
|
||||||
|
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.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 mail notification if notify is enabled
|
||||||
|
if handler.Configuration.Notify.Enabled {
|
||||||
|
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
|
||||||
|
godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), 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
|
||||||
|
log.Printf("Going to sleep, will start next checking in %d minutes...\r\n", godns.INTERVAL)
|
||||||
|
time.Sleep(time.Minute * godns.INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateHeader generates the request header for DNSPod API
|
||||||
|
func (handler *DNSPodHandler) GenerateHeader(content url.Values) url.Values {
|
||||||
|
header := url.Values{}
|
||||||
|
if handler.Configuration.LoginToken != "" {
|
||||||
|
header.Add("login_token", handler.Configuration.LoginToken)
|
||||||
|
} else {
|
||||||
|
header.Add("login_email", handler.Configuration.Email)
|
||||||
|
header.Add("login_password", handler.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomain returns specific domain by name
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubDomain returns subdomain by domain id
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateIP update subdomain with current 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!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostData post data and invoke DNSPod API
|
||||||
|
func (handler *DNSPodHandler) PostData(url string, content url.Values) (string, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
if handler.Configuration.Socks5Proxy != "" {
|
||||||
|
|
||||||
|
log.Println("use socks5 proxy:" + handler.Configuration.Socks5Proxy)
|
||||||
|
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", handler.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)", handler.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
|
||||||
|
}
|
23
handler/handler.go
Normal file
23
handler/handler.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import "github.com/TimothyYe/godns"
|
||||||
|
|
||||||
|
// 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.DNSPOD:
|
||||||
|
handler = IHandler(&DNSPodHandler{})
|
||||||
|
case godns.HE:
|
||||||
|
handler = IHandler(&HEHandler{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
104
handler/he_handler.go
Normal file
104
handler/he_handler.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TimothyYe/godns"
|
||||||
|
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// HEUrl the API address for he.net
|
||||||
|
HEUrl = "https://dyn.dns.he.net/nic/update"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HEHandler struct
|
||||||
|
type HEHandler struct {
|
||||||
|
Configuration *godns.Settings
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfiguration pass dns settings and store it to handler instance
|
||||||
|
func (handler *HEHandler) SetConfiguration(conf *godns.Settings) {
|
||||||
|
handler.Configuration = conf
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainLoop the main logic loop
|
||||||
|
func (handler *HEHandler) 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
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
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 {
|
||||||
|
log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName)
|
||||||
|
handler.UpdateIP(domain.DomainName, subDomain, currentIP)
|
||||||
|
|
||||||
|
// Send mail notification if notify is enabled
|
||||||
|
if handler.Configuration.Notify.Enabled {
|
||||||
|
log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
|
||||||
|
godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interval is 5 minutes
|
||||||
|
log.Printf("Going to sleep, will start next checking in %d minutes...\r\n", godns.INTERVAL)
|
||||||
|
time.Sleep(time.Minute * godns.INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateIP update subdomain with current IP
|
||||||
|
func (handler *HEHandler) 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 := &http.Client{}
|
||||||
|
|
||||||
|
if handler.Configuration.Socks5Proxy != "" {
|
||||||
|
log.Println("use socks5 proxy:" + handler.Configuration.Socks5Proxy)
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", handler.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
292
logger.go
292
logger.go
@ -1,292 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime/debug"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
L_INFO int = iota
|
|
||||||
L_WARNING
|
|
||||||
L_DEBUG
|
|
||||||
PRE_INFO = "[ INFO]"
|
|
||||||
PRE_WARNING = "[WARNING]"
|
|
||||||
PRE_DEBUG = "[ DEBUG]"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (this *Logger) Write(p []byte) (n int, err error) {
|
|
||||||
if this.DEV_MODE {
|
|
||||||
n, err = os.Stdout.Write(p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n, err = this.fd.Write(p)
|
|
||||||
if err == nil {
|
|
||||||
fi, e := this.fd.Stat()
|
|
||||||
if e != nil {
|
|
||||||
err = e
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if fi.Size() > int64(this.size) {
|
|
||||||
this.muSplit.Lock()
|
|
||||||
defer this.muSplit.Unlock()
|
|
||||||
|
|
||||||
fname := fi.Name()
|
|
||||||
strings.HasSuffix(fname, ".log")
|
|
||||||
fbase := fname[:len(fname)-3]
|
|
||||||
|
|
||||||
oldBs := make([]byte, 0, this.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) > this.size {
|
|
||||||
newBs = append(newBs, line...)
|
|
||||||
} else {
|
|
||||||
oldBs = append(oldBs, line...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
|
|
||||||
_, err = this.saveLog(1, fbase, oldBs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = this.fd.Close()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = os.Remove(fname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.fd, err = os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModeAppend|0666)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = this.fd.Write(newBs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) saveLog(index int, fbase string, data []byte) (n int, err error) {
|
|
||||||
fn := fbase + strconv.Itoa(index) + ".log"
|
|
||||||
_, err = os.Stat(fn)
|
|
||||||
if index < this.num && err == nil {
|
|
||||||
var b []byte
|
|
||||||
b, err = ioutil.ReadFile(fn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n, err = this.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 (this *Logger) Flush() {
|
|
||||||
if this.buf.Len() > 0 {
|
|
||||||
this.mu.Lock()
|
|
||||||
defer this.mu.Unlock()
|
|
||||||
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.Print(this.buf)
|
|
||||||
log.SetFlags(log.LstdFlags)
|
|
||||||
this.buf.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//clean prefix and check buf size
|
|
||||||
func (this *Logger) clean() {
|
|
||||||
this.log.SetPrefix("")
|
|
||||||
if this.buf.Len()/1024 > this.flushSize {
|
|
||||||
go this.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) setPrefix(lv int) bool {
|
|
||||||
if lv > this.level {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch lv {
|
|
||||||
case L_INFO:
|
|
||||||
this.log.SetPrefix(PRE_INFO)
|
|
||||||
case L_WARNING:
|
|
||||||
this.log.SetPrefix(PRE_WARNING)
|
|
||||||
case L_DEBUG:
|
|
||||||
this.log.SetPrefix(PRE_DEBUG)
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) logPrint(lv int, args ...interface{}) {
|
|
||||||
this.mu.Lock()
|
|
||||||
defer this.mu.Unlock()
|
|
||||||
|
|
||||||
if !this.setPrefix(lv) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.log.Print(args...)
|
|
||||||
this.clean()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) logPrintln(lv int, args ...interface{}) {
|
|
||||||
this.mu.Lock()
|
|
||||||
defer this.mu.Unlock()
|
|
||||||
|
|
||||||
if !this.setPrefix(lv) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.log.Println(args...)
|
|
||||||
this.clean()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) logPrintf(lv int, format string, args ...interface{}) {
|
|
||||||
this.mu.Lock()
|
|
||||||
defer this.mu.Unlock()
|
|
||||||
|
|
||||||
if !this.setPrefix(lv) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.log.Printf(format, args...)
|
|
||||||
this.clean()
|
|
||||||
}
|
|
||||||
|
|
||||||
//close fd
|
|
||||||
func (this *Logger) Close() {
|
|
||||||
if this.fd != nil {
|
|
||||||
this.Flush()
|
|
||||||
this.fd.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Info(args ...interface{}) {
|
|
||||||
this.logPrint(L_INFO, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Infoln(args ...interface{}) {
|
|
||||||
this.logPrintln(L_INFO, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Infof(format string, args ...interface{}) {
|
|
||||||
this.logPrintf(L_INFO, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Warning(args ...interface{}) {
|
|
||||||
this.logPrint(L_WARNING, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Warningln(args ...interface{}) {
|
|
||||||
this.logPrintln(L_WARNING, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Warningf(format string, args ...interface{}) {
|
|
||||||
this.logPrintf(L_WARNING, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Debug(args ...interface{}) {
|
|
||||||
this.logPrint(L_DEBUG, args...)
|
|
||||||
this.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Debugln(args ...interface{}) {
|
|
||||||
this.logPrintln(L_DEBUG, args...)
|
|
||||||
this.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Logger) Debugf(format string, args ...interface{}) {
|
|
||||||
this.logPrintf(L_DEBUG, format, args...)
|
|
||||||
this.Flush()
|
|
||||||
}
|
|
72
settings.go
72
settings.go
@ -1,33 +1,43 @@
|
|||||||
package main
|
package godns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Domain struct
|
||||||
type Domain struct {
|
type Domain struct {
|
||||||
DomainName string `json:"domain_name"`
|
DomainName string `json:"domain_name"`
|
||||||
SubDomains []string `json:"sub_domains"`
|
SubDomains []string `json:"sub_domains"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//Settings struct
|
// Notify struct for SMTP notification
|
||||||
type Settings struct {
|
type Notify struct {
|
||||||
Email string `json:"email"`
|
Enabled bool `json:"enabled"`
|
||||||
Password string `json:"password"`
|
SMTPServer string `json:"smtp_server"`
|
||||||
LoginToken string `json:"login_token"`
|
SMTPUsername string `json:"smtp_username"`
|
||||||
Domains []Domain `json:"domains"`
|
SMTPPassword string `json:"smtp_password"`
|
||||||
IPUrl string `json:"ip_url"`
|
SMTPPort int `json:"smtp_port"`
|
||||||
LogPath string `json:"log_path"`
|
SendTo string `json:"send_to"`
|
||||||
LogSize int `json:"log_size"`
|
|
||||||
LogNum int `json:"log_num"`
|
|
||||||
Socks5Proxy string `json:"socks5_proxy"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//LoadSettings -- Load settings from config file
|
// Settings struct
|
||||||
|
type Settings struct {
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
LoginToken string `json:"login_token"`
|
||||||
|
Domains []Domain `json:"domains"`
|
||||||
|
IPUrl string `json:"ip_url"`
|
||||||
|
LogPath string `json:"log_path"`
|
||||||
|
Socks5Proxy string `json:"socks5_proxy"`
|
||||||
|
Notify Notify `json:"notify"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadSettings -- Load settings from config file
|
||||||
func LoadSettings(configPath string, settings *Settings) error {
|
func LoadSettings(configPath string, settings *Settings) error {
|
||||||
//LoadSettings from config file
|
// LoadSettings from config file
|
||||||
file, err := ioutil.ReadFile(configPath)
|
file, err := ioutil.ReadFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error occurs while reading config file, please make sure config file exists!")
|
fmt.Println("Error occurs while reading config file, please make sure config file exists!")
|
||||||
@ -42,35 +52,3 @@ func LoadSettings(configPath string, settings *Settings) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//LoadDomains -- Load domains from domains string
|
|
||||||
func LoadDomains(domainsOrginStr string, domains *[]Domain) error {
|
|
||||||
|
|
||||||
domainsMap := make(map[string]*Domain)
|
|
||||||
domainsArray := strings.Split(domainsOrginStr, ",")
|
|
||||||
for _, host := range domainsArray {
|
|
||||||
dotCount := strings.Count(host, ".")
|
|
||||||
if dotCount < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
len := len(host)
|
|
||||||
pos := strings.Index(host, ".")
|
|
||||||
subDomain := host[0:pos]
|
|
||||||
domainName := host[pos+1 : len]
|
|
||||||
|
|
||||||
if d, exist := domainsMap[domainName]; exist {
|
|
||||||
d.SubDomains = append(d.SubDomains, subDomain)
|
|
||||||
} else {
|
|
||||||
d := new(Domain)
|
|
||||||
d.DomainName = domainName
|
|
||||||
d.SubDomains = append(d.SubDomains, subDomain)
|
|
||||||
domainsMap[domainName] = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range domainsMap {
|
|
||||||
*domains = append(*domains, *d)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package godns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -13,6 +13,11 @@ 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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
snapshots/he1.png
Normal file
BIN
snapshots/he1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 155 KiB |
BIN
snapshots/he2.png
Normal file
BIN
snapshots/he2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 115 KiB |
BIN
snapshots/mail.png
Normal file
BIN
snapshots/mail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
snapshots/notify.png
Normal file
BIN
snapshots/notify.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
105
template.go
Normal file
105
template.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package godns
|
||||||
|
|
||||||
|
var mailTemplate = `
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div role="section">
|
||||||
|
<div style="background-color: #281557;">
|
||||||
|
<div class="layout one-col" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
|
||||||
|
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
|
||||||
|
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-full-width" style="background-color: #281557;"><td class="layout__edges"> </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 is 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>
|
||||||
|
`
|
145
utils.go
145
utils.go
@ -1,51 +1,130 @@
|
|||||||
package main
|
package godns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"html/template"
|
||||||
"fmt"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"net/http"
|
||||||
"strings"
|
"golang.org/x/net/proxy"
|
||||||
|
"gopkg.in/gomail.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func identifyPanic() string {
|
var (
|
||||||
var name, file string
|
// Logo for GoDNS
|
||||||
var line int
|
Logo = `
|
||||||
var pc [16]uintptr
|
|
||||||
|
|
||||||
n := runtime.Callers(3, pc[:])
|
██████╗ ██████╗ ██████╗ ███╗ ██╗███████╗
|
||||||
for _, pc := range pc[:n] {
|
██╔════╝ ██╔═══██╗██╔══██╗████╗ ██║██╔════╝
|
||||||
fn := runtime.FuncForPC(pc)
|
██║ ███╗██║ ██║██║ ██║██╔██╗ ██║███████╗
|
||||||
if fn == nil {
|
██║ ██║██║ ██║██║ ██║██║╚██╗██║╚════██║
|
||||||
continue
|
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
|
||||||
}
|
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
||||||
file, line = fn.FileLine(pc)
|
|
||||||
name = fn.Name()
|
GoDNS V%s
|
||||||
if !strings.HasPrefix(name, "runtime.") {
|
https://github.com/TimothyYe/godns
|
||||||
break
|
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PanicMax is the max allowed panic times
|
||||||
|
PanicMax = 5
|
||||||
|
// INTERVAL is minute
|
||||||
|
INTERVAL = 5
|
||||||
|
// DNSPOD for dnspod.cn
|
||||||
|
DNSPOD = "DNSPod"
|
||||||
|
// HE for he.net
|
||||||
|
HE = "HE"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetCurrentIP gets public IP from internet
|
||||||
|
func GetCurrentIP(configuration *Settings) (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 {
|
||||||
|
log.Println("can't connect to the proxy:", err)
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpTransport := &http.Transport{}
|
||||||
|
client.Transport = httpTransport
|
||||||
|
httpTransport.Dial = dialer.Dial
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
response, err := client.Get(configuration.IPUrl)
|
||||||
case name != "":
|
|
||||||
return fmt.Sprintf("%v:%v", name, line)
|
if err != nil {
|
||||||
case file != "":
|
log.Println("Cannot get IP...")
|
||||||
return fmt.Sprintf("%v:%v", file, line)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("pc:%x", pc)
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
body, _ := ioutil.ReadAll(response.Body)
|
||||||
|
return string(body), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func usage() {
|
// CheckSettings check the format of settings
|
||||||
log.Println("[command] -c=[config file path]")
|
func CheckSettings(config *Settings) error {
|
||||||
flag.PrintDefaults()
|
if config.Provider == DNSPOD {
|
||||||
}
|
if (config.Email == "" || config.Password == "") && config.LoginToken == "" {
|
||||||
|
return errors.New("email/password or login token cannot be empty")
|
||||||
func checkSettings(config *Settings) error {
|
}
|
||||||
if (config.Email == "" || config.Password == "") && config.LoginToken == "" {
|
} else if config.Provider == HE {
|
||||||
return errors.New("Input email/password or login token cannot be empty!")
|
if config.Password == "" {
|
||||||
|
return errors.New("password cannot be empty")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("please provide supported DNS provider: DNSPod/HE")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendNotify sends mail notify if IP is changed
|
||||||
|
func SendNotify(configuration *Settings, domain, currentIP string) error {
|
||||||
|
m := gomail.NewMessage()
|
||||||
|
|
||||||
|
m.SetHeader("From", configuration.Notify.SMTPUsername)
|
||||||
|
m.SetHeader("To", configuration.Notify.SendTo)
|
||||||
|
m.SetHeader("Subject", "GoDNS Notification")
|
||||||
|
log.Println("currentIP:", currentIP)
|
||||||
|
log.Println("domain:", domain)
|
||||||
|
m.SetBody("text/html", buildTemplate(currentIP, domain))
|
||||||
|
|
||||||
|
d := gomail.NewPlainDialer(configuration.Notify.SMTPServer, configuration.Notify.SMTPPort, configuration.Notify.SMTPUsername, configuration.Notify.SMTPPassword)
|
||||||
|
|
||||||
|
// Send the email config by sendlist .
|
||||||
|
if err := d.DialAndSend(m); err != nil {
|
||||||
|
log.Println("Send email notification with error:", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTemplate(currentIP, domain string) string {
|
||||||
|
t := template.New("notification template")
|
||||||
|
t.Parse(mailTemplate)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
49
utils_test.go
Normal file
49
utils_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package godns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCurrentIP(t *testing.T) {
|
||||||
|
conf := &Settings{IPUrl: "http://members.3322.org/dyndns/getip"}
|
||||||
|
ip, _ := GetCurrentIP(conf)
|
||||||
|
|
||||||
|
if ip == "" {
|
||||||
|
t.Log("IP is empty...")
|
||||||
|
} else {
|
||||||
|
t.Log("IP is:" + ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf = &Settings{Socks5Proxy: "localhost:8899", IPUrl: "http://members.3322.org/dyndns/getip"}
|
||||||
|
ip, err := GetCurrentIP(conf)
|
||||||
|
|
||||||
|
if ip != "" && err == nil {
|
||||||
|
t.Error("should return error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckSettings(t *testing.T) {
|
||||||
|
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