github.com/sagernet/sing-box@v1.9.0-rc.20/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/sagernet/sing-box/adapter"
    13  	"github.com/sagernet/sing-box/common/geoip"
    14  	"github.com/sagernet/sing-box/common/geosite"
    15  	C "github.com/sagernet/sing-box/constant"
    16  	E "github.com/sagernet/sing/common/exceptions"
    17  	M "github.com/sagernet/sing/common/metadata"
    18  	"github.com/sagernet/sing/common/rw"
    19  	"github.com/sagernet/sing/service/filemanager"
    20  )
    21  
    22  func (r *Router) GeoIPReader() *geoip.Reader {
    23  	return r.geoIPReader
    24  }
    25  
    26  func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
    27  	rule, cached := r.geositeCache[code]
    28  	if cached {
    29  		return rule, nil
    30  	}
    31  	items, err := r.geositeReader.Read(code)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	r.geositeCache[code] = rule
    40  	return rule, nil
    41  }
    42  
    43  func (r *Router) prepareGeoIPDatabase() error {
    44  	var geoPath string
    45  	if r.geoIPOptions.Path != "" {
    46  		geoPath = r.geoIPOptions.Path
    47  	} else {
    48  		geoPath = "geoip.db"
    49  		if foundPath, loaded := C.FindPath(geoPath); loaded {
    50  			geoPath = foundPath
    51  		}
    52  	}
    53  	if !rw.FileExists(geoPath) {
    54  		geoPath = filemanager.BasePath(r.ctx, geoPath)
    55  	}
    56  	if stat, err := os.Stat(geoPath); err == nil {
    57  		if stat.IsDir() {
    58  			return E.New("geoip path is a directory: ", geoPath)
    59  		}
    60  		if stat.Size() == 0 {
    61  			os.Remove(geoPath)
    62  		}
    63  	}
    64  	if !rw.FileExists(geoPath) {
    65  		r.logger.Warn("geoip database not exists: ", geoPath)
    66  		var err error
    67  		for attempts := 0; attempts < 3; attempts++ {
    68  			err = r.downloadGeoIPDatabase(geoPath)
    69  			if err == nil {
    70  				break
    71  			}
    72  			r.logger.Error("download geoip database: ", err)
    73  			os.Remove(geoPath)
    74  			// time.Sleep(10 * time.Second)
    75  		}
    76  		if err != nil {
    77  			return err
    78  		}
    79  	}
    80  	geoReader, codes, err := geoip.Open(geoPath)
    81  	if err != nil {
    82  		return E.Cause(err, "open geoip database")
    83  	}
    84  	r.logger.Info("loaded geoip database: ", len(codes), " codes")
    85  	r.geoIPReader = geoReader
    86  	return nil
    87  }
    88  
    89  func (r *Router) prepareGeositeDatabase() error {
    90  	var geoPath string
    91  	if r.geositeOptions.Path != "" {
    92  		geoPath = r.geositeOptions.Path
    93  	} else {
    94  		geoPath = "geosite.db"
    95  		if foundPath, loaded := C.FindPath(geoPath); loaded {
    96  			geoPath = foundPath
    97  		}
    98  	}
    99  	if !rw.FileExists(geoPath) {
   100  		geoPath = filemanager.BasePath(r.ctx, geoPath)
   101  	}
   102  	if stat, err := os.Stat(geoPath); err == nil {
   103  		if stat.IsDir() {
   104  			return E.New("geoip path is a directory: ", geoPath)
   105  		}
   106  		if stat.Size() == 0 {
   107  			os.Remove(geoPath)
   108  		}
   109  	}
   110  	if !rw.FileExists(geoPath) {
   111  		r.logger.Warn("geosite database not exists: ", geoPath)
   112  		var err error
   113  		for attempts := 0; attempts < 3; attempts++ {
   114  			err = r.downloadGeositeDatabase(geoPath)
   115  			if err == nil {
   116  				break
   117  			}
   118  			r.logger.Error("download geosite database: ", err)
   119  			os.Remove(geoPath)
   120  		}
   121  		if err != nil {
   122  			return err
   123  		}
   124  	}
   125  	geoReader, codes, err := geosite.Open(geoPath)
   126  	if err == nil {
   127  		r.logger.Info("loaded geosite database: ", len(codes), " codes")
   128  		r.geositeReader = geoReader
   129  	} else {
   130  		return E.Cause(err, "open geosite database")
   131  	}
   132  	return nil
   133  }
   134  
   135  func (r *Router) downloadGeoIPDatabase(savePath string) error {
   136  	var downloadURL string
   137  	if r.geoIPOptions.DownloadURL != "" {
   138  		downloadURL = r.geoIPOptions.DownloadURL
   139  	} else {
   140  		downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
   141  	}
   142  	r.logger.Info("downloading geoip database")
   143  	var detour adapter.Outbound
   144  	if r.geoIPOptions.DownloadDetour != "" {
   145  		outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
   146  		if !loaded {
   147  			return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
   148  		}
   149  		detour = outbound
   150  	} else {
   151  		detour = r.defaultOutboundForConnection
   152  	}
   153  
   154  	if parentDir := filepath.Dir(savePath); parentDir != "" {
   155  		filemanager.MkdirAll(r.ctx, parentDir, 0o755)
   156  	}
   157  
   158  	httpClient := &http.Client{
   159  		Transport: &http.Transport{
   160  			ForceAttemptHTTP2:   true,
   161  			TLSHandshakeTimeout: 5 * time.Second,
   162  			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
   163  				return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
   164  			},
   165  		},
   166  	}
   167  	defer httpClient.CloseIdleConnections()
   168  	request, err := http.NewRequest("GET", downloadURL, nil)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	response, err := httpClient.Do(request.WithContext(r.ctx))
   173  	if err != nil {
   174  		return err
   175  	}
   176  	defer response.Body.Close()
   177  
   178  	saveFile, err := filemanager.Create(r.ctx, savePath)
   179  	if err != nil {
   180  		return E.Cause(err, "open output file: ", downloadURL)
   181  	}
   182  	_, err = io.Copy(saveFile, response.Body)
   183  	saveFile.Close()
   184  	if err != nil {
   185  		filemanager.Remove(r.ctx, savePath)
   186  	}
   187  	return err
   188  }
   189  
   190  func (r *Router) downloadGeositeDatabase(savePath string) error {
   191  	var downloadURL string
   192  	if r.geositeOptions.DownloadURL != "" {
   193  		downloadURL = r.geositeOptions.DownloadURL
   194  	} else {
   195  		downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
   196  	}
   197  	r.logger.Info("downloading geosite database")
   198  	var detour adapter.Outbound
   199  	if r.geositeOptions.DownloadDetour != "" {
   200  		outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
   201  		if !loaded {
   202  			return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
   203  		}
   204  		detour = outbound
   205  	} else {
   206  		detour = r.defaultOutboundForConnection
   207  	}
   208  
   209  	if parentDir := filepath.Dir(savePath); parentDir != "" {
   210  		filemanager.MkdirAll(r.ctx, parentDir, 0o755)
   211  	}
   212  
   213  	httpClient := &http.Client{
   214  		Transport: &http.Transport{
   215  			ForceAttemptHTTP2:   true,
   216  			TLSHandshakeTimeout: 5 * time.Second,
   217  			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
   218  				return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
   219  			},
   220  		},
   221  	}
   222  	defer httpClient.CloseIdleConnections()
   223  	request, err := http.NewRequest("GET", downloadURL, nil)
   224  	if err != nil {
   225  		return err
   226  	}
   227  	response, err := httpClient.Do(request.WithContext(r.ctx))
   228  	if err != nil {
   229  		return err
   230  	}
   231  	defer response.Body.Close()
   232  
   233  	saveFile, err := filemanager.Create(r.ctx, savePath)
   234  	if err != nil {
   235  		return E.Cause(err, "open output file: ", downloadURL)
   236  	}
   237  	_, err = io.Copy(saveFile, response.Body)
   238  	saveFile.Close()
   239  	if err != nil {
   240  		filemanager.Remove(r.ctx, savePath)
   241  	}
   242  	return err
   243  }