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 }