github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/route/router_geo_resources.go (about)

     1  package route
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net"
     7  	"net/http"
     8  	"os"
     9  	"path/filepath"
    10  	"time"
    11  
    12  	"github.com/inazumav/sing-box/adapter"
    13  	"github.com/inazumav/sing-box/common/geoip"
    14  	"github.com/inazumav/sing-box/common/geosite"
    15  	C "github.com/inazumav/sing-box/constant"
    16  	"github.com/inazumav/sing-box/option"
    17  	"github.com/sagernet/sing/common"
    18  	E "github.com/sagernet/sing/common/exceptions"
    19  	M "github.com/sagernet/sing/common/metadata"
    20  	"github.com/sagernet/sing/common/rw"
    21  	"github.com/sagernet/sing/service/filemanager"
    22  )
    23  
    24  func (r *Router) GeoIPReader() *geoip.Reader {
    25  	return r.geoIPReader
    26  }
    27  
    28  func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
    29  	rule, cached := r.geositeCache[code]
    30  	if cached {
    31  		return rule, nil
    32  	}
    33  	items, err := r.geositeReader.Read(code)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	r.geositeCache[code] = rule
    42  	return rule, nil
    43  }
    44  
    45  func (r *Router) prepareGeoIPDatabase() error {
    46  	var geoPath string
    47  	if r.geoIPOptions.Path != "" {
    48  		geoPath = r.geoIPOptions.Path
    49  	} else {
    50  		geoPath = "geoip.db"
    51  		if foundPath, loaded := C.FindPath(geoPath); loaded {
    52  			geoPath = foundPath
    53  		}
    54  	}
    55  	if !rw.FileExists(geoPath) {
    56  		geoPath = filemanager.BasePath(r.ctx, geoPath)
    57  	}
    58  	if stat, err := os.Stat(geoPath); err == nil {
    59  		if stat.IsDir() {
    60  			return E.New("geoip path is a directory: ", geoPath)
    61  		}
    62  		if stat.Size() == 0 {
    63  			os.Remove(geoPath)
    64  		}
    65  	}
    66  	if !rw.FileExists(geoPath) {
    67  		r.logger.Warn("geoip database not exists: ", geoPath)
    68  		var err error
    69  		for attempts := 0; attempts < 3; attempts++ {
    70  			err = r.downloadGeoIPDatabase(geoPath)
    71  			if err == nil {
    72  				break
    73  			}
    74  			r.logger.Error("download geoip database: ", err)
    75  			os.Remove(geoPath)
    76  			// time.Sleep(10 * time.Second)
    77  		}
    78  		if err != nil {
    79  			return err
    80  		}
    81  	}
    82  	geoReader, codes, err := geoip.Open(geoPath)
    83  	if err != nil {
    84  		return E.Cause(err, "open geoip database")
    85  	}
    86  	r.logger.Info("loaded geoip database: ", len(codes), " codes")
    87  	r.geoIPReader = geoReader
    88  	return nil
    89  }
    90  
    91  func (r *Router) prepareGeositeDatabase() error {
    92  	var geoPath string
    93  	if r.geositeOptions.Path != "" {
    94  		geoPath = r.geositeOptions.Path
    95  	} else {
    96  		geoPath = "geosite.db"
    97  		if foundPath, loaded := C.FindPath(geoPath); loaded {
    98  			geoPath = foundPath
    99  		}
   100  	}
   101  	if !rw.FileExists(geoPath) {
   102  		geoPath = filemanager.BasePath(r.ctx, geoPath)
   103  	}
   104  	if stat, err := os.Stat(geoPath); err == nil {
   105  		if stat.IsDir() {
   106  			return E.New("geoip path is a directory: ", geoPath)
   107  		}
   108  		if stat.Size() == 0 {
   109  			os.Remove(geoPath)
   110  		}
   111  	}
   112  	if !rw.FileExists(geoPath) {
   113  		r.logger.Warn("geosite database not exists: ", geoPath)
   114  		var err error
   115  		for attempts := 0; attempts < 3; attempts++ {
   116  			err = r.downloadGeositeDatabase(geoPath)
   117  			if err == nil {
   118  				break
   119  			}
   120  			r.logger.Error("download geosite database: ", err)
   121  			os.Remove(geoPath)
   122  		}
   123  		if err != nil {
   124  			return err
   125  		}
   126  	}
   127  	geoReader, codes, err := geosite.Open(geoPath)
   128  	if err == nil {
   129  		r.logger.Info("loaded geosite database: ", len(codes), " codes")
   130  		r.geositeReader = geoReader
   131  	} else {
   132  		return E.Cause(err, "open geosite database")
   133  	}
   134  	return nil
   135  }
   136  
   137  func (r *Router) downloadGeoIPDatabase(savePath string) error {
   138  	var downloadURL string
   139  	if r.geoIPOptions.DownloadURL != "" {
   140  		downloadURL = r.geoIPOptions.DownloadURL
   141  	} else {
   142  		downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
   143  	}
   144  	r.logger.Info("downloading geoip database")
   145  	var detour adapter.Outbound
   146  	if r.geoIPOptions.DownloadDetour != "" {
   147  		outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
   148  		if !loaded {
   149  			return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
   150  		}
   151  		detour = outbound
   152  	} else {
   153  		detour = r.defaultOutboundForConnection
   154  	}
   155  
   156  	if parentDir := filepath.Dir(savePath); parentDir != "" {
   157  		filemanager.MkdirAll(r.ctx, parentDir, 0o755)
   158  	}
   159  
   160  	saveFile, err := filemanager.Create(r.ctx, savePath)
   161  	if err != nil {
   162  		return E.Cause(err, "open output file: ", downloadURL)
   163  	}
   164  	defer saveFile.Close()
   165  
   166  	httpClient := &http.Client{
   167  		Transport: &http.Transport{
   168  			ForceAttemptHTTP2:   true,
   169  			TLSHandshakeTimeout: 5 * time.Second,
   170  			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
   171  				return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
   172  			},
   173  		},
   174  	}
   175  	defer httpClient.CloseIdleConnections()
   176  	response, err := httpClient.Get(downloadURL)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	defer response.Body.Close()
   181  	_, err = io.Copy(saveFile, response.Body)
   182  	return err
   183  }
   184  
   185  func (r *Router) downloadGeositeDatabase(savePath string) error {
   186  	var downloadURL string
   187  	if r.geositeOptions.DownloadURL != "" {
   188  		downloadURL = r.geositeOptions.DownloadURL
   189  	} else {
   190  		downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
   191  	}
   192  	r.logger.Info("downloading geosite database")
   193  	var detour adapter.Outbound
   194  	if r.geositeOptions.DownloadDetour != "" {
   195  		outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
   196  		if !loaded {
   197  			return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
   198  		}
   199  		detour = outbound
   200  	} else {
   201  		detour = r.defaultOutboundForConnection
   202  	}
   203  
   204  	if parentDir := filepath.Dir(savePath); parentDir != "" {
   205  		filemanager.MkdirAll(r.ctx, parentDir, 0o755)
   206  	}
   207  
   208  	saveFile, err := filemanager.Create(r.ctx, savePath)
   209  	if err != nil {
   210  		return E.Cause(err, "open output file: ", downloadURL)
   211  	}
   212  	defer saveFile.Close()
   213  
   214  	httpClient := &http.Client{
   215  		Transport: &http.Transport{
   216  			ForceAttemptHTTP2:   true,
   217  			TLSHandshakeTimeout: 5 * time.Second,
   218  			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
   219  				return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
   220  			},
   221  		},
   222  	}
   223  	defer httpClient.CloseIdleConnections()
   224  	response, err := httpClient.Get(downloadURL)
   225  	if err != nil {
   226  		return err
   227  	}
   228  	defer response.Body.Close()
   229  	_, err = io.Copy(saveFile, response.Body)
   230  	return err
   231  }
   232  
   233  func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
   234  	for _, rule := range rules {
   235  		switch rule.Type {
   236  		case C.RuleTypeDefault:
   237  			if cond(rule.DefaultOptions) {
   238  				return true
   239  			}
   240  		case C.RuleTypeLogical:
   241  			for _, subRule := range rule.LogicalOptions.Rules {
   242  				if cond(subRule) {
   243  					return true
   244  				}
   245  			}
   246  		}
   247  	}
   248  	return false
   249  }
   250  
   251  func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
   252  	for _, rule := range rules {
   253  		switch rule.Type {
   254  		case C.RuleTypeDefault:
   255  			if cond(rule.DefaultOptions) {
   256  				return true
   257  			}
   258  		case C.RuleTypeLogical:
   259  			for _, subRule := range rule.LogicalOptions.Rules {
   260  				if cond(subRule) {
   261  					return true
   262  				}
   263  			}
   264  		}
   265  	}
   266  	return false
   267  }
   268  
   269  func isGeoIPRule(rule option.DefaultRule) bool {
   270  	return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
   271  }
   272  
   273  func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
   274  	return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
   275  }
   276  
   277  func isGeositeRule(rule option.DefaultRule) bool {
   278  	return len(rule.Geosite) > 0
   279  }
   280  
   281  func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
   282  	return len(rule.Geosite) > 0
   283  }
   284  
   285  func isProcessRule(rule option.DefaultRule) bool {
   286  	return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
   287  }
   288  
   289  func isProcessDNSRule(rule option.DefaultDNSRule) bool {
   290  	return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
   291  }
   292  
   293  func notPrivateNode(code string) bool {
   294  	return code != "private"
   295  }