Clash.Meta/component/updater/update_geo.go

281 lines
7.0 KiB
Go
Raw Normal View History

2024-05-17 11:49:09 +08:00
package updater
import (
2024-09-09 10:05:38 +08:00
"context"
2024-05-17 11:49:09 +08:00
"errors"
"fmt"
2024-05-17 11:49:09 +08:00
"os"
"runtime"
2024-05-17 11:49:09 +08:00
"time"
2023-04-10 21:03:31 +08:00
2024-05-17 11:49:09 +08:00
"github.com/metacubex/mihomo/common/atomic"
2024-09-09 10:05:38 +08:00
"github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/common/utils"
2023-11-03 21:01:45 +08:00
"github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard"
2024-02-21 21:56:20 +08:00
"github.com/metacubex/mihomo/component/mmdb"
2024-09-22 13:57:57 +08:00
"github.com/metacubex/mihomo/component/resource"
2023-11-03 21:01:45 +08:00
C "github.com/metacubex/mihomo/constant"
2024-05-17 11:49:09 +08:00
"github.com/metacubex/mihomo/log"
2023-04-10 21:03:31 +08:00
2023-07-14 22:28:24 +08:00
"github.com/oschwald/maxminddb-golang"
)
2024-05-17 11:49:09 +08:00
var (
2024-09-09 16:08:48 +08:00
autoUpdate bool
updateInterval int
2024-09-09 10:05:38 +08:00
updatingGeo atomic.Bool
2024-05-17 11:49:09 +08:00
)
2024-09-09 16:08:48 +08:00
func GeoAutoUpdate() bool {
return autoUpdate
}
func GeoUpdateInterval() int {
return updateInterval
}
func SetGeoAutoUpdate(newAutoUpdate bool) {
autoUpdate = newAutoUpdate
}
func SetGeoUpdateInterval(newGeoUpdateInterval int) {
updateInterval = newGeoUpdateInterval
}
2024-09-09 10:05:38 +08:00
func UpdateMMDB() (err error) {
2024-09-22 13:57:57 +08:00
vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout)
var oldHash utils.HashType
2024-09-22 13:57:57 +08:00
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
2024-09-22 13:57:57 +08:00
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
2022-06-03 16:50:05 +08:00
if err != nil {
2024-09-09 10:05:38 +08:00
return fmt.Errorf("can't download MMDB database file: %w", err)
2022-06-03 16:50:05 +08:00
}
2024-09-22 13:57:57 +08:00
if oldHash.Equal(hash) { // same hash, ignored
return nil
}
if len(data) == 0 {
return fmt.Errorf("can't download MMDB database file: no data")
}
2024-09-09 10:05:38 +08:00
instance, err := maxminddb.FromBytes(data)
if err != nil {
return fmt.Errorf("invalid MMDB database file: %s", err)
}
_ = instance.Close()
2024-09-22 13:57:57 +08:00
defer mmdb.ReloadIP()
2024-09-09 10:05:38 +08:00
mmdb.IPInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
2024-09-22 13:57:57 +08:00
if err = vehicle.Write(data); err != nil {
2024-09-09 10:05:38 +08:00
return fmt.Errorf("can't save MMDB database file: %w", err)
}
return nil
}
2024-02-21 21:56:20 +08:00
2024-09-09 10:05:38 +08:00
func UpdateASN() (err error) {
2024-09-22 13:57:57 +08:00
vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout)
var oldHash utils.HashType
2024-09-22 13:57:57 +08:00
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
2024-09-22 13:57:57 +08:00
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
2024-09-09 10:05:38 +08:00
if err != nil {
return fmt.Errorf("can't download ASN database file: %w", err)
}
2024-09-22 13:57:57 +08:00
if oldHash.Equal(hash) { // same hash, ignored
return nil
}
if len(data) == 0 {
return fmt.Errorf("can't download ASN database file: no data")
}
2024-09-09 10:05:38 +08:00
instance, err := maxminddb.FromBytes(data)
if err != nil {
return fmt.Errorf("invalid ASN database file: %s", err)
}
_ = instance.Close()
2024-03-12 03:14:25 +08:00
2024-09-22 13:57:57 +08:00
defer mmdb.ReloadASN()
2024-09-09 10:05:38 +08:00
mmdb.ASNInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
2024-09-22 13:57:57 +08:00
if err = vehicle.Write(data); err != nil {
2024-09-09 10:05:38 +08:00
return fmt.Errorf("can't save ASN database file: %w", err)
}
return nil
}
2024-03-12 03:14:25 +08:00
2024-09-09 10:05:38 +08:00
func UpdateGeoIp() (err error) {
geoLoader, err := geodata.GetGeoDataLoader("standard")
2024-09-22 13:57:57 +08:00
vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout)
var oldHash utils.HashType
2024-09-22 13:57:57 +08:00
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
2024-09-22 13:57:57 +08:00
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
2024-09-09 10:05:38 +08:00
if err != nil {
return fmt.Errorf("can't download GeoIP database file: %w", err)
}
2024-09-22 13:57:57 +08:00
if oldHash.Equal(hash) { // same hash, ignored
return nil
}
if len(data) == 0 {
return fmt.Errorf("can't download GeoIP database file: no data")
}
2024-09-09 10:05:38 +08:00
if _, err = geoLoader.LoadIPByBytes(data, "cn"); err != nil {
return fmt.Errorf("invalid GeoIP database file: %s", err)
2024-03-12 03:14:25 +08:00
}
2024-09-22 13:57:57 +08:00
defer geodata.ClearGeoIPCache()
if err = vehicle.Write(data); err != nil {
2024-09-09 10:05:38 +08:00
return fmt.Errorf("can't save GeoIP database file: %w", err)
}
return nil
}
2024-03-12 03:14:25 +08:00
2024-09-09 10:05:38 +08:00
func UpdateGeoSite() (err error) {
geoLoader, err := geodata.GetGeoDataLoader("standard")
2024-09-22 13:57:57 +08:00
vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout)
var oldHash utils.HashType
2024-09-22 13:57:57 +08:00
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
2024-09-22 13:57:57 +08:00
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
2022-06-03 16:50:05 +08:00
if err != nil {
return fmt.Errorf("can't download GeoSite database file: %w", err)
}
2024-09-22 13:57:57 +08:00
if oldHash.Equal(hash) { // same hash, ignored
return nil
}
if len(data) == 0 {
return fmt.Errorf("can't download GeoSite database file: no data")
}
2022-06-03 16:50:05 +08:00
if _, err = geoLoader.LoadSiteByBytes(data, "cn"); err != nil {
return fmt.Errorf("invalid GeoSite database file: %s", err)
}
2024-09-22 13:57:57 +08:00
defer geodata.ClearGeoSiteCache()
if err = vehicle.Write(data); err != nil {
2022-06-03 16:50:05 +08:00
return fmt.Errorf("can't save GeoSite database file: %w", err)
}
2024-09-09 10:05:38 +08:00
return nil
}
func updateGeoDatabases() error {
defer runtime.GC()
b, _ := batch.New[interface{}](context.Background())
2024-09-09 16:08:48 +08:00
if geodata.GeoIpEnable() {
if geodata.GeodataMode() {
b.Go("UpdateGeoIp", func() (_ interface{}, err error) {
err = UpdateGeoIp()
return
})
} else {
b.Go("UpdateMMDB", func() (_ interface{}, err error) {
err = UpdateMMDB()
return
})
}
2024-09-09 10:05:38 +08:00
}
2024-09-09 16:08:48 +08:00
if geodata.ASNEnable() {
2024-09-09 10:05:38 +08:00
b.Go("UpdateASN", func() (_ interface{}, err error) {
err = UpdateASN()
return
})
}
2024-09-09 16:08:48 +08:00
if geodata.GeoSiteEnable() {
b.Go("UpdateGeoSite", func() (_ interface{}, err error) {
err = UpdateGeoSite()
return
})
}
2024-09-09 10:05:38 +08:00
if e := b.Wait(); e != nil {
return e.Err
}
2022-06-03 16:50:05 +08:00
return nil
}
2024-05-17 11:49:09 +08:00
2024-05-19 17:30:39 +08:00
var ErrGetDatabaseUpdateSkip = errors.New("GEO database is updating, skip")
func UpdateGeoDatabases() error {
2024-05-17 11:49:09 +08:00
log.Infoln("[GEO] Start updating GEO database")
2024-09-09 10:05:38 +08:00
if updatingGeo.Load() {
2024-05-19 17:30:39 +08:00
return ErrGetDatabaseUpdateSkip
2024-05-17 11:49:09 +08:00
}
2024-09-09 10:05:38 +08:00
updatingGeo.Store(true)
defer updatingGeo.Store(false)
2024-05-17 11:49:09 +08:00
log.Infoln("[GEO] Updating GEO database")
if err := updateGeoDatabases(); err != nil {
log.Errorln("[GEO] update GEO database error: %s", err.Error())
return err
}
return nil
}
func getUpdateTime() (err error, time time.Time) {
var fileInfo os.FileInfo
2024-09-09 16:08:48 +08:00
if geodata.GeodataMode() {
2024-05-17 11:49:09 +08:00
fileInfo, err = os.Stat(C.Path.GeoIP())
if err != nil {
return err, time
}
} else {
fileInfo, err = os.Stat(C.Path.MMDB())
if err != nil {
return err, time
}
}
return nil, fileInfo.ModTime()
}
func RegisterGeoUpdater() {
2024-09-09 16:08:48 +08:00
if updateInterval <= 0 {
log.Errorln("[GEO] Invalid update interval: %d", updateInterval)
2024-05-17 11:49:09 +08:00
return
}
go func() {
2024-09-09 16:08:48 +08:00
ticker := time.NewTicker(time.Duration(updateInterval) * time.Hour)
2024-05-19 17:30:39 +08:00
defer ticker.Stop()
2024-05-17 11:49:09 +08:00
err, lastUpdate := getUpdateTime()
if err != nil {
log.Errorln("[GEO] Get GEO database update time error: %s", err.Error())
return
}
log.Infoln("[GEO] last update time %s", lastUpdate)
2024-09-09 16:08:48 +08:00
if lastUpdate.Add(time.Duration(updateInterval) * time.Hour).Before(time.Now()) {
log.Infoln("[GEO] Database has not been updated for %v, update now", time.Duration(updateInterval)*time.Hour)
2024-05-19 17:30:39 +08:00
if err := UpdateGeoDatabases(); err != nil {
2024-05-17 11:49:09 +08:00
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
return
}
}
for range ticker.C {
2024-09-09 16:08:48 +08:00
log.Infoln("[GEO] updating database every %d hours", updateInterval)
2024-05-19 17:30:39 +08:00
if err := UpdateGeoDatabases(); err != nil {
2024-05-17 11:49:09 +08:00
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
}
}
}()
}