diff --git a/config/components/httpcli/dns.go b/config/components/httpcli/dns.go index b2ca337b..a690a379 100644 --- a/config/components/httpcli/dns.go +++ b/config/components/httpcli/dns.go @@ -35,23 +35,23 @@ import ( htcdns "github.com/nabbar/golib/httpcli/dns-mapper" ) -func (o *componentHttpClient) Add(endpoint string, ip string) { +func (o *componentHttpClient) Add(from string, to string) { if d := o.getDNSMapper(); d != nil { - d.Add(endpoint, ip) + d.Add(from, to) o.setDNSMapper(d) } } -func (o *componentHttpClient) Get(endpoint string) string { +func (o *componentHttpClient) Get(from string) string { if d := o.getDNSMapper(); d != nil { - return d.Get(endpoint) + return d.Get(from) } return "" } -func (o *componentHttpClient) Del(endpoint string) { +func (o *componentHttpClient) Del(from string) { if d := o.getDNSMapper(); d != nil { - d.Del(endpoint) + d.Del(from) o.setDNSMapper(d) } } @@ -101,3 +101,41 @@ func (o *componentHttpClient) TimeCleaner(ctx context.Context, dur time.Duration d.TimeCleaner(ctx, dur) } } + +func (o *componentHttpClient) Len() int { + if d := o.getDNSMapper(); d != nil { + return d.Len() + } + + return 0 +} + +func (o *componentHttpClient) Walk(f func(from string, to string) bool) { + if d := o.getDNSMapper(); d != nil { + d.Walk(f) + } +} + +func (o *componentHttpClient) Clean(endpoint string) (host string, port string, err error) { + if d := o.getDNSMapper(); d != nil { + return d.Clean(endpoint) + } + + return "", "", ErrorComponentNotInitialized.Error() +} + +func (o *componentHttpClient) Search(endpoint string) (string, error) { + if d := o.getDNSMapper(); d != nil { + return d.Search(endpoint) + } + + return "", ErrorComponentNotInitialized.Error() +} + +func (o *componentHttpClient) SearchWithCache(endpoint string) (string, error) { + if d := o.getDNSMapper(); d != nil { + return d.SearchWithCache(endpoint) + } + + return "", ErrorComponentNotInitialized.Error() +} diff --git a/httpcli/dns-mapper/cache.go b/httpcli/dns-mapper/cache.go new file mode 100644 index 00000000..6d375278 --- /dev/null +++ b/httpcli/dns-mapper/cache.go @@ -0,0 +1,46 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package dns_mapper + +func (o *dmp) CacheHas(endpoint string) bool { + _, l := o.z.Load(endpoint) + return l +} + +func (o *dmp) CacheGet(endpoint string) string { + if i, l := o.z.Load(endpoint); !l { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *dmp) CacheSet(endpoint, ip string) { + o.z.Store(endpoint, ip) +} diff --git a/httpcli/dns-mapper/collection.go b/httpcli/dns-mapper/collection.go new file mode 100644 index 00000000..1fe9add1 --- /dev/null +++ b/httpcli/dns-mapper/collection.go @@ -0,0 +1,177 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package dns_mapper + +import ( + "net" + "strings" +) + +func (o *dmp) Len() int { + var i int + o.d.Range(func(key, value any) bool { + i++ + return true + }) + return i +} + +func (o *dmp) Add(from, to string) { + if d := newPart(from); d == nil { + return + } else { + o.d.Store(d, to) + } +} + +func (o *dmp) Get(endpoint string) string { + var ( + h, p, _ = o.Clean(endpoint) + res string + ) + + if p != "" { + h = h + ":" + p + } + + o.Walk(func(from, to string) bool { + if from == h { + res = to + return false + } + + return true + }) + + return res +} + +func (o *dmp) Del(endpoint string) { + var h, p, _ = o.Clean(endpoint) + + if p != "" { + h = h + ":" + p + } + + o.WalkDP(func(from *dp, to string) bool { + if from.String() == h { + o.d.Delete(from) + return false + } + + return true + }) +} + +func (o *dmp) Walk(fct func(from, to string) bool) { + o.d.Range(func(key, value any) bool { + if d, l := key.(*dp); !l { + return true + } else if t, k := value.(string); !k { + return true + } else { + return fct(d.String(), t) + } + }) +} + +func (o *dmp) WalkDP(fct func(from *dp, to string) bool) { + o.d.Range(func(key, value any) bool { + if d, l := key.(*dp); !l { + return true + } else if t, k := value.(string); !k { + return true + } else { + return fct(d, t) + } + }) +} + +func (o *dmp) Clean(endpoint string) (host string, port string, err error) { + host, port, err = net.SplitHostPort(endpoint) + + if err != nil { + return strings.TrimSpace(endpoint), "", err + } + + return strings.TrimSpace(host), strings.TrimPrefix(strings.TrimSpace(port), "0"), nil +} + +func (o *dmp) Search(endpoint string) (string, error) { + var ( + h, p, e = o.Clean(endpoint) + src *dp + res string + ) + + if e != nil { + return "", e + } else if src = newPartDetail(h, p); src == nil { + return endpoint, nil + } + + o.WalkDP(func(from *dp, to string) bool { + if from.FQDNMatch(src.FQDNRaw()) { + if from.PortMatch(src.Port()) { + res = to + + if _, _, e = net.SplitHostPort(to); e != nil { + res += ":" + src.Port() + } + + return false + } + } + + return true + }) + + return res, nil +} + +func (o *dmp) SearchWithCache(endpoint string) (string, error) { + var ( + e error + d string + ) + + if o.CacheHas(endpoint) { + if d = o.CacheGet(endpoint); len(d) > 0 { + return d, nil + } + } + + if d, e = o.Search(endpoint); e != nil { + return "", e + } else if len(d) > 0 { + o.CacheSet(endpoint, d) + return d, nil + } else { + o.CacheSet(endpoint, endpoint) + return endpoint, nil + } +} diff --git a/httpcli/dns-mapper/dns_mapper_suite_test.go b/httpcli/dns-mapper/dns_mapper_suite_test.go new file mode 100644 index 00000000..2c1b7adb --- /dev/null +++ b/httpcli/dns-mapper/dns_mapper_suite_test.go @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package dns_mapper_test + +import ( + "context" + "errors" + "fmt" + "net/http" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +/* + Using https://onsi.github.io/ginkgo/ + Running with $> ginkgo -cover . +*/ + +var srv = &http.Server{ + Addr: ":8080", + Handler: Hello(), +} + +var ( + ctx, cnl = context.WithCancel(context.Background()) +) + +func TestGolibHttpDNSMapperHelper(t *testing.T) { + defer func() { + cnl() + _ = srv.Shutdown(context.Background()) + }() + + go func() { + if e := srv.ListenAndServe(); e != nil { + if !errors.Is(e, http.ErrServerClosed) { + panic(e) + } + } + }() + + time.Sleep(500 * time.Millisecond) + + RegisterFailHandler(Fail) + RunSpecs(t, "HTTP DNS Mapper Helper Suite") +} + +var _ = BeforeSuite(func() { +}) + +var _ = AfterSuite(func() { +}) + +func Hello() http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + _, _ = fmt.Fprintf(writer, "hello\n") + _, _ = fmt.Fprintf(writer, "Requested Hostname: %s\n", request.Host) + _, _ = fmt.Fprintf(writer, "Requested Uri: %s\n", request.RequestURI) + _, _ = fmt.Fprintf(writer, "Requested: %s\n", request.RemoteAddr) + } +} diff --git a/httpcli/dns-mapper/dns_mapper_test.go b/httpcli/dns-mapper/dns_mapper_test.go new file mode 100644 index 00000000..0a388ec7 --- /dev/null +++ b/httpcli/dns-mapper/dns_mapper_test.go @@ -0,0 +1,573 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package dns_mapper_test + +import ( + "fmt" + "time" + + libdur "github.com/nabbar/golib/duration" + htcdns "github.com/nabbar/golib/httpcli/dns-mapper" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var ( + idx int = 1 + opt htcdns.Config + dns htcdns.DNSMapper +) + +func init() { + opt = htcdns.Config{ + DNSMapper: make(map[string]string), + TimerClean: libdur.ParseDuration(1000 * time.Hour), + Transport: htcdns.TransportConfig{}, + } + + addDns(addIdx("127.0.0."), addIdx("8"), addIdx("127.0.0."), "") + idx++ + + addDns("test.example.com", numIdx("8"), addIdx("127.0.0."), "") + idx++ + addDns("test.example.com", numIdx("*"), addIdx("127.0.0."), "") + idx++ + addDns("*.test.example.com", numIdx("8"), addIdx("127.0.0."), "") + idx++ + addDns("*.test.example.com", numIdx("*"), addIdx("127.0.0."), "") + idx++ + + addDns("test.example.com", numIdx("8"), addIdx("127.0.0."), numIdx("8")) + idx++ + addDns("test.example.com", numIdx("*"), addIdx("127.0.0."), numIdx("8")) + idx++ + addDns("*.test.example.com", numIdx("8"), addIdx("127.0.0."), numIdx("8")) + idx++ + addDns("*.test.example.com", numIdx("*"), addIdx("127.0.0."), numIdx("8")) + idx++ + + addDns("*.test.example.com", numIdx("8"), addIdx("127.0.0."), "") + idx++ + addDns("*.test.example.com", numIdx("*"), addIdx("127.0.0."), "") + idx++ + addDns("*.*.test.example.com", numIdx("8"), addIdx("127.0.0."), "") + idx++ + addDns("*.*.test.example.com", numIdx("*"), addIdx("127.0.0."), "") + idx++ + + addDns("*.test.example.com", numIdx("8"), addIdx("127.0.0."), numIdx("8")) + idx++ + addDns("*.test.example.com", numIdx("*"), addIdx("127.0.0."), numIdx("8")) + idx++ + addDns("*.*.test.example.com", numIdx("8"), addIdx("127.0.0."), numIdx("8")) + idx++ + addDns("*.*.test.example.com", numIdx("*"), addIdx("127.0.0."), numIdx("8")) + idx++ + + dns = htcdns.New(ctx, &opt, nil) +} + +func addIdx(src string) string { + return fmt.Sprintf("%s%d", src, idx) +} + +func numIdx(src string) string { + if idx < 10 { + return fmt.Sprintf("%s0%d", src, idx) + } else { + return fmt.Sprintf("%s%d", src, idx) + } +} + +func addDns(hostSrc, portSrc, hostDst, portDst string) { + if portSrc != "" { + hostSrc = hostSrc + ":" + portSrc + } + + if portDst != "" { + hostDst = hostDst + ":" + portDst + } + + opt.DNSMapper[hostSrc] = hostDst +} + +var _ = Describe("DNS Mapper", func() { + Context("check dns mapper", func() { + It("be a valid dns mapper", func() { + Expect(dns).NotTo(BeNil()) + }) + + It(fmt.Sprintf("must having '%d' item in dns mapper", idx-2), func() { + Expect(dns.Len()).To(BeIdenticalTo(idx - 2)) + }) + + It("must having one more item if adding a valid item", func() { + l := dns.Len() + dns.Add("*.localhost", "127.0.0.1:8080") + Expect(dns.Len()).To(BeIdenticalTo(l + 1)) + }) + + It("must having item just adding and can return it", func() { + Expect(dns.Get("*.localhost")).To(BeIdenticalTo("127.0.0.1:8080")) + }) + + It("must return a new client to dial to localserver", func() { + cli := dns.DefaultClient() + Expect(cli).NotTo(BeNil()) + + rsp, err := cli.Get("http://test.localhost") + + Expect(err).NotTo(HaveOccurred()) + Expect(rsp).NotTo(BeNil()) + Expect(rsp.Body).NotTo(BeNil()) + Expect(rsp.Body.Close()).NotTo(HaveOccurred()) + }) + + It(fmt.Sprintf("must having '%d' item in dns mapper if delete just added item", idx-2), func() { + dns.Del("*.localhost") + Expect(dns.Len()).To(BeIdenticalTo(idx - 2)) + }) + + It("must fail for testing source as ip without port '127.0.0.128'", func() { + var ( + err error + src = "127.0.0.128" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).To(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).To(HaveOccurred()) + Expect(res1).To(BeIdenticalTo("")) + + res2, err = dns.SearchWithCache(src) + Expect(err).To(HaveOccurred()) + Expect(res2).To(BeIdenticalTo("")) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing source as ip '127.0.0.128:9001' and result must be source ip:port", func() { + var ( + err error + src = "127.0.0.128:9001" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(src)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(src)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must failt for testing 'test.example.com' without port", func() { + var ( + err error + src = "test.example.com" + res1 string + res2 string + ) + + _, _, err = dns.Clean(src) + Expect(err).To(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).To(HaveOccurred()) + Expect(res1).To(BeIdenticalTo("")) + + res2, err = dns.SearchWithCache(src) + Expect(err).To(HaveOccurred()) + Expect(res2).To(BeIdenticalTo("")) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'test.example.com:801' with same source and destination", func() { + var ( + err error + src = "test.example.com:801" + res1 string + res2 string + ) + + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(src)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(src)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'test.example.com:802'", func() { + var ( + err error + src = "test.example.com:802" + ctr = "127.0.0.2:802" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'test.example.com:803'", func() { + var ( + err error + src = "test.example.com:803" + ctr = "127.0.0.3:803" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'any.test.example.com:804'", func() { + var ( + err error + src = "any.test.example.com:804" + ctr = "127.0.0.4:804" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'any.test.example.com:805'", func() { + var ( + err error + src = "any.test.example.com:805" + ctr = "127.0.0.5:805" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'test.example.com:806'", func() { + var ( + err error + src = "test.example.com:806" + ctr = "127.0.0.6:806" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'test.example.com:807'", func() { + var ( + err error + src = "test.example.com:807" + ctr = "127.0.0.7:807" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'any.test.example.com:808'", func() { + var ( + err error + src = "any.test.example.com:808" + ctr = "127.0.0.8:808" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'any.test.example.com:809'", func() { + var ( + err error + src = "any.test.example.com:809" + ctr = "127.0.0.9:809" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'one.test.example.com:810'", func() { + var ( + err error + src = "one.test.example.com:810" + ctr = "127.0.0.10:810" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'one.test.example.com:811'", func() { + var ( + err error + src = "one.test.example.com:811" + ctr = "127.0.0.11:811" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'one.any.test.example.com:812'", func() { + var ( + err error + src = "one.any.test.example.com:812" + ctr = "127.0.0.12:812" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'one.any.test.example.com:813'", func() { + var ( + err error + src = "one.any.test.example.com:813" + ctr = "127.0.0.13:813" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'one.test.example.com:814'", func() { + var ( + err error + src = "one.test.example.com:814" + ctr = "127.0.0.14:814" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'one.test.example.com:815'", func() { + var ( + err error + src = "one.test.example.com:815" + ctr = "127.0.0.15:815" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'one.any.test.example.com:816'", func() { + var ( + err error + src = "one.any.test.example.com:816" + ctr = "127.0.0.16:816" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + + It("must success for testing 'one.any.test.example.com:817'", func() { + var ( + err error + src = "one.any.test.example.com:817" + ctr = "127.0.0.17:817" + res1 string + res2 string + ) + _, _, err = dns.Clean(src) + Expect(err).ToNot(HaveOccurred()) + + res1, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res1).To(BeIdenticalTo(ctr)) + + res2, err = dns.SearchWithCache(src) + Expect(err).ToNot(HaveOccurred()) + Expect(res2).To(BeIdenticalTo(ctr)) + Expect(res2).To(BeIdenticalTo(res1)) + }) + }) +}) diff --git a/httpcli/dns-mapper/interface.go b/httpcli/dns-mapper/interface.go index c5691474..7fcc5954 100644 --- a/httpcli/dns-mapper/interface.go +++ b/httpcli/dns-mapper/interface.go @@ -39,9 +39,14 @@ import ( ) type DNSMapper interface { - Add(endpoint, ip string) - Get(endpoint string) string - Del(endpoint string) + Add(from, to string) + Get(from string) string + Del(from string) + Len() int + Walk(func(from, to string) bool) + Clean(endpoint string) (host string, port string, err error) + Search(endpoint string) (string, error) + SearchWithCache(endpoint string) (string, error) DialContext(ctx context.Context, network, address string) (net.Conn, error) Transport(cfg TransportConfig) *http.Transport diff --git a/httpcli/dns-mapper/model.go b/httpcli/dns-mapper/model.go index 0c252ae2..1c1b82e0 100644 --- a/httpcli/dns-mapper/model.go +++ b/httpcli/dns-mapper/model.go @@ -28,8 +28,6 @@ package dns_mapper import ( "context" - "net" - "strings" "sync" "sync/atomic" "time" @@ -78,103 +76,6 @@ func (o *dmp) configDialerKeepAlive() time.Duration { } } -func (o *dmp) CacheHas(endpoint string) bool { - _, l := o.z.Load(endpoint) - return l -} - -func (o *dmp) CacheGet(endpoint string) string { - if i, l := o.z.Load(endpoint); !l { - return "" - } else if v, k := i.(string); !k { - return "" - } else { - return v - } -} - -func (o *dmp) CacheSet(endpoint, ip string) { - o.z.Store(endpoint, ip) -} - -func (o *dmp) Add(endpoint, ip string) { - o.d.Store(endpoint, ip) -} - -func (o *dmp) Get(endpoint string) string { - if i, l := o.d.Load(endpoint); !l { - return "" - } else if s, k := i.(string); !k { - return "" - } else { - return s - } -} - -func (o *dmp) Search(endpoint string) string { - var res string - - o.d.Range(func(key, value any) bool { - var ( - e error - k bool - h string - - src string - dst string - ) - - if src, k = key.(string); !k { - return true - } else if dst, k = value.(string); !k { - return true - } - - if strings.EqualFold(src, endpoint) { - res = dst - return false - } - - h, _, e = net.SplitHostPort(src) - if e == nil { - src = h - } - - if strings.EqualFold(src, endpoint) { - res = dst - return false - } else if strings.HasPrefix(src, "*.") { - // search for wildcard - f := src - t := endpoint - - for strings.HasPrefix(f, "*.") { - if p := strings.SplitAfterN(f, ".", 2); len(p) > 1 { - f = p[1] - } else { - break - } - if p := strings.SplitAfterN(t, ".", 2); len(p) > 1 { - t = p[1] - } - } - - if strings.EqualFold(f, t) { - res = dst - return false - } - } - - return true - }) - - return res -} - -func (o *dmp) Del(endpoint string) { - o.d.Delete(endpoint) -} - func (o *dmp) TimeCleaner(ctx context.Context, dur time.Duration) { if dur < 5*time.Second { dur = 5 * time.Minute @@ -198,12 +99,3 @@ func (o *dmp) TimeCleaner(ctx context.Context, dur time.Duration) { } }() } - -func (o *dmp) Len() int { - var i int - o.d.Range(func(key, value any) bool { - i++ - return true - }) - return i -} diff --git a/httpcli/dns-mapper/part.go b/httpcli/dns-mapper/part.go new file mode 100644 index 00000000..4fd3e80b --- /dev/null +++ b/httpcli/dns-mapper/part.go @@ -0,0 +1,164 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package dns_mapper + +import ( + "net" + "strings" +) + +type dp struct { + fqdn []string + port string +} + +func newPartDetail(fqdn, port string) *dp { + if net.ParseIP(fqdn) != nil { + return nil + } + + return &dp{ + fqdn: strings.Split(strings.TrimSpace(fqdn), "."), + port: strings.TrimPrefix(strings.TrimSpace(port), "0"), + } +} + +func newPart(entry string) *dp { + var ( + host string + port = "*" + ) + + if h, p, e := net.SplitHostPort(entry); e != nil { + const missingPort = "missing port in address" + if en, k := e.(*net.AddrError); !k { + host = entry + } else if en.Err == missingPort { + host = entry + } else { + return nil + } + } else { + host = h + port = p + } + + if net.ParseIP(host) != nil { + return nil + } else { + return &dp{ + fqdn: strings.Split(strings.TrimSpace(host), "."), + port: strings.TrimPrefix(strings.TrimSpace(port), "0"), + } + } +} + +func (o *dp) String() string { + if o.port == "*" { + return o.FQDN() + } else { + return o.FQDN() + ":" + o.port + } +} + +func (o *dp) FQDN() string { + return strings.Join(o.fqdn, ".") +} + +func (o *dp) FQDNRaw() []string { + return o.fqdn +} + +func (o *dp) FQDNWildcard() bool { + return o.fqdn[0] == "*" +} + +func (o *dp) FQDNMatch(fqdn []string) bool { + if len(fqdn) != len(o.fqdn) { + return false + } else if o.FQDNEqual(fqdn) { + return true + } else if !o.FQDNWildcard() { + return false + } + + var fLen = len(o.fqdn) - 1 + + for i := 0; i <= fLen; i++ { + var idx = fLen - i + + if o.fqdn[idx] == "*" { + continue + } else if o.fqdn[idx] != fqdn[idx] { + return false + } + } + + return true +} + +func (o *dp) FQDNEqual(in []string) bool { + if len(in) != len(o.fqdn) { + return false + } + + for idx := range o.fqdn { + if !strings.EqualFold(o.fqdn[idx], in[idx]) { + return false + } + } + + return true +} + +func (o *dp) Port() string { + if o.port != "*" { + return o.port + } + + return "" +} + +func (o *dp) PortWildcard() bool { + return strings.Contains(o.port, "*") +} + +func (o *dp) PortMatch(port string) bool { + if !o.PortWildcard() { + return port == o.port + } else if o.port == "*" { + return true + } + + var part = strings.SplitN(o.port, "*", 2) + + if part[0] == "" { + return port[len(port)-len(part[1]):] == part[1] + } else { + return port[:len(part[0])] == part[0] + } +} diff --git a/httpcli/dns-mapper/transport.go b/httpcli/dns-mapper/transport.go index fe5ffa58..88d98d8c 100644 --- a/httpcli/dns-mapper/transport.go +++ b/httpcli/dns-mapper/transport.go @@ -52,47 +52,16 @@ func (o *dmp) Dial(network, address string) (net.Conn, error) { func (o *dmp) DialContext(ctx context.Context, network, address string) (net.Conn, error) { var ( - e error - d = o.dialer() - a string - - hs string - ps string + e error + d = o.dialer() + dst string ) - if o.CacheHas(address) { - a = o.CacheGet(address) - } - - if len(a) > 0 { - return d.DialContext(ctx, network, a) - } else { - a = o.Get(address) - } - - hs, ps, e = net.SplitHostPort(address) - if e != nil { + if dst, e = o.SearchWithCache(address); e != nil { return nil, e - } - - if len(a) < 1 { - a = o.Get(hs) - } - - if len(a) < 1 { - a = o.Search(hs) - } - - if len(a) < 1 { - o.CacheSet(address, address) - return d.DialContext(ctx, network, address) - } else if _, _, e = net.SplitHostPort(a); e == nil { - o.CacheSet(address, a) - return d.DialContext(ctx, network, a) } else { - a = net.JoinHostPort(a, ps) - o.CacheSet(address, a) - return d.DialContext(ctx, network, a) + o.CacheSet(address, dst) + return d.DialContext(ctx, network, dst) } }