From 8935f35b4ed54dbb22e562b2b6bd819abfd8d431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=A5=A5=E8=B6=85?= Date: Sat, 16 Oct 2021 12:03:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BB=BB=E6=84=8F=E5=9F=9F?= =?UTF-8?q?=E5=90=8D=E9=80=9A=E8=BF=87CNAME=E8=AE=BF=E9=97=AE=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=EF=BC=88=E5=BC=80=E5=90=AF=E9=80=89=E9=A1=B9=E5=90=8E?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 1 + go.sum | 3 ++ internal/nodes/listener_base.go | 41 +++++++++++++++-- internal/nodes/listener_http.go | 49 ++++++++++++--------- internal/nodes/server_cname_manager.go | 48 ++++++++++++++++++++ internal/nodes/server_cname_manager_test.go | 19 ++++++++ internal/utils/lookup.go | 35 +++++++++++++++ internal/utils/lookup_test.go | 9 ++++ 8 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 internal/nodes/server_cname_manager.go create mode 100644 internal/nodes/server_cname_manager_test.go create mode 100644 internal/utils/lookup.go create mode 100644 internal/utils/lookup_test.go diff --git a/go.mod b/go.mod index 1c6eba0..e247494 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/lionsoul2014/ip2region v2.2.0-release+incompatible github.com/mattn/go-sqlite3 v2.0.3+incompatible + github.com/miekg/dns v1.1.43 github.com/mssola/user_agent v0.5.2 github.com/pires/go-proxyproto v0.6.1 github.com/shirou/gopsutil v3.21.5+incompatible diff --git a/go.sum b/go.sum index 017e22d..a7a9502 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,8 @@ github.com/lionsoul2014/ip2region v2.2.0-release+incompatible h1:1qp9iks+69h7IGL github.com/lionsoul2014/ip2region v2.2.0-release+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mssola/user_agent v0.5.2 h1:CZkTUahjL1+OcZ5zv3kZr8QiJ8jy2H08vZIEkBeRbxo= @@ -163,6 +165,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/internal/nodes/listener_base.go b/internal/nodes/listener_base.go index 53502c1..3b026fd 100644 --- a/internal/nodes/listener_base.go +++ b/internal/nodes/listener_base.go @@ -21,19 +21,19 @@ type BaseListener struct { countActiveConnections int64 // 当前活跃的连接数 } -// 初始化 +// Init 初始化 func (this *BaseListener) Init() { this.namedServers = map[string]*NamedServer{} } -// 清除既有配置 +// Reset 清除既有配置 func (this *BaseListener) Reset() { this.namedServersLocker.Lock() this.namedServers = map[string]*NamedServer{} this.namedServersLocker.Unlock() } -// 获取当前活跃连接数 +// CountActiveListeners 获取当前活跃连接数 func (this *BaseListener) CountActiveListeners() int { return types.Int(this.countActiveConnections) } @@ -253,3 +253,38 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser return nil, name } + +// 使用CNAME来查找服务 +// TODO 防止单IP随机生成域名攻击 +func (this *BaseListener) findServerWithCname(domain string) *serverconfigs.ServerConfig { + if !sharedNodeConfig.SupportCNAME { + return nil + } + + var realName = sharedCNAMEManager.Lookup(domain) + if len(realName) == 0 { + return nil + } + + this.serversLocker.Lock() + defer this.serversLocker.Unlock() + + group := this.Group + if group == nil { + return nil + } + + currentServers := group.Servers + countServers := len(currentServers) + if countServers == 0 { + return nil + } + + for _, server := range currentServers { + if server.SupportCNAME && lists.ContainsString(server.AliasServerNames, realName) { + return server + } + } + + return nil +} diff --git a/internal/nodes/listener_http.go b/internal/nodes/listener_http.go index ba05424..0882ffe 100644 --- a/internal/nodes/listener_http.go +++ b/internal/nodes/listener_http.go @@ -157,33 +157,38 @@ func (this *HTTPListener) handleHTTP(rawWriter http.ResponseWriter, rawReq *http server, serverName := this.findNamedServer(domain) if server == nil { - // 严格匹配域名模式下,我们拒绝用户访问 - if sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly { - httpAllConfig := sharedNodeConfig.GlobalConfig.HTTPAll - mismatchAction := httpAllConfig.DomainMismatchAction - if mismatchAction != nil && mismatchAction.Code == "page" { - if mismatchAction.Options != nil { - rawWriter.Header().Set("Content-Type", "text/html; charset=utf-8") - rawWriter.WriteHeader(mismatchAction.Options.GetInt("statusCode")) - _, _ = rawWriter.Write([]byte(mismatchAction.Options.GetString("contentHTML"))) + server = this.findServerWithCname(domain) + if server == nil { + // 严格匹配域名模式下,我们拒绝用户访问 + if sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly { + httpAllConfig := sharedNodeConfig.GlobalConfig.HTTPAll + mismatchAction := httpAllConfig.DomainMismatchAction + if mismatchAction != nil && mismatchAction.Code == "page" { + if mismatchAction.Options != nil { + rawWriter.Header().Set("Content-Type", "text/html; charset=utf-8") + rawWriter.WriteHeader(mismatchAction.Options.GetInt("statusCode")) + _, _ = rawWriter.Write([]byte(mismatchAction.Options.GetString("contentHTML"))) + } else { + http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound) + } + return } else { - http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound) - } - return - } else { - hijacker, ok := rawWriter.(http.Hijacker) - if ok { - conn, _, _ := hijacker.Hijack() - if conn != nil { - _ = conn.Close() - return + hijacker, ok := rawWriter.(http.Hijacker) + if ok { + conn, _, _ := hijacker.Hijack() + if conn != nil { + _ = conn.Close() + return + } } } } - } - http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound) - return + http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound) + return + } else { + serverName = domain + } } // 包装新请求对象 diff --git a/internal/nodes/server_cname_manager.go b/internal/nodes/server_cname_manager.go new file mode 100644 index 0000000..1e5790c --- /dev/null +++ b/internal/nodes/server_cname_manager.go @@ -0,0 +1,48 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package nodes + +import ( + "github.com/TeaOSLab/EdgeNode/internal/ttlcache" + "github.com/TeaOSLab/EdgeNode/internal/utils" + "github.com/iwind/TeaGo/types" + "strings" + "sync" + "time" +) + +var sharedCNAMEManager = NewServerCNAMEManager() + +// ServerCNAMEManager 服务CNAME管理 +// TODO 需要自动更新缓存里的记录 +type ServerCNAMEManager struct { + ttlCache *ttlcache.Cache + + locker sync.Mutex +} + +func NewServerCNAMEManager() *ServerCNAMEManager { + return &ServerCNAMEManager{ + ttlCache: ttlcache.NewCache(), + } +} + +func (this *ServerCNAMEManager) Lookup(domain string) string { + if len(domain) == 0 { + return "" + } + + var item = this.ttlCache.Read(domain) + if item != nil { + return types.String(item.Value) + } + + cname, _ := utils.LookupCNAME(domain) + if len(cname) > 0 { + cname = strings.TrimSuffix(cname, ".") + } + + this.ttlCache.Write(domain, cname, time.Now().Unix()+600) + + return cname +} diff --git a/internal/nodes/server_cname_manager_test.go b/internal/nodes/server_cname_manager_test.go new file mode 100644 index 0000000..8af805c --- /dev/null +++ b/internal/nodes/server_cname_manager_test.go @@ -0,0 +1,19 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package nodes + +import ( + "testing" + "time" +) + +func TestServerCNameManager_Lookup(t *testing.T) { + var cnameManager = NewServerCNAMEManager() + t.Log(cnameManager.Lookup("www.yun4s.cn")) + + var before = time.Now() + defer func() { + t.Log(time.Since(before).Seconds()*1000, "ms") + }() + t.Log(cnameManager.Lookup("www.yun4s.cn")) +} diff --git a/internal/utils/lookup.go b/internal/utils/lookup.go new file mode 100644 index 0000000..8e6fb1e --- /dev/null +++ b/internal/utils/lookup.go @@ -0,0 +1,35 @@ +package utils + +import ( + "github.com/TeaOSLab/EdgeCommon/pkg/configutils" + "github.com/miekg/dns" +) + +// LookupCNAME 获取CNAME +func LookupCNAME(host string) (string, error) { + config, err := dns.ClientConfigFromFile("/etc/resolv.conf") + if err != nil { + return "", err + } + + c := new(dns.Client) + m := new(dns.Msg) + + m.SetQuestion(host+".", dns.TypeCNAME) + m.RecursionDesired = true + + var lastErr error + for _, serverAddr := range config.Servers { + r, _, err := c.Exchange(m, configutils.QuoteIP(serverAddr)+":"+config.Port) + if err != nil { + lastErr = err + continue + } + if len(r.Answer) == 0 { + continue + } + + return r.Answer[0].(*dns.CNAME).Target, nil + } + return "", lastErr +} diff --git a/internal/utils/lookup_test.go b/internal/utils/lookup_test.go new file mode 100644 index 0000000..83869f0 --- /dev/null +++ b/internal/utils/lookup_test.go @@ -0,0 +1,9 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package utils + +import "testing" + +func TestLookupCNAME(t *testing.T) { + t.Log(LookupCNAME("www.yun4s.cn")) +}