Skip to content

Commit

Permalink
支持任意域名通过CNAME访问服务(开启选项后)
Browse files Browse the repository at this point in the history
  • Loading branch information
goedgecloud committed Oct 16, 2021
1 parent 775eb54 commit 67324c9
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 25 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
41 changes: 38 additions & 3 deletions internal/nodes/listener_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
49 changes: 27 additions & 22 deletions internal/nodes/listener_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

// 包装新请求对象
Expand Down
48 changes: 48 additions & 0 deletions internal/nodes/server_cname_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2021 Liuxiangchao [email protected]. 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 changes: 19 additions & 0 deletions internal/nodes/server_cname_manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2021 Liuxiangchao [email protected]. 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 changes: 35 additions & 0 deletions internal/utils/lookup.go
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions internal/utils/lookup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2021 Liuxiangchao [email protected]. All rights reserved.

package utils

import "testing"

func TestLookupCNAME(t *testing.T) {
t.Log(LookupCNAME("www.yun4s.cn"))
}

0 comments on commit 67324c9

Please sign in to comment.