2023-06-07 20:28:21 +08:00
|
|
|
package route
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
|
|
"github.com/sagernet/sing-box/common/geoip"
|
|
|
|
"github.com/sagernet/sing-box/common/geosite"
|
|
|
|
C "github.com/sagernet/sing-box/constant"
|
2024-10-18 09:58:03 +08:00
|
|
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
2024-10-21 23:38:34 +08:00
|
|
|
R "github.com/sagernet/sing-box/route/rule"
|
2023-06-07 20:28:21 +08:00
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
|
|
"github.com/sagernet/sing/common/rw"
|
2023-04-21 17:29:00 +08:00
|
|
|
"github.com/sagernet/sing/service/filemanager"
|
2023-06-07 20:28:21 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
func (r *Router) GeoIPReader() *geoip.Reader {
|
|
|
|
return r.geoIPReader
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
|
|
|
rule, cached := r.geositeCache[code]
|
|
|
|
if cached {
|
|
|
|
return rule, nil
|
|
|
|
}
|
|
|
|
items, err := r.geositeReader.Read(code)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-11-10 12:11:21 +08:00
|
|
|
rule, err = R.NewDefaultRule(r.ctx, nil, geosite.Compile(items))
|
2023-06-07 20:28:21 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r.geositeCache[code] = rule
|
|
|
|
return rule, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Router) prepareGeoIPDatabase() error {
|
2024-10-18 09:58:03 +08:00
|
|
|
deprecated.Report(r.ctx, deprecated.OptionGEOIP)
|
2023-06-07 20:28:21 +08:00
|
|
|
var geoPath string
|
|
|
|
if r.geoIPOptions.Path != "" {
|
|
|
|
geoPath = r.geoIPOptions.Path
|
|
|
|
} else {
|
|
|
|
geoPath = "geoip.db"
|
|
|
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
|
|
geoPath = foundPath
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 09:49:15 +08:00
|
|
|
if !rw.IsFile(geoPath) {
|
2023-07-19 21:01:06 +08:00
|
|
|
geoPath = filemanager.BasePath(r.ctx, geoPath)
|
|
|
|
}
|
|
|
|
if stat, err := os.Stat(geoPath); err == nil {
|
|
|
|
if stat.IsDir() {
|
|
|
|
return E.New("geoip path is a directory: ", geoPath)
|
|
|
|
}
|
|
|
|
if stat.Size() == 0 {
|
|
|
|
os.Remove(geoPath)
|
2023-06-07 20:28:21 +08:00
|
|
|
}
|
|
|
|
}
|
2024-06-24 09:49:15 +08:00
|
|
|
if !rw.IsFile(geoPath) {
|
2023-06-07 20:28:21 +08:00
|
|
|
r.logger.Warn("geoip database not exists: ", geoPath)
|
|
|
|
var err error
|
|
|
|
for attempts := 0; attempts < 3; attempts++ {
|
|
|
|
err = r.downloadGeoIPDatabase(geoPath)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
r.logger.Error("download geoip database: ", err)
|
|
|
|
os.Remove(geoPath)
|
|
|
|
// time.Sleep(10 * time.Second)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
geoReader, codes, err := geoip.Open(geoPath)
|
|
|
|
if err != nil {
|
|
|
|
return E.Cause(err, "open geoip database")
|
|
|
|
}
|
|
|
|
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
|
|
|
r.geoIPReader = geoReader
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Router) prepareGeositeDatabase() error {
|
2024-10-18 09:58:03 +08:00
|
|
|
deprecated.Report(r.ctx, deprecated.OptionGEOSITE)
|
2023-06-07 20:28:21 +08:00
|
|
|
var geoPath string
|
|
|
|
if r.geositeOptions.Path != "" {
|
|
|
|
geoPath = r.geositeOptions.Path
|
|
|
|
} else {
|
|
|
|
geoPath = "geosite.db"
|
|
|
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
|
|
geoPath = foundPath
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 09:49:15 +08:00
|
|
|
if !rw.IsFile(geoPath) {
|
2023-07-19 21:01:06 +08:00
|
|
|
geoPath = filemanager.BasePath(r.ctx, geoPath)
|
|
|
|
}
|
|
|
|
if stat, err := os.Stat(geoPath); err == nil {
|
|
|
|
if stat.IsDir() {
|
|
|
|
return E.New("geoip path is a directory: ", geoPath)
|
|
|
|
}
|
|
|
|
if stat.Size() == 0 {
|
|
|
|
os.Remove(geoPath)
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 09:49:15 +08:00
|
|
|
if !rw.IsFile(geoPath) {
|
2023-06-07 20:28:21 +08:00
|
|
|
r.logger.Warn("geosite database not exists: ", geoPath)
|
|
|
|
var err error
|
|
|
|
for attempts := 0; attempts < 3; attempts++ {
|
|
|
|
err = r.downloadGeositeDatabase(geoPath)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
r.logger.Error("download geosite database: ", err)
|
|
|
|
os.Remove(geoPath)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
geoReader, codes, err := geosite.Open(geoPath)
|
|
|
|
if err == nil {
|
|
|
|
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
|
|
|
r.geositeReader = geoReader
|
|
|
|
} else {
|
|
|
|
return E.Cause(err, "open geosite database")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
|
|
|
var downloadURL string
|
|
|
|
if r.geoIPOptions.DownloadURL != "" {
|
|
|
|
downloadURL = r.geoIPOptions.DownloadURL
|
|
|
|
} else {
|
|
|
|
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
|
|
|
}
|
|
|
|
r.logger.Info("downloading geoip database")
|
|
|
|
var detour adapter.Outbound
|
|
|
|
if r.geoIPOptions.DownloadDetour != "" {
|
2024-11-20 11:32:02 +08:00
|
|
|
outbound, loaded := r.outbound.Outbound(r.geoIPOptions.DownloadDetour)
|
2023-06-07 20:28:21 +08:00
|
|
|
if !loaded {
|
|
|
|
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
|
|
|
}
|
|
|
|
detour = outbound
|
|
|
|
} else {
|
2024-11-20 11:32:02 +08:00
|
|
|
detour = r.outbound.Default()
|
2023-06-07 20:28:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
2023-04-21 17:29:00 +08:00
|
|
|
filemanager.MkdirAll(r.ctx, parentDir, 0o755)
|
2023-06-07 20:28:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
httpClient := &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
ForceAttemptHTTP2: true,
|
2024-10-30 13:09:05 +08:00
|
|
|
TLSHandshakeTimeout: C.TCPTimeout,
|
2023-06-07 20:28:21 +08:00
|
|
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
defer httpClient.CloseIdleConnections()
|
2023-09-23 12:41:51 +08:00
|
|
|
request, err := http.NewRequest("GET", downloadURL, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
response, err := httpClient.Do(request.WithContext(r.ctx))
|
2023-06-07 20:28:21 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer response.Body.Close()
|
2023-11-05 15:31:42 +08:00
|
|
|
|
|
|
|
saveFile, err := filemanager.Create(r.ctx, savePath)
|
|
|
|
if err != nil {
|
|
|
|
return E.Cause(err, "open output file: ", downloadURL)
|
|
|
|
}
|
2023-06-07 20:28:21 +08:00
|
|
|
_, err = io.Copy(saveFile, response.Body)
|
2023-11-05 15:31:42 +08:00
|
|
|
saveFile.Close()
|
|
|
|
if err != nil {
|
|
|
|
filemanager.Remove(r.ctx, savePath)
|
|
|
|
}
|
2023-06-07 20:28:21 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
|
|
|
var downloadURL string
|
|
|
|
if r.geositeOptions.DownloadURL != "" {
|
|
|
|
downloadURL = r.geositeOptions.DownloadURL
|
|
|
|
} else {
|
|
|
|
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
|
|
|
}
|
|
|
|
r.logger.Info("downloading geosite database")
|
|
|
|
var detour adapter.Outbound
|
|
|
|
if r.geositeOptions.DownloadDetour != "" {
|
2024-11-20 11:32:02 +08:00
|
|
|
outbound, loaded := r.outbound.Outbound(r.geositeOptions.DownloadDetour)
|
2023-06-07 20:28:21 +08:00
|
|
|
if !loaded {
|
|
|
|
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
|
|
|
}
|
|
|
|
detour = outbound
|
|
|
|
} else {
|
2024-11-20 11:32:02 +08:00
|
|
|
detour = r.outbound.Default()
|
2023-06-07 20:28:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
2023-04-21 17:29:00 +08:00
|
|
|
filemanager.MkdirAll(r.ctx, parentDir, 0o755)
|
2023-06-07 20:28:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
httpClient := &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
ForceAttemptHTTP2: true,
|
2024-10-30 13:09:05 +08:00
|
|
|
TLSHandshakeTimeout: C.TCPTimeout,
|
2023-06-07 20:28:21 +08:00
|
|
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
defer httpClient.CloseIdleConnections()
|
2023-09-23 12:41:51 +08:00
|
|
|
request, err := http.NewRequest("GET", downloadURL, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
response, err := httpClient.Do(request.WithContext(r.ctx))
|
2023-06-07 20:28:21 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer response.Body.Close()
|
2023-11-05 15:31:42 +08:00
|
|
|
|
|
|
|
saveFile, err := filemanager.Create(r.ctx, savePath)
|
|
|
|
if err != nil {
|
|
|
|
return E.Cause(err, "open output file: ", downloadURL)
|
|
|
|
}
|
2023-06-07 20:28:21 +08:00
|
|
|
_, err = io.Copy(saveFile, response.Body)
|
2023-11-05 15:31:42 +08:00
|
|
|
saveFile.Close()
|
|
|
|
if err != nil {
|
|
|
|
filemanager.Remove(r.ctx, savePath)
|
|
|
|
}
|
2023-06-07 20:28:21 +08:00
|
|
|
return err
|
|
|
|
}
|