diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..061c12b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +_output/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..782fc5e --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +# ============================================================================== +# 定义全局 Makefile 变量方便后面引用 +PROJ_NAME = dnsproxy + +COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +# 项目根目录 +ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/ && pwd -P)) +# 构建产物、临时文件存放目录 +OUTPUT_DIR := $(ROOT_DIR)/_output + +# ============================================================================== +# 定义 Makefile all 伪目标,执行 `make` 时,会默认会执行 all 伪目标 +.PHONY: all +all: add-copyright format build + +# ============================================================================== +# 定义其他需要的伪目标 + +.PHONY: build +build: tidy # 编译源码,依赖 tidy 目标自动添加/移除依赖包. + @go build -v -o $(OUTPUT_DIR)/${PROJ_NAME} $(ROOT_DIR)/dnsproxy.go + +.PHONY: format +format: # 格式化 Go 源码. + @gofmt -s -w ./ + +.PHONY: tidy +tidy: # 自动添加/移除依赖包. + @go mod tidy + +.PHONY: clean +clean: # 清理构建产物、临时文件等. + @-rm -vrf $(OUTPUT_DIR) + +.PHONY: run +run: + sudo $(OUTPUT_DIR)/${PROJ_NAME} diff --git a/README-en.md b/README-en.md new file mode 100644 index 0000000..dae9328 --- /dev/null +++ b/README-en.md @@ -0,0 +1,55 @@ +## Proxy DNS query use TCP in go lang + +苦于本地DNS污染,连github.com这种都经常解析不了。最近愈发频繁,所以写了这个程序。 + +- 采用多个dns地址轮询。 +- dns 请求时,默认 read/write 都为 100ms 超时,实测已经足够,更长时间会导致网页访问变慢。 +- 使用 TCP 做 DNS 解析,转发正常的 UDP 请求。 +- go-cache 做缓存,默认一小时失效,pure go,无需安装其它组件。 + +另有使用 redis 做缓存的版本在 redis-cache 分支 + +Dependencies: + + go get github.com/miekg/dns + go get github.com/pmylund/go-cache + +跨平台编译后放到了我的 arm 开发板 pcDuino 上,现在又可以作为 DNS服务器 了 ^_^ +Build for special platform: + + GOOS=linux GOARCH=arm go build src/dnsproxy.go + +数台电脑,移动设备,平稳运行两天,正常解析。 + +## Using + +Supported arguments, all these could use as commandline flags like `-xxx=xxx`: + + dnss = flag.String("dns", "192.168.2.1:53,8.8.8.8:53,8.8.4.4:53", "dns address, use `,` as sep") + local = flag.String("local", ":53", "local listen address") + debug = flag.Int("debug", 0, "debug level 0 1 2") + cache = flag.Bool("cache", true, "enable go-cache") + expire = flag.Int64("expire", 3600, "default cache expire time") + ipv6 = flag.Bool("6", false, "skip ipv6 record query AAAA") + hostfile = flag.String("hostfile", "_output/host-file.txt, "host file for dns result intercept & substitute") + +`make build` to build the executable file: `dnsproxy` + +> [New Feature] To replace DNS response results using rules defined in the hostfile, you need to manually place the edited hostfile into the _output directory. + +`make run` to run + +Run manually: + + sudo ./dnsproxy + +Set dns address,using `,` as the delimiter. + + sudo ./dnsproxy -dns=x.x.x.x:53,x.x.x.x:53 + +Using `-debug` flag could print the dns query log. + + sudo ./dnsproxy -debug=1 + +### Thanks +Part of source code from [HERE](https://gist.github.com/mrluanma/3722792) diff --git a/README.md b/README.md index f853383..bbc25d6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -##Proxy DNS query use TCP in go lang +## Proxy DNS query use TCP in go lang 苦于本地DNS污染,连github.com这种都经常解析不了。最近愈发频繁,所以写了这个程序。 @@ -20,7 +20,7 @@ 数台电脑,移动设备,平稳运行两天,正常解析。 -##使用方法 +## 使用方法 支持的参数: @@ -30,9 +30,15 @@ cache = flag.Bool("cache", true, "enable go-cache") expire = flag.Int64("expire", 3600, "default cache expire time") ipv6 = flag.Bool("6", false, "skip ipv6 record query AAAA") + hostfile = flag.String("hostfile", "_output/host-file.txt, "host file for dns result intercept & substitute") -build 生成 dnsproxy 文件后 -执行: +`make build` 生成 dnsproxy 文件后 + +> 如要使用hostfile内规则替换DNS响应结果,需手动将编辑好的hostfile文件放入`_output`目录下 + +`make run`运行 + +手动执行: sudo ./dnsproxy @@ -44,5 +50,5 @@ build 生成 dnsproxy 文件后 sudo ./dnsproxy -debug=1 -###Thanks +### Thanks 部分代码源自 [这里](https://gist.github.com/mrluanma/3722792) diff --git a/dnsproxy.go b/dnsproxy.go index 7ae0601..66e4e4d 100644 --- a/dnsproxy.go +++ b/dnsproxy.go @@ -2,11 +2,10 @@ package main import ( "crypto/md5" + "dnsproxy/regex" "encoding/hex" "flag" "fmt" - "github.com/miekg/dns" - "github.com/pmylund/go-cache" "log" "net" "os" @@ -17,17 +16,21 @@ import ( "strings" "syscall" "time" + + "github.com/miekg/dns" + "github.com/pmylund/go-cache" ) var ( - dnss = flag.String("dns", "192.168.2.1:53:udp,8.8.8.8:53:udp,8.8.4.4:53:udp,8.8.8.8:53:tcp,8.8.4.4:53:tcp", "dns address, use `,` as sep") - local = flag.String("local", ":53", "local listen address") - debug = flag.Int("debug", 0, "debug level 0 1 2") - encache = flag.Bool("cache", true, "enable go-cache") - expire = flag.Int64("expire", 3600, "default cache expire seconds, -1 means use doamin ttl time") - file = flag.String("file", filepath.Join(path.Dir(os.Args[0]), "cache.dat"), "cached file") - ipv6 = flag.Bool("6", false, "skip ipv6 record query AAAA") - timeout = flag.Int("timeout", 200, "read/write timeout") + dnss = flag.String("dns", "127.0.0.53:53:udp,127.0.0.53:53:tcp", "dns address, use `,` as sep") + local = flag.String("local", "127.0.0.1:53", "local listen address") + debug = flag.Int("debug", 0, "debug level 0 1 2") + encache = flag.Bool("cache", true, "enable go-cache") + expire = flag.Int64("expire", 3600, "default cache expire seconds, -1 means use doamin ttl time") + file = flag.String("file", filepath.Join(path.Dir(os.Args[0]), "cache.dat"), "cached file") + ipv6 = flag.Bool("6", false, "skip ipv6 record query AAAA") + timeout = flag.Int("timeout", 200, "read/write timeout") + hostfile = flag.String("hostfile", filepath.Join(path.Dir(os.Args[0]), "host-file.txt"), "hosts with wildcard") clientTCP *dns.Client clientUDP *dns.Client @@ -125,6 +128,8 @@ func init() { log.Fatalln("dns address must be not empty") } + regex.Init(*hostfile) + signal.Notify(saveSig, syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT) } @@ -157,7 +162,14 @@ func proxyServe(w dns.ResponseWriter, req *dns.Msg) { query []string questions []dns.Question used string + reqName string ) + if len(req.Question) > 0 { + for _, v := range req.Question { + // log.Printf("%s\t,Recv:%s", v.Name, v.String()) + reqName = v.Name + } + } defer func() { if err := recover(); err != nil { @@ -192,6 +204,13 @@ func proxyServe(w dns.ResponseWriter, req *dns.Msg) { if ENCACHE { if reply, ok := conn.Get(key); ok { + if DEBUG > 1 { + if ok { + log.Printf("Cache find [%s]", questions[0].Name) + } else { + log.Printf("Cache MISS [%s]", questions[0].Name) + } + } data, _ = reply.([]byte) } if data != nil && len(data) > 0 { @@ -234,6 +253,8 @@ func proxyServe(w dns.ResponseWriter, req *dns.Msg) { } } + substituteResponse(reqName, m) + if err == nil { if DEBUG > 0 { if tried { @@ -260,12 +281,17 @@ func proxyServe(w dns.ResponseWriter, req *dns.Msg) { } } conn.Set(key, data, time.Second*time.Duration(ttl)) + // log.Printf("Cache Set [%s]", questions[0].Name) m.Id = id if DEBUG > 0 { log.Printf("id: %5d cache: CACHED %v TTL %v\n", id, query, ttl) } } + } else { + log.Printf("Response write fail [%s]", questions[0].Name) } + } else { + log.Printf("Response pack fail [%s]", questions[0].Name) } } @@ -284,3 +310,32 @@ end: fmt.Println("====================================================") } } + +func substituteResponse(reqName string, m *dns.Msg) error { + if regex.MappingTree == nil || len(reqName) < 1 { + return nil + } + defer func() { + if err := recover(); err != nil { + log.Fatalln(err) + } + }() + // log.Printf("Begin replace [%s]", reqName) + // Check if the last character is a period + reqName = strings.TrimSuffix(reqName, ".") + + ip := regex.MappingTree.FindReverse(reqName) + if ip == "" { + // log.Printf(" --- Replace not Found [%s]", reqName) + return nil + } + log.Printf("Do replace [%s] with IP [%s]", reqName, ip) + newResponse, err := dns.NewRR(reqName + " 3600 IN A " + ip) + if err != nil { + log.Printf(" --- Replace ERROR [%s]", reqName) + return err + } + m.Answer = make([]dns.RR, 1) + m.Answer[0] = newResponse + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c800c29 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module dnsproxy + +go 1.21.4 + +require ( + github.com/miekg/dns v1.1.57 + github.com/pmylund/go-cache v2.1.0+incompatible +) + +require ( + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/tools v0.13.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5610f01 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/pmylund/go-cache v2.1.0+incompatible h1:n+7K51jLz6a3sCvff3BppuCAkixuDHuJ/C57Vw/XjTE= +github.com/pmylund/go-cache v2.1.0+incompatible/go.mod h1:hmz95dGvINpbRZGsqPcd7B5xXY5+EKb5PpGhQY3NTHk= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= diff --git a/host-file.txt b/host-file.txt new file mode 100644 index 0000000..5a5c897 --- /dev/null +++ b/host-file.txt @@ -0,0 +1,114 @@ +1.1.1.1 *akadns.net +1.1.1.1 *akam.net +1.1.1.1 *akamai.com +1.1.1.1 *akamai.net +1.1.1.1 *akamaiedge.net +1.1.1.1 *akamaihd.net +1.1.1.1 *akamaistream.net +1.1.1.1 *akamaitech.net +1.1.1.1 *akamaitechnologies.com +1.1.1.1 *akamaitechnologies.fr +1.1.1.1 *akamaized.net +1.1.1.1 *edgekey.net +1.1.1.1 *edgesuite.net +1.1.1.1 *srip.net +1.1.1.1 *footprint.net +1.1.1.1 *level3.net +1.1.1.1 *llnwd.net +1.1.1.1 *edgecastcdn.net +1.1.1.1 *cloudfront.net +1.1.1.1 *netflix.com +1.1.1.1 *netflix.net +1.1.1.1 *nflximg.com +1.1.1.1 *nflximg.net +1.1.1.1 *nflxvideo.net +1.1.1.1 *nflxso.net +1.1.1.1 *nflxext.com +1.1.1.1 *hulu.com +1.1.1.1 *huluim.com +1.1.1.1 *hbonow.com +1.1.1.1 *hbogo.com +1.1.1.1 *hbo.com +1.1.1.1 *amazon.com +1.1.1.1 *amazon.co.uk +1.1.1.1 *amazonvideo.com +1.1.1.1 *crackle.com +1.1.1.1 *pandora.com +1.1.1.1 *vudu.com +1.1.1.1 *blinkbox.com +1.1.1.1 *abc.com +1.1.1.1 *fox.com +1.1.1.1 *theplatform.com +1.1.1.1 *nbc.com +1.1.1.1 *nbcuni.com +1.1.1.1 *ip2location.com +1.1.1.1 *pbs.org +1.1.1.1 *warnerbros.com +1.1.1.1 *southpark.cc.com +1.1.1.1 *cbs.com +1.1.1.1 *brightcove.com +1.1.1.1 *cwtv.com +1.1.1.1 *spike.com +1.1.1.1 *go.com +1.1.1.1 *mtv.com +1.1.1.1 *mtvnservices.com +1.1.1.1 *playstation.net +1.1.1.1 *uplynk.com +1.1.1.1 *maxmind.com +1.1.1.1 *disney.com +1.1.1.1 *disneyjunior.com +1.1.1.1 *adobedtm.com +1.1.1.1 *bam.nr-data.net +1.1.1.1 *bamgrid.com +1.1.1.1 *braze.com +1.1.1.1 *cdn.optimizely.com +1.1.1.1 *cdn.registerdisney.go.com +1.1.1.1 *cws.conviva.com +1.1.1.1 *d9.flashtalking.com +1.1.1.1 *disney-plus.net +1.1.1.1 *disney-portal.my.onetrust.com +1.1.1.1 *disney.demdex.net +1.1.1.1 *disney.my.sentry.io +1.1.1.1 *disneyplus.bn5x.net +1.1.1.1 *disneyplus.com +1.1.1.1 *disneyplus.com.ssl.sc.omtrdc.net +1.1.1.1 *disneystreaming.com +1.1.1.1 *dssott.com +1.1.1.1 *execute-api.us-east-1.amazonaws.com +1.1.1.1 *js-agent.newrelic.com +1.1.1.1 *xboxlive.com +1.1.1.1 *lovefilm.com +1.1.1.1 *turner.com +1.1.1.1 *amctv.com +1.1.1.1 *sho.com +1.1.1.1 *mog.com +1.1.1.1 *wdtvlive.com +1.1.1.1 *beinsportsconnect.tv +1.1.1.1 *beinsportsconnect.net +1.1.1.1 *fig.bbc.co.uk +1.1.1.1 *open.live.bbc.co.uk +1.1.1.1 *sa.bbc.co.uk +1.1.1.1 *www.bbc.co.uk +1.1.1.1 *crunchyroll.com +1.1.1.1 *ifconfig.co +1.1.1.1 *omtrdc.net +1.1.1.1 *sling.com +1.1.1.1 *movetv.com +1.1.1.1 *happyon.jp +1.1.1.1 *abema.tv +1.1.1.1 *hulu.jp +1.1.1.1 *optus.com.au +1.1.1.1 *optusnet.com.au +1.1.1.1 *gamer.com.tw +1.1.1.1 *bahamut.com.tw +1.1.1.1 *hinet.net +1.1.1.1 *dmm.com +1.1.1.1 *dmm.co.jp +1.1.1.1 *dmm-extension.com +1.1.1.1 *dmmapis.com +1.1.1.1 *api-p.videomarket.jp +1.1.1.1 *saima.zlzd.xyz +1.1.1.1 *challenges.cloudflare.com +1.1.1.1 *ai.com +1.1.1.1 *openai.com +1.1.1.1 *fast.com \ No newline at end of file diff --git a/regex/regex_test.go b/regex/regex_test.go new file mode 100644 index 0000000..d61425e --- /dev/null +++ b/regex/regex_test.go @@ -0,0 +1,106 @@ +package regex_test + +import ( + "dnsproxy/regex" + "fmt" + "regexp" + "testing" +) + +func TestRegex1(t *testing.T) { + pattern := `.*edgekey\.net$` + + // Example domains + domains := map[string]bool{ + "edgekey.net": true, + "s1.edgekey.net": true, + "t1.s1.edgekey.net": true, + "aabbccedgekey.net": true, + "example.com": false, + "aabbccedgekey.net.co": false, + } + + for domain, flag := range domains { + match, _ := regexp.MatchString(pattern, domain) + fmt.Printf("%s [%t]\n", domain, flag) + if match != flag { + t.Errorf("%s should %t, but not", domain, flag) + } + } +} + +func TestReadHostfile(t *testing.T) { + regex.Init("../host-file.txt") +} + +func initData() map[string]string { + hostfile := make(map[string]string) + hostfile["*akadns.net"] = "1.1.1.1" + hostfile["*akam.net"] = "1.1.1.2" + hostfile["*akamai.com"] = "1.1.1.3" + hostfile["*akamai.net"] = "1.1.1.4" + hostfile["*akamaized.net"] = "1.1.1.5" + hostfile["*edgekey.net"] = "1.1.1.6" + hostfile["*edgesuite.net"] = "1.1.1.7" + hostfile["*netflix.net"] = "1.1.1.8" + hostfile["*netflix.com"] = "1.1.1.9" + hostfile["*nflxext.com"] = "1.1.2.1" + hostfile["*nflxso.net"] = "1.1.2.2" + hostfile["*nflxso.n"] = "1.1.2.3" + return hostfile +} + +func TestConstructTree(t *testing.T) { + hostfile := initData() + + tree := regex.ConstructTreeReverse(hostfile) + regex.PrintTree(tree) +} + +func TestFindTree(t *testing.T) { + hostfile := initData() + + tree := regex.ConstructTreeReverse(hostfile) + res := tree.FindReverse("netflix.com") + if res != hostfile["*netflix.com"] { + t.Errorf("not match [%s]", res) + } + + res = tree.FindReverse("nnnanflxso.com") + if res != "" { + t.Errorf("not match [%s]", res) + } +} + +func TestFindTree2(t *testing.T) { + hostfile := make(map[string]string) + hostfile["*akadns.net"] = "1.1.1.1" + hostfile["*docker_container_1"] = "2.2.2.2" + + tree := regex.ConstructTreeReverse(hostfile) + res := tree.FindReverse("docker_container_1") + if res != hostfile["*docker_container_1"] { + t.Errorf("not match [%s]", res) + } + res = tree.FindReverse("r_1") + if res != "" { + t.Errorf("not match [%s]", res) + } +} + +func TestFindTreeWithHostfile(t *testing.T) { + regex.Init("../host-file.txt") + + tree := regex.MappingTree + res := tree.FindReverse("netflix.com") + if res != "1.1.1.1" { + t.Errorf("not match [%s]", res) + } + fmt.Println(res) + + res = tree.FindReverse("nnnanflxso.net") + if res != "1.1.1.1" { + t.Errorf("not match [%s]", res) + } + fmt.Println(res) +} diff --git a/regex/regexmatch.go b/regex/regexmatch.go new file mode 100644 index 0000000..897e2dd --- /dev/null +++ b/regex/regexmatch.go @@ -0,0 +1,60 @@ +package regex + +import ( + "bufio" + "fmt" + "log" + "os" + "strings" +) + +var ( + MappingTree *Tree +) + +func Init(hostfile string) { + // Open the file + file, err := os.Open(hostfile) + if err != nil { + fmt.Println("Error opening file:", err) + return + } + defer file.Close() + + frontMatchMap := make(map[string]string) + + // Read the file line by line + scanner := bufio.NewScanner(file) + var count int16 = 0 + for scanner.Scan() { + line := scanner.Text() + count++ + + // Split the line by whitespace + fields := strings.Fields(line) + if len(fields) == 2 { + ip := fields[0] + domain := fields[1] + if strings.HasPrefix(domain, "*") { + frontMatchMap[domain] = ip + } + } + } + log.Printf("Read hostfile %d lines\n", count) + + // Check for any scanner errors + if err := scanner.Err(); err != nil { + fmt.Println("Error reading hostfile:", err) + return + } + + // Print the domain and IP mapping + /* + for domain, ip := range frontMatchMap { + fmt.Printf("Domain: [%s], IP: [%s]\n", domain, ip) + } + */ + + MappingTree = ConstructTreeReverse(frontMatchMap) + // PrintTree(MappingTree) +} diff --git a/regex/tree.go b/regex/tree.go new file mode 100644 index 0000000..c11e2af --- /dev/null +++ b/regex/tree.go @@ -0,0 +1,147 @@ +package regex + +import "fmt" + +type Tree struct { + Root *TreeNode +} +type TreeNode struct { + K rune // key, [a]kadns.net + B *TreeNode // brother + Pb *TreeNode // previous brother + C *TreeNode // child + F *TreeNode // father + N *Node // value +} + +type Node struct { + V string // dns response ip, 1.1.1.1 +} + +func (tn *TreeNode) createFirstChild(k rune) { + tn.C = &TreeNode{ + K: k, B: nil, Pb: nil, C: nil, F: tn, N: nil, + } +} + +func (tn *TreeNode) appendBrother(k rune) { + if tn.B != nil { + panic("appendBrother not nil") + } + tn.B = &TreeNode{ + K: k, B: nil, Pb: tn, C: nil, F: nil, N: nil, + } +} + +func (tn *TreeNode) findInBrother(k rune) (bool, *TreeNode) { + now := tn + prev := now + for now != nil { + prev = now + if now.K == k { + return true, now + } + now = now.B + } + return false, prev +} + +func (t *Tree) construct(domain, ip string) { + if t.Root == nil { + t.Root = &TreeNode{} + } + prev := t.Root + now := prev.C + + for _, ch := range domain { + // fmt.Printf("%c ", ch) + if now == nil { + prev.createFirstChild(ch) + prev = prev.C + now = prev.C + continue + } + f, k := now.findInBrother(ch) + if f { + prev = k + now = prev.C + continue + } else { + k.appendBrother(ch) + prev = k.B + now = prev.C + } + } + prev.N = &Node{V: ip} + // fmt.Println() +} + +func reverseString(str string) string { + runes := []rune(str) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +func (t *Tree) ConstructReverse(domain, ip string) { + t.construct(reverseString(domain), ip) +} + +func ConstructTreeReverse(m map[string]string) *Tree { + t := &Tree{} + for k, v := range m { + t.ConstructReverse(k, v) + } + return t +} + +func PrintTree(t *Tree) { + now := t.Root + printNextChild("", now.C) +} + +func printNextChild(segment string, now *TreeNode) { + newSeg := segment + string(now.K) + if now.N != nil { + fmt.Printf("%s: %s\n", newSeg, now.N.V) + } + if now.C != nil { + printNextChild(newSeg, now.C) + } + if now.B != nil { + printNextChild(segment, now.B) + } +} + +func (t *Tree) FindReverse(domain string) (ip string) { + domain = reverseString(domain) + var matched bool = false + now := t.Root.C + for _, k := range domain { + for now != nil { + if now.K == k { + matched = true + break + } + if now.K == '*' { + matched = true + return now.N.V + } + now = now.B + } + if now != nil && now.K == k { + now = now.C + matched = true + continue + } + matched = false + break + } + if matched { + if now != nil && now.N != nil { + return now.N.V + } + } + return "" +}