mirror of
				https://github.com/TeaOSLab/EdgeNode.git
				synced 2025-11-04 07:40:56 +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/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
 | 
				
			||||||
	github.com/lionsoul2014/ip2region v2.2.0-release+incompatible
 | 
						github.com/lionsoul2014/ip2region v2.2.0-release+incompatible
 | 
				
			||||||
	github.com/mattn/go-sqlite3 v2.0.3+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/mssola/user_agent v0.5.2
 | 
				
			||||||
	github.com/pires/go-proxyproto v0.6.1
 | 
						github.com/pires/go-proxyproto v0.6.1
 | 
				
			||||||
	github.com/shirou/gopsutil v3.21.5+incompatible
 | 
						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/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 h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
					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/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/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
				
			||||||
github.com/mssola/user_agent v0.5.2 h1:CZkTUahjL1+OcZ5zv3kZr8QiJ8jy2H08vZIEkBeRbxo=
 | 
					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-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-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-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/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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 // 当前活跃的连接数
 | 
						countActiveConnections int64 // 当前活跃的连接数
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 初始化
 | 
					// Init 初始化
 | 
				
			||||||
func (this *BaseListener) Init() {
 | 
					func (this *BaseListener) Init() {
 | 
				
			||||||
	this.namedServers = map[string]*NamedServer{}
 | 
						this.namedServers = map[string]*NamedServer{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 清除既有配置
 | 
					// Reset 清除既有配置
 | 
				
			||||||
func (this *BaseListener) Reset() {
 | 
					func (this *BaseListener) Reset() {
 | 
				
			||||||
	this.namedServersLocker.Lock()
 | 
						this.namedServersLocker.Lock()
 | 
				
			||||||
	this.namedServers = map[string]*NamedServer{}
 | 
						this.namedServers = map[string]*NamedServer{}
 | 
				
			||||||
	this.namedServersLocker.Unlock()
 | 
						this.namedServersLocker.Unlock()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 获取当前活跃连接数
 | 
					// CountActiveListeners 获取当前活跃连接数
 | 
				
			||||||
func (this *BaseListener) CountActiveListeners() int {
 | 
					func (this *BaseListener) CountActiveListeners() int {
 | 
				
			||||||
	return types.Int(this.countActiveConnections)
 | 
						return types.Int(this.countActiveConnections)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -253,3 +253,38 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return nil, name
 | 
						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)
 | 
						server, serverName := this.findNamedServer(domain)
 | 
				
			||||||
	if server == nil {
 | 
						if server == nil {
 | 
				
			||||||
		// 严格匹配域名模式下,我们拒绝用户访问
 | 
							server = this.findServerWithCname(domain)
 | 
				
			||||||
		if sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly {
 | 
							if server == nil {
 | 
				
			||||||
			httpAllConfig := sharedNodeConfig.GlobalConfig.HTTPAll
 | 
								// 严格匹配域名模式下,我们拒绝用户访问
 | 
				
			||||||
			mismatchAction := httpAllConfig.DomainMismatchAction
 | 
								if sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly {
 | 
				
			||||||
			if mismatchAction != nil && mismatchAction.Code == "page" {
 | 
									httpAllConfig := sharedNodeConfig.GlobalConfig.HTTPAll
 | 
				
			||||||
				if mismatchAction.Options != nil {
 | 
									mismatchAction := httpAllConfig.DomainMismatchAction
 | 
				
			||||||
					rawWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
 | 
									if mismatchAction != nil && mismatchAction.Code == "page" {
 | 
				
			||||||
					rawWriter.WriteHeader(mismatchAction.Options.GetInt("statusCode"))
 | 
										if mismatchAction.Options != nil {
 | 
				
			||||||
					_, _ = rawWriter.Write([]byte(mismatchAction.Options.GetString("contentHTML")))
 | 
											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 {
 | 
									} else {
 | 
				
			||||||
					http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound)
 | 
										hijacker, ok := rawWriter.(http.Hijacker)
 | 
				
			||||||
				}
 | 
										if ok {
 | 
				
			||||||
				return
 | 
											conn, _, _ := hijacker.Hijack()
 | 
				
			||||||
			} else {
 | 
											if conn != nil {
 | 
				
			||||||
				hijacker, ok := rawWriter.(http.Hijacker)
 | 
												_ = conn.Close()
 | 
				
			||||||
				if ok {
 | 
												return
 | 
				
			||||||
					conn, _, _ := hijacker.Hijack()
 | 
											}
 | 
				
			||||||
					if conn != nil {
 | 
					 | 
				
			||||||
						_ = conn.Close()
 | 
					 | 
				
			||||||
						return
 | 
					 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound)
 | 
								http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound)
 | 
				
			||||||
		return
 | 
								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