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
|
||||
*.log
|
||||
*.swp
|
||||
godns
|
||||
*.gz
|
||||
cmd/godns/godns
|
||||
|
||||
vendor/*
|
||||
/.idea
|
||||
/godns.iml
|
||||
/godns.ipr
|
||||
/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
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.7.x
|
||||
- 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:
|
||||
- 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).
|
||||
|
||||
## 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
|
||||
|
||||
* 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:
|
||||
|
||||
@ -33,11 +60,15 @@ git clone https://github.com/timothyye/godns.git
|
||||
* Go into the godns directory, get related library and then build it:
|
||||
|
||||
```bash
|
||||
cd godns
|
||||
go get
|
||||
cd cmd/godns
|
||||
go get -v
|
||||
go build
|
||||
```
|
||||
|
||||
### Download from releases
|
||||
|
||||
Download compiled binaries from [releases](https://github.com/TimothyYe/godns/releases)
|
||||
|
||||
## Get help
|
||||
|
||||
```bash
|
||||
@ -53,11 +84,100 @@ Usage of ./godns:
|
||||
|
||||
* Get [config_sample.json](https://github.com/timothyye/godns/blob/master/config_sample.json) from Github.
|
||||
* Rename it to **config.json**.
|
||||
* Configure your domain/sub-domain info, username and password of DNSPod account.
|
||||
* Configure log file path, max size of log file, max count of log file.
|
||||
* Configure user id, group id for safety.
|
||||
* Configure your provider, domain/sub-domain info, username and password, etc.
|
||||
* Configure the SMTP options if you want, a mail notification will sent to your mailbox once the IP is changed.
|
||||
* Save it in the same directory of GoDNS, or use -c=your_conf_path command.
|
||||
|
||||
### Config 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
|
||||
|
||||
```bash
|
||||
@ -85,24 +205,17 @@ sudo systemctl enable godns
|
||||
sudo systemctl start godns
|
||||
```
|
||||
|
||||
## Run it in docker
|
||||
## Run it with docker
|
||||
|
||||
Now godns supports to run in docker.
|
||||
|
||||
* Pull godns image from docker hub:
|
||||
```bash
|
||||
docker pull timothyye/godns:1.0
|
||||
```
|
||||
|
||||
* Run godns in container and pass config parameters to it via enviroment variables:
|
||||
* 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
|
||||
docker run -d --name godns --restart=always \
|
||||
-e EMAIL=your_dnspod_account \
|
||||
-e PASSWORD=your_dnspod_password \
|
||||
-e DOMAINS="your_domain1,your_domain2" DOCKER_IMAGE_ID
|
||||
-v /path/to/config.json:/usr/local/godns/config.json timothyye/godns:latest
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Enjoy it!
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM alpine:latest
|
||||
FROM timothyye/alpine:3.6-glibc
|
||||
MAINTAINER Timothy
|
||||
RUN apk add --update ca-certificates
|
||||
RUN mkdir -p /usr/local/godns
|
||||
@ -6,4 +6,4 @@ COPY godns /usr/local/godns
|
||||
RUN chmod +x /usr/local/godns/godns
|
||||
RUN rm -rf /var/cache/apk/*
|
||||
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",
|
||||
"password": "",
|
||||
"login_token": "",
|
||||
"domains": [{
|
||||
"domains": [
|
||||
{
|
||||
"domain_name": "example.com",
|
||||
"sub_domains":["www","test"]
|
||||
},{
|
||||
"sub_domains": [
|
||||
"www",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain_name": "example2.com",
|
||||
"sub_domains":["www","test"]
|
||||
"sub_domains": [
|
||||
"www",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ip_url": "http://members.3322.org/dyndns/getip",
|
||||
"log_path":"./godns.log",
|
||||
"log_size":16,
|
||||
"log_num":3
|
||||
"socks5_proxy": "",
|
||||
"notify": {
|
||||
"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()
|
||||
}
|
50
settings.go
50
settings.go
@ -1,28 +1,38 @@
|
||||
package main
|
||||
package godns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Domain struct
|
||||
type Domain struct {
|
||||
DomainName string `json:"domain_name"`
|
||||
SubDomains []string `json:"sub_domains"`
|
||||
}
|
||||
|
||||
// Notify struct for SMTP notification
|
||||
type Notify 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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
LogSize int `json:"log_size"`
|
||||
LogNum int `json:"log_num"`
|
||||
Socks5Proxy string `json:"socks5_proxy"`
|
||||
Notify Notify `json:"notify"`
|
||||
}
|
||||
|
||||
// LoadSettings -- Load settings from config file
|
||||
@ -42,35 +52,3 @@ func LoadSettings(configPath string, settings *Settings) error {
|
||||
|
||||
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 (
|
||||
"testing"
|
||||
@ -13,6 +13,11 @@ func TestLoadSetting(t *testing.T) {
|
||||
}
|
||||
|
||||
if settings.IPUrl == "" {
|
||||
t.Error("Cannot load ip_url from config file")
|
||||
t.Error("cannot load ip_url from config file")
|
||||
}
|
||||
|
||||
err = LoadSettings("./file/does/not/exists", &settings)
|
||||
if err == nil {
|
||||
t.Error("file doesn't exist, should return error")
|
||||
}
|
||||
}
|
||||
|
BIN
snapshots/he1.png
Normal file
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>
|
||||
`
|
141
utils.go
141
utils.go
@ -1,51 +1,130 @@
|
||||
package main
|
||||
package godns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
"net/http"
|
||||
"golang.org/x/net/proxy"
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
func identifyPanic() string {
|
||||
var name, file string
|
||||
var line int
|
||||
var pc [16]uintptr
|
||||
var (
|
||||
// Logo for GoDNS
|
||||
Logo = `
|
||||
|
||||
n := runtime.Callers(3, pc[:])
|
||||
for _, pc := range pc[:n] {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
continue
|
||||
}
|
||||
file, line = fn.FileLine(pc)
|
||||
name = fn.Name()
|
||||
if !strings.HasPrefix(name, "runtime.") {
|
||||
break
|
||||
}
|
||||
██████╗ ██████╗ ██████╗ ███╗ ██╗███████╗
|
||||
██╔════╝ ██╔═══██╗██╔══██╗████╗ ██║██╔════╝
|
||||
██║ ███╗██║ ██║██║ ██║██╔██╗ ██║███████╗
|
||||
██║ ██║██║ ██║██║ ██║██║╚██╗██║╚════██║
|
||||
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
|
||||
╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
||||
|
||||
GoDNS V%s
|
||||
https://github.com/TimothyYe/godns
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
switch {
|
||||
case name != "":
|
||||
return fmt.Sprintf("%v:%v", name, line)
|
||||
case file != "":
|
||||
return fmt.Sprintf("%v:%v", file, line)
|
||||
httpTransport := &http.Transport{}
|
||||
client.Transport = httpTransport
|
||||
httpTransport.Dial = dialer.Dial
|
||||
}
|
||||
|
||||
return fmt.Sprintf("pc:%x", pc)
|
||||
response, err := client.Get(configuration.IPUrl)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Cannot get IP...")
|
||||
return "", err
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Println("[command] -c=[config file path]")
|
||||
flag.PrintDefaults()
|
||||
defer response.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func checkSettings(config *Settings) error {
|
||||
// CheckSettings check the format of settings
|
||||
func CheckSettings(config *Settings) error {
|
||||
if config.Provider == DNSPOD {
|
||||
if (config.Email == "" || config.Password == "") && config.LoginToken == "" {
|
||||
return errors.New("Input email/password or login token cannot be empty!")
|
||||
return errors.New("email/password or login token cannot be empty")
|
||||
}
|
||||
} else if config.Provider == HE {
|
||||
if config.Password == "" {
|
||||
return errors.New("password cannot be empty")
|
||||
}
|
||||
} else {
|
||||
return errors.New("please provide supported DNS provider: DNSPod/HE")
|
||||
}
|
||||
|
||||
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