mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2025-11-03 06:40:25 +08:00
支持任意域名通过CNAME访问服务(开启选项后)
This commit is contained in:
1
go.mod
1
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
|
||||
|
||||
3
go.sum
3
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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
// 包装新请求对象
|
||||
|
||||
48
internal/nodes/server_cname_manager.go
Normal file
48
internal/nodes/server_cname_manager.go
Normal file
@@ -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
|
||||
}
|
||||
19
internal/nodes/server_cname_manager_test.go
Normal file
19
internal/nodes/server_cname_manager_test.go
Normal file
@@ -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"))
|
||||
}
|
||||
35
internal/utils/lookup.go
Normal file
35
internal/utils/lookup.go
Normal file
@@ -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
|
||||
}
|
||||
9
internal/utils/lookup_test.go
Normal file
9
internal/utils/lookup_test.go
Normal file
@@ -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"))
|
||||
}
|
||||
Reference in New Issue
Block a user