Golang 匹配域名通配符

Golang match domain names wild card

我有主机名白名单映射

var hostnameWhitelist = map[string] bool { "test.mydomain.com":true, "test12.mydomaindev.com":true}

我检查传入请求的主机名是否被允许的方法是 -

    url, errURL := url.Parse("test.mydomain.com")
    if errURL != nil {
        fmt.Println("error during parsing URL")
        return false
    }
    fmt.Println("HOSTNAME = " + url.Hostname())

    if ok := hostnameWhitelist[url.Hostname()]; ok {
        fmt.Println("valid domain, allow access")
    } else {
        fmt.Println("NOT valid domain")
        return false
    }

虽然这很好用,但我如何进行外卡匹配 -

*.mydomain.com 
*.mydomaindev.com 

这两个都应该通过。

同时,

*.test.com
*.hello.com

应该失败

您可以将地图的键以*.domain.com

的格式存储

使用 strings.SplitAfterN and strings.Join.

将您获得的所有主机名转换为该格式
split := strings.SplitAfterN(url.Hostname(),".",2)
split[0] = "*"
hostName := strings.Join(split,".")
...
hostnameWhitelist[hostName]
...

Play Link

不相关的改进

如果您将地图纯粹用作白名单,则可以使用 map[string]struct{} 而不是 map[string]bool。但正如 Peter 在他的评论中提到的,只有当你有一个非常大的白名单时,它才可能是相关的。

正则表达式是您问题的解决方案,当您尝试将正则表达式与单个值匹配时,map[string]bool 可能无法按预期工作。

package main

import (
    "fmt"
    "regexp"
)

func main() {
    if matched, _ := regexp.MatchString(".*\.?mydomain.*", "mydomaindev.com"); matched {
        fmt.Println("Valid domain")
    }
}

这将匹配所有具有模式 mydomain 的域,因此 www.mydomain.com www.mydomaindev.com 将匹配 byt test.com 而 hello.com 将失败

其他方便的字符串操作是,

//This would match a.mydomain.com, b.mydomain.com etc.,
if strings.HasSuffix(url1.Hostname(), ".mydomain.com") {
    fmt.Println("Valid domain allow access")
}

//To match anything with mydomain - like mydomain.com, mydomaindev.com
if strings.Contains(url2.Hostname(), "mydomain.com") {
    fmt.Println("Valid domain allow access")
}

如果您希望能够在您的域中有多个深度,例如:

  • *.foo.example.org
  • *.example.com

然后我会为通配符添加第二个容器:

var wdomains = []string { ".foo.example.org", ".example.com"}

然后检查您要测试的域是否以这些条目之一结尾:

func inWdomain(wdomains []string, domain string) bool {
    for _, suffix := range wdomains {
        if strings.HasSuffix(domain, suffix) {
            return true
        }
    }
    return false
}

注意:如果您的域超过数百个,您可以使用基数树。

https://play.golang.org/p/-4n8mlGmpH

在开始时使用通配符进行通配符匹配非常昂贵。正则表达式在性能方面可能很困难,具体取决于数据集的大小和对数据集进行评估的速度。您可以尝试使用后缀树,但我怀疑性能可能会成为一个问题(我还没有在我们的数据上测试过)。

我们使用的一种方法是使用反向八位字节顺序的签名域名标签构建 Radix Trie(紧凑型前缀 trie)。您的签名域 *.foo.example.com 变为 com.example.foo.*,将通配符放在末尾。您自定义构建的 Radix 树仅在到达通配符节点时才需要停止匹配。您的 Trie 可以同时支持精确字符串匹配和通配符匹配。如果您希望允许通配符位于域名中间,性能可能会成为问题。

我们使用 Trie 评估域名时遇到的最大挑战之一不是搜索时间,而是内存消耗,因此当您有很多签名时启动程序需要多长时间。

我们评估了一些实现(在开始时主要没有通配符支持)测试加载时间、分配、内部节点数量、内存消耗、GC 时间和 search/insert/remove 时间。

我们测试过的实现:

显然,使用 golang 映射会提供最佳性能,但是当需要检索(Trie 这个词的来源)时,例如来自数据集的前缀信息,golang maps 没有给我们提供我们需要的特征。

我们在 Trie 中保留了大约 700 000 个域名签名。构建时间为2秒,300MB内存,500万分配,2秒GC和搜索成本150ns/op.

如果我们对相同的签名(没有通配符)使用 golang 映射,我们得到加载时间 0.5 秒、50MB 内存、可忽略的分配、1.6 秒 GC 和搜索成本 25ns/op。

在我们的初始实施中,构建时间为 6 秒、1GB 内存、6000 万次分配、5 秒 GC 和搜索成本约为 200 ns/op。

正如您从这些结果中看到的,我们设法降低了内存消耗和加载时间,同时搜索成本大致保持不变。

如果您要进行 CIDR 匹配,我建议您查看 https://github.com/kentik/patricia。为了减少 GC 时间,实现了避免指针。

祝你工作顺利!

您可以像使用 Set 数据结构一样使用 fstest.MapFS,还有以下好处 全局匹配:

package main
import "testing/fstest"

var tests = []struct {
   pat string
   res int
} {
   {"*.hello.com", 0},
   {"*.mydomain.com", 1},
   {"*.mydomaindev.com", 1},
   {"*.test.com", 0},
}

func main() {
   m := fstest.MapFS{"test.mydomain.com": nil, "test12.mydomaindev.com": nil}
   for _, test := range tests {
      a, e := m.Glob(test.pat)
      if e != nil {
         panic(e)
      }
      if len(a) != test.res {
         panic(len(a))
      }
   }
}

https://golang.org/pkg/testing/fstest