Skip to content

Commit

Permalink
feat: add IP-ASN rule
Browse files Browse the repository at this point in the history
  • Loading branch information
xishang0128 committed Mar 11, 2024
1 parent 7ad37ca commit 99b6ffa
Show file tree
Hide file tree
Showing 15 changed files with 248 additions and 33 deletions.
33 changes: 30 additions & 3 deletions component/geodata/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import (
"github.com/metacubex/mihomo/log"
)

var initGeoSite bool
var initGeoIP int
var (
initGeoSite bool
initGeoIP int
initASN bool
)

func InitGeoSite() error {
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
Expand Down Expand Up @@ -113,7 +116,7 @@ func InitGeoIP() error {
}

if initGeoIP != 2 {
if !mmdb.Verify() {
if !mmdb.Verify(C.Path.MMDB()) {
log.Warnln("MMDB invalid, remove and download")
if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
Expand All @@ -126,3 +129,27 @@ func InitGeoIP() error {
}
return nil
}

func InitASN() error {
if _, err := os.Stat(C.Path.ASN()); os.IsNotExist(err) {
log.Infoln("Can't find ASN.mmdb, start download")
if err := mmdb.DownloadASN(C.Path.ASN()); err != nil {
return fmt.Errorf("can't download ASN.mmdb: %s", err.Error())
}
log.Infoln("Download ASN.mmdb finish")
initASN = false
}
if !initASN {
if !mmdb.Verify(C.Path.ASN()) {
log.Warnln("ASN invalid, remove and download")
if err := os.Remove(C.Path.ASN()); err != nil {
return fmt.Errorf("can't remove invalid ASN: %s", err.Error())
}
if err := mmdb.DownloadASN(C.Path.ASN()); err != nil {
return fmt.Errorf("can't download ASN: %s", err.Error())
}
}
initASN = true
}
return nil
}
75 changes: 57 additions & 18 deletions component/mmdb/mmdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,56 +25,58 @@ const (
)

var (
reader Reader
once sync.Once
IPreader IPReader
ASNreader ASNReader
IPonce sync.Once
ASNonce sync.Once
)

func LoadFromBytes(buffer []byte) {
once.Do(func() {
IPonce.Do(func() {
mmdb, err := maxminddb.FromBytes(buffer)
if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error())
}
reader = Reader{Reader: mmdb}
IPreader = IPReader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType {
case "sing-geoip":
reader.databaseType = typeSing
IPreader.databaseType = typeSing
case "Meta-geoip0":
reader.databaseType = typeMetaV0
IPreader.databaseType = typeMetaV0
default:
reader.databaseType = typeMaxmind
IPreader.databaseType = typeMaxmind
}
})
}

func Verify() bool {
instance, err := maxminddb.Open(C.Path.MMDB())
func Verify(path string) bool {
instance, err := maxminddb.Open(path)
if err == nil {
instance.Close()
}
return err == nil
}

func Instance() Reader {
once.Do(func() {
func IPInstance() IPReader {
IPonce.Do(func() {
mmdbPath := C.Path.MMDB()
log.Infoln("Load MMDB file: %s", mmdbPath)
mmdb, err := maxminddb.Open(mmdbPath)
if err != nil {
log.Fatalln("Can't load MMDB: %s", err.Error())
}
reader = Reader{Reader: mmdb}
IPreader = IPReader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType {
case "sing-geoip":
reader.databaseType = typeSing
IPreader.databaseType = typeSing
case "Meta-geoip0":
reader.databaseType = typeMetaV0
IPreader.databaseType = typeMetaV0
default:
reader.databaseType = typeMaxmind
IPreader.databaseType = typeMaxmind
}
})

return reader
return IPreader
}

func DownloadMMDB(path string) (err error) {
Expand All @@ -96,6 +98,43 @@ func DownloadMMDB(path string) (err error) {
return err
}

func Reload() {
mihomoOnce.Reset(&once)
func ASNInstance() ASNReader {
ASNonce.Do(func() {
ASNPath := C.Path.ASN()
log.Infoln("Load ASN file: %s", ASNPath)
asn, err := maxminddb.Open(ASNPath)
if err != nil {
log.Fatalln("Can't load ASN: %s", err.Error())
}
ASNreader = ASNReader{Reader: asn}
})

return ASNreader
}

func DownloadASN(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return
}
defer resp.Body.Close()

f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)

return err
}

func ReloadIP() {
mihomoOnce.Reset(&IPonce)
}

func ReloadASN() {
mihomoOnce.Reset(&ASNonce)
}
10 changes: 5 additions & 5 deletions component/mmdb/patch_android.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ package mmdb
import "github.com/oschwald/maxminddb-golang"

func InstallOverride(override *maxminddb.Reader) {
newReader := Reader{Reader: override}
newReader := IPReader{Reader: override}
switch override.Metadata.DatabaseType {
case "sing-geoip":
reader.databaseType = typeSing
IPreader.databaseType = typeSing
case "Meta-geoip0":
reader.databaseType = typeMetaV0
IPreader.databaseType = typeMetaV0
default:
reader.databaseType = typeMaxmind
IPreader.databaseType = typeMaxmind
}
reader = newReader
IPreader = newReader
}
19 changes: 17 additions & 2 deletions component/mmdb/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@ type geoip2Country struct {
} `maxminddb:"country"`
}

type Reader struct {
type IPReader struct {
*maxminddb.Reader
databaseType
}

func (r Reader) LookupCode(ipAddress net.IP) []string {
type ASNReader struct {
*maxminddb.Reader
}

type ASNResult struct {
AutonomousSystemNumber uint32 `maxminddb:"autonomous_system_number"`
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
}

func (r IPReader) LookupCode(ipAddress net.IP) []string {
switch r.databaseType {
case typeMaxmind:
var country geoip2Country
Expand Down Expand Up @@ -56,3 +65,9 @@ func (r Reader) LookupCode(ipAddress net.IP) []string {
panic(fmt.Sprint("unknown geoip database type:", r.databaseType))
}
}

func (r ASNReader) LookupASN(ip net.IP) ASNResult {
var result ASNResult
r.Lookup(ip, &result)
return result
}
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ type RawConfig struct {
type GeoXUrl struct {
GeoIp string `yaml:"geoip" json:"geoip"`
Mmdb string `yaml:"mmdb" json:"mmdb"`
ASN string `yaml:"asn" json:"asn"`
GeoSite string `yaml:"geosite" json:"geosite"`
}

Expand Down Expand Up @@ -495,6 +496,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
},
GeoXUrl: GeoXUrl{
Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
ASN: "https://github.com/P3TERX/GeoLite.mmdb/releases/download/2024.03.10/GeoLite2-ASN.mmdb",
GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
},
Expand Down Expand Up @@ -620,6 +622,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
C.MmdbUrl = cfg.GeoXUrl.Mmdb
C.ASNUrl = cfg.GeoXUrl.ASN
C.GeodataMode = cfg.GeodataMode
C.UA = cfg.GlobalUA
if cfg.KeepAliveInterval != 0 {
Expand Down
23 changes: 21 additions & 2 deletions config/update_geo.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func UpdateGeoDatabases() error {
}

} else {
defer mmdb.Reload()
defer mmdb.ReloadIP()
data, err := downloadForBytes(C.MmdbUrl)
if err != nil {
return fmt.Errorf("can't download MMDB database file: %w", err)
Expand All @@ -46,12 +46,31 @@ func UpdateGeoDatabases() error {
}
_ = instance.Close()

mmdb.Instance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
mmdb.IPInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
if err = saveFile(data, C.Path.MMDB()); err != nil {
return fmt.Errorf("can't save MMDB database file: %w", err)
}
}

if C.ASNEnable {
defer mmdb.ReloadASN()
data, err := downloadForBytes(C.ASNUrl)
if err != nil {
return fmt.Errorf("can't download ASN database file: %w", err)
}

instance, err := maxminddb.FromBytes(data)
if err != nil {
return fmt.Errorf("invalid ASN database file: %s", err)
}
_ = instance.Close()

mmdb.ASNInstance().Reader.Close()
if err = saveFile(data, C.Path.ASN()); err != nil {
return fmt.Errorf("can't save ASN database file: %w", err)
}
}

data, err := downloadForBytes(C.GeoSiteUrl)
if err != nil {
return fmt.Errorf("can't download GeoSite database file: %w", err)
Expand Down
2 changes: 2 additions & 0 deletions constant/geodata.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package constant

var (
ASNEnable bool
GeodataMode bool
GeoAutoUpdate bool
GeoUpdateInterval int
GeoIpUrl string
MmdbUrl string
GeoSiteUrl string
ASNUrl string
)
3 changes: 2 additions & 1 deletion constant/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ type Metadata struct {
Type Type `json:"type"`
SrcIP netip.Addr `json:"sourceIP"`
DstIP netip.Addr `json:"destinationIP"`
DstGeoIP []string `json:"destinationGeoIP"` // can be nil if never queried, empty slice if got no result
DstGeoIP []string `json:"destinationGeoIP"` // can be nil if never queried, empty slice if got no result
DstIPASN string `json:"destinationIPASN"`
SrcPort uint16 `json:"sourcePort,string"` // `,string` is used to compatible with old version json output
DstPort uint16 `json:"destinationPort,string"` // `,string` is used to compatible with old version json output
InIP netip.Addr `json:"inboundIP"`
Expand Down
20 changes: 20 additions & 0 deletions constant/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Name = "mihomo"
var (
GeositeName = "GeoSite.dat"
GeoipName = "GeoIP.dat"
ASNName = "ASN.mmdb"
)

// Path is used to get the configuration path
Expand Down Expand Up @@ -112,6 +113,25 @@ func (p *path) MMDB() string {
return P.Join(p.homeDir, "geoip.metadb")
}

func (p *path) ASN() string {
files, err := os.ReadDir(p.homeDir)
if err != nil {
return ""
}
for _, fi := range files {
if fi.IsDir() {
// 目录则直接跳过
continue
} else {
if strings.EqualFold(fi.Name(), "ASN.mmdb") {
ASNName = fi.Name()
return P.Join(p.homeDir, fi.Name())
}
}
}
return P.Join(p.homeDir, ASNName)
}

func (p *path) OldCache() string {
return P.Join(p.homeDir, ".cache")
}
Expand Down
3 changes: 3 additions & 0 deletions constant/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
GEOSITE
GEOIP
IPCIDR
IPASN
SrcIPCIDR
IPSuffix
SrcIPSuffix
Expand Down Expand Up @@ -49,6 +50,8 @@ func (rt RuleType) String() string {
return "GeoIP"
case IPCIDR:
return "IPCIDR"
case IPASN:
return "IPASN"
case SrcIPCIDR:
return "SrcIPCIDR"
case IPSuffix:
Expand Down
2 changes: 1 addition & 1 deletion dns/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var geoIPMatcher *router.GeoIPMatcher

func (gf *geoipFilter) Match(ip netip.Addr) bool {
if !C.GeodataMode {
codes := mmdb.Instance().LookupCode(ip.AsSlice())
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
for _, code := range codes {
if !strings.EqualFold(code, gf.code) && !ip.IsPrivate() {
return true
Expand Down
Loading

0 comments on commit 99b6ffa

Please sign in to comment.