mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-10-10 11:10:20 +08:00
338 lines
8.1 KiB
Go
338 lines
8.1 KiB
Go
package netLayer
|
||
|
||
import (
|
||
"archive/tar"
|
||
"bytes"
|
||
"compress/gzip"
|
||
"crypto/tls"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"io/fs"
|
||
"io/ioutil"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"path/filepath"
|
||
"regexp"
|
||
"strings"
|
||
|
||
"github.com/hahahrfool/v2ray_simple/utils"
|
||
)
|
||
|
||
// geosite是v2fly社区维护的,非常有用!本作以及任何其它项目都没必要另起炉灶,
|
||
// 直接使用v2fly所提供的资料即可。
|
||
//
|
||
// 然而需要注意的是,geosite是一个中国人维护的项目
|
||
// 所有网站的资料都围绕着中国人的需求产生,比如 geolocation-cn 文件,没有同类的 geolocation-us 文件.
|
||
//
|
||
// geosite数据格式可参考
|
||
// https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto
|
||
// 或者xray的 app/router/config.proto
|
||
// 然而我们不引用任何v2ray和xray的代码, 也不使用protobuf
|
||
/*
|
||
我们只能自行读取该项目原始文件,然后生成自己的数据结构
|
||
|
||
文件格式 项目已经解释的很好了,不过使用的英文
|
||
https://github.com/v2fly/domain-list-community
|
||
|
||
# comments
|
||
include:another-file
|
||
domain:google.com @attr1 @attr2
|
||
keyword:google
|
||
regexp:www\.google\.com$
|
||
full:www.google.com
|
||
|
||
下面以中文举例方式讲解一下该geosite单个文件的内容格式
|
||
|
||
一般一行一个域名
|
||
有的行后面跟着空格 和 @和一个属性
|
||
有的行第一个字符为 #, 是注释,有的行行尾也有 # 注释
|
||
|
||
a.alimama.cn @ads
|
||
|
||
有的文件,如 amazon,有如下结构
|
||
|
||
include:amazon-ads
|
||
|
||
有的域名有如下形式
|
||
|
||
full:images-cn.ssl-images-amazon.com @cn
|
||
full:images-cn-8.ssl-images-amazon.com @cn
|
||
|
||
很显然意思是 完整匹配
|
||
|
||
有的域名连点号都没有,比如 amazon
|
||
|
||
我们要做的,首先是下载最新项目文件
|
||
|
||
获取最新版本号
|
||
curl -sL https://api.github.com/repos/v2fly/domain-list-community/releases/latest | jq -r ".tag_name"
|
||
|
||
上面输出设为 tag
|
||
|
||
下载最新源文件
|
||
wget https://github.com/v2fly/domain-list-community/archive/refs/tags/$tag.tar.xz
|
||
|
||
我们只要把这个命令行转化成go语言的形式即可
|
||
|
||
*/
|
||
|
||
var GeositeListMap = make(map[string]*GeositeList)
|
||
|
||
// v2fly经典匹配配置:
|
||
//full:v2ray.com, domain:v2ray.com, domain意思是匹配子域名,
|
||
// 如果没有冒号前缀那就是纯字符串匹配
|
||
// regexp:\.goo.*\.com$ 正则表达式匹配
|
||
//geosite:cn 这种是geosite列表匹配
|
||
|
||
func IsDomainInsideGeosite(geositeName string, domain string) bool {
|
||
geositeName = strings.ToUpper(geositeName)
|
||
glist := GeositeListMap[geositeName]
|
||
//log.Println("IsDomainInsideGeosite called", geositeName, len(glist))
|
||
if glist == nil {
|
||
return false
|
||
}
|
||
|
||
if _, found := glist.FullDomains[domain]; found {
|
||
return true
|
||
}
|
||
if HasFullOrSubDomain(domain, MapGeositeDomainHaser(glist.Domains)) {
|
||
return true
|
||
}
|
||
|
||
for _, reg := range glist.RegexDomains {
|
||
if reg.MatchString(domain) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
type GeositeDomain struct {
|
||
Type string //domain, regexp, full
|
||
Value string
|
||
Attrs []GeositeAttr
|
||
}
|
||
|
||
type GeositeAttr struct {
|
||
Key string
|
||
Value any //bool or int64
|
||
}
|
||
|
||
//GeositeList 用于内存中匹配使用
|
||
type GeositeList struct {
|
||
//Name实际上就是v2fly Community的protobuf里的 CountryCode. Geosite本意是给一个国家的域名分类, 但是实际上功能越来越多,绝大部分Name现在实际上都是网站名称,只有 CN, GEOLOCATION-CN 的是国家名. 其它的还有很多分类名称,比如 CATEGORY-ECOMMERCE
|
||
// 在parse过后,可以发现所有的Name都被转换成了大写字符的形式
|
||
Name string
|
||
//Inclusion map[string]bool //一个list可能包含另一个list, 典型的cn列表就包含了大量子表。在Parse过后,所有的Inclusion项也都被加到了Domains列表中, 所以实际上这个对于实际检索是可有可无的, v2fly的protobuf里就没有该项
|
||
// 这个Inclusion存在的意义是防止重复添加某项,比如列表中出了两个 include相同的表,则只会被include一遍
|
||
// 当一切都加载完毕后, Inclusion 这个map就没有存在的意义了,可以设为nil
|
||
|
||
FullDomains map[string]GeositeDomain
|
||
Domains map[string]GeositeDomain
|
||
RegexDomains []*regexp.Regexp
|
||
}
|
||
|
||
type MapGeositeDomainHaser map[string]GeositeDomain
|
||
|
||
func (mdh MapGeositeDomainHaser) HasDomain(d string) bool {
|
||
_, found := mdh[d]
|
||
return found
|
||
}
|
||
|
||
//从 geosite/data 文件夹中读取所有文件并加载到 GeositeListMap 中.
|
||
//
|
||
//该 geosite/data 就是 github.com/v2fly/domain-list-community 项目的 data文件夹.
|
||
func LoadGeositeFiles() (err error) {
|
||
dir := "geosite/data"
|
||
dir = utils.GetFilePath(dir)
|
||
if !utils.DirExist(dir) {
|
||
return os.ErrNotExist
|
||
}
|
||
ref := make(map[string]*GeositeRawList)
|
||
|
||
err = filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if info.IsDir() {
|
||
return nil
|
||
}
|
||
list, err := LoadGeositeFile(path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
ref[list.Name] = list
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
fmt.Println("Failed: ", err)
|
||
return
|
||
}
|
||
|
||
for name, list := range ref {
|
||
pl, err := ParseGeositeList(list, ref)
|
||
if err != nil {
|
||
fmt.Println("Failed: ", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
GeositeListMap[name] = pl.ToGeositeList()
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// DownloadCommunity_DomainListFiles 从 v2fly/domain-list-community 下载数据文件, 并放到 geosite文件夹中。
|
||
// 如果已存在geosite文件夹,return immediately.
|
||
//
|
||
// 该函数适用于系统中没有git的情况, 如果有git我们直接 git clone就行了,而且还能不断pull进行滚动更新
|
||
func DownloadCommunity_DomainListFiles(proxyurl string) {
|
||
|
||
dir := "geosite/data"
|
||
dir = utils.GetFilePath(dir)
|
||
if utils.DirExist(dir) {
|
||
fmt.Println("geosite/data folder already exists.")
|
||
return
|
||
}
|
||
|
||
var resp *http.Response
|
||
var err error
|
||
|
||
const requestUrl = "https://api.github.com/repos/v2fly/domain-list-community/releases/latest"
|
||
|
||
var thehttpClient = http.DefaultClient
|
||
|
||
if proxyurl == "" {
|
||
resp, err = thehttpClient.Get(requestUrl)
|
||
|
||
} else {
|
||
url_proxy, e2 := url.Parse(proxyurl)
|
||
if e2 != nil {
|
||
fmt.Println("proxyurl given was wrong,", proxyurl, e2)
|
||
return
|
||
}
|
||
|
||
client := &http.Client{
|
||
Transport: &http.Transport{
|
||
Proxy: http.ProxyURL(url_proxy),
|
||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||
},
|
||
}
|
||
|
||
resp, err = client.Get(requestUrl)
|
||
|
||
thehttpClient = client
|
||
}
|
||
if err != nil {
|
||
fmt.Println("http get failed", err)
|
||
return
|
||
}
|
||
body, err := ioutil.ReadAll(resp.Body)
|
||
|
||
if err != nil {
|
||
fmt.Println("http read failed", err)
|
||
return
|
||
}
|
||
|
||
type struct1 struct {
|
||
Tag string `json:"tag_name"`
|
||
}
|
||
var s = struct1{}
|
||
json.Unmarshal(body, &s)
|
||
if s.Tag == "" {
|
||
return
|
||
}
|
||
|
||
const downloadStr = "https://github.com/v2fly/domain-list-community/archive/refs/tags/%s.tar.gz"
|
||
|
||
resp, err = thehttpClient.Get(fmt.Sprintf(downloadStr, s.Tag))
|
||
if err != nil {
|
||
fmt.Println("http get failed 2", err)
|
||
return
|
||
}
|
||
var buf bytes.Buffer
|
||
_, err = buf.ReadFrom(resp.Body)
|
||
|
||
if err != nil {
|
||
fmt.Println("http read failed 2", err)
|
||
return
|
||
}
|
||
|
||
fmt.Println("downloaded size", buf.Len())
|
||
|
||
folderName, err := untarGeositeSourceFiles(&buf)
|
||
|
||
if err != nil {
|
||
fmt.Println("untar failed,", err)
|
||
return
|
||
}
|
||
|
||
fmt.Println("download and extract success!")
|
||
|
||
err = os.Rename(folderName, "geosite")
|
||
if err != nil {
|
||
fmt.Println("rename folder failed", err)
|
||
return
|
||
}
|
||
}
|
||
|
||
//把tar.gz内容解压出来, 并返回根文件夹名称
|
||
func untarGeositeSourceFiles(fr io.Reader) (rootFolderName string, err error) {
|
||
|
||
gr, err := gzip.NewReader(fr)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer gr.Close()
|
||
|
||
tr := tar.NewReader(gr)
|
||
|
||
for {
|
||
var hdr *tar.Header
|
||
hdr, err = tr.Next()
|
||
|
||
switch {
|
||
case err == io.EOF:
|
||
err = nil
|
||
return
|
||
case err != nil:
|
||
return
|
||
case hdr == nil:
|
||
continue
|
||
}
|
||
|
||
dstFileDir := hdr.Name
|
||
|
||
switch hdr.Typeflag {
|
||
case tar.TypeDir:
|
||
if b := utils.DirExist(dstFileDir); !b {
|
||
if err = os.MkdirAll(dstFileDir, 0775); err != nil {
|
||
return
|
||
}
|
||
}
|
||
|
||
if rootFolderName == "" {
|
||
rootFolderName = dstFileDir
|
||
}
|
||
|
||
case tar.TypeReg:
|
||
|
||
var file *os.File
|
||
file, err = os.OpenFile(dstFileDir, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode))
|
||
if err != nil {
|
||
return
|
||
}
|
||
_, err = io.Copy(file, tr)
|
||
if err != nil {
|
||
file.Close()
|
||
return
|
||
}
|
||
|
||
file.Close()
|
||
}
|
||
}
|
||
|
||
}
|