diff --git a/.gitignore b/.gitignore index 1310df3..114cbf7 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 122cdfb..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -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 diff --git a/.travis.yml b/.travis.yml index 04220fe..9775110 100644 --- a/.travis.yml +++ b/.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 diff --git a/README.md b/README.md index f7f4e16..49d5ad0 100644 --- a/README.md +++ b/README.md @@ -5,25 +5,52 @@ ██║ ██║██║ ██║██║ ██║██║╚██╗██║╚════██║ ╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ - -Latest release: V1.1 ``` -[![Build Status](https://travis-ci.org/TimothyYe/godns.svg?branch=master)](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: + + + +Fill your own DDNS key or generate a random DDNS key for this new created "A record": + + + +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: + + + +### 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! diff --git a/Dockerfile b/cmd/godns/Dockerfile similarity index 68% rename from Dockerfile rename to cmd/godns/Dockerfile index 5d5bb94..5fe2664 100644 --- a/Dockerfile +++ b/cmd/godns/Dockerfile @@ -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"] diff --git a/cmd/godns/Makefile b/cmd/godns/Makefile new file mode 100644 index 0000000..274156f --- /dev/null +++ b/cmd/godns/Makefile @@ -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 diff --git a/cmd/godns/godns.go b/cmd/godns/godns.go new file mode 100644 index 0000000..6cc795b --- /dev/null +++ b/cmd/godns/godns.go @@ -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) + } + } +} diff --git a/config_sample.json b/config_sample.json index 3e1f521..7553c94 100644 --- a/config_sample.json +++ b/config_sample.json @@ -1,17 +1,32 @@ { + "provider": "DNSPod", "email": "example@gmail.com", "password": "", "login_token": "", - "domains": [{ - "domain_name":"example.com", - "sub_domains":["www","test"] - },{ - "domain_name":"example2.com", - "sub_domains":["www","test"] + "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", - "log_size":16, - "log_num":3 -} + "socks5_proxy": "", + "notify": { + "enabled": false, + "smtp_server": "", + "smtp_username": "", + "smtp_password": "", + "smtp_port": 25, + "send_to": "" + } +} \ No newline at end of file diff --git a/dns_handler.go b/dns_handler.go deleted file mode 100644 index 5bef4ca..0000000 --- a/dns_handler.go +++ /dev/null @@ -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 -} diff --git a/dns_handler_test.go b/dns_handler_test.go deleted file mode 100644 index 21f3ee1..0000000 --- a/dns_handler_test.go +++ /dev/null @@ -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) - } -} diff --git a/godns.go b/godns.go deleted file mode 100644 index 3af621d..0000000 --- a/godns.go +++ /dev/null @@ -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) - } -} diff --git a/handler/dnspod_handler.go b/handler/dnspod_handler.go new file mode 100644 index 0000000..06a4381 --- /dev/null +++ b/handler/dnspod_handler.go @@ -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 +} diff --git a/handler/handler.go b/handler/handler.go new file mode 100644 index 0000000..1bc202e --- /dev/null +++ b/handler/handler.go @@ -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 +} diff --git a/handler/he_handler.go b/handler/he_handler.go new file mode 100644 index 0000000..c34e02c --- /dev/null +++ b/handler/he_handler.go @@ -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)) + } + } +} diff --git a/logger.go b/logger.go deleted file mode 100644 index a6474c1..0000000 --- a/logger.go +++ /dev/null @@ -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() -} diff --git a/settings.go b/settings.go index 54b4936..c287628 100644 --- a/settings.go +++ b/settings.go @@ -1,33 +1,43 @@ -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"` } -//Settings struct -type Settings struct { - 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 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"` } -//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 { - //LoadSettings from config file + // LoadSettings from config file file, err := ioutil.ReadFile(configPath) if err != nil { fmt.Println("Error occurs while reading config file, please make sure config file exists!") @@ -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 -} diff --git a/settings_test.go b/settings_test.go index 3c9dd54..2f606f4 100644 --- a/settings_test.go +++ b/settings_test.go @@ -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") } } diff --git a/snapshots/he1.png b/snapshots/he1.png new file mode 100644 index 0000000..9dd40f3 Binary files /dev/null and b/snapshots/he1.png differ diff --git a/snapshots/he2.png b/snapshots/he2.png new file mode 100644 index 0000000..0163c96 Binary files /dev/null and b/snapshots/he2.png differ diff --git a/snapshots/mail.png b/snapshots/mail.png new file mode 100644 index 0000000..b3df9b0 Binary files /dev/null and b/snapshots/mail.png differ diff --git a/snapshots/notify.png b/snapshots/notify.png new file mode 100644 index 0000000..aaa5307 Binary files /dev/null and b/snapshots/notify.png differ diff --git a/template.go b/template.go new file mode 100644 index 0000000..9b66792 --- /dev/null +++ b/template.go @@ -0,0 +1,105 @@ +package godns + +var mailTemplate = ` + + +
+
+
+
+ +
+ +
+
 
+
+ +
+ +
+
+
+ +
+
+
+ +
+ +
+
 
+
+ +
+
+

+ + Your IP address is changed to + +

+

+ + + {{ .CurrentIP }} + + +

+

+ + Domain {{ .Domain }} is updated + +

+
+
+ +
+
 
+
+ +
+
 
+
+ +
+ +
+
+
+ +
 
+ + +
+ +
+
 
+ +
+ +` diff --git a/utils.go b/utils.go index 106dd58..b43f95b 100644 --- a/utils.go +++ b/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 } + + httpTransport := &http.Transport{} + client.Transport = httpTransport + httpTransport.Dial = dialer.Dial } - switch { - case name != "": - return fmt.Sprintf("%v:%v", name, line) - case file != "": - return fmt.Sprintf("%v:%v", file, line) + response, err := client.Get(configuration.IPUrl) + + if err != nil { + log.Println("Cannot get IP...") + return "", err } - return fmt.Sprintf("pc:%x", pc) + defer response.Body.Close() + + body, _ := ioutil.ReadAll(response.Body) + return string(body), nil } -func usage() { - log.Println("[command] -c=[config file path]") - flag.PrintDefaults() -} - -func checkSettings(config *Settings) error { - if (config.Email == "" || config.Password == "") && config.LoginToken == "" { - return errors.New("Input email/password or login token cannot be empty!") +// CheckSettings check the format of settings +func CheckSettings(config *Settings) error { + if config.Provider == DNSPOD { + if (config.Email == "" || config.Password == "") && config.LoginToken == "" { + return errors.New("email/password or login token cannot be empty") + } + } else if config.Provider == HE { + if config.Password == "" { + 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() +} diff --git a/utils_test.go b/utils_test.go new file mode 100644 index 0000000..5a77a52 --- /dev/null +++ b/utils_test.go @@ -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") + } +}