Files
v2ray_simple/netLayer/geosite.go

338 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
}
}
}