From 474a473181cc718094f8651bb6e786cbf3bb1a45 Mon Sep 17 00:00:00 2001 From: H1JK Date: Thu, 15 Feb 2024 20:05:39 +0800 Subject: [PATCH] Fix Meta-geoip convert --- convert/v2ray_ip.go | 92 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/convert/v2ray_ip.go b/convert/v2ray_ip.go index c1fd197..e15d587 100644 --- a/convert/v2ray_ip.go +++ b/convert/v2ray_ip.go @@ -1,8 +1,10 @@ package convert import ( + "fmt" "io" "net" + "net/netip" "strings" "github.com/metacubex/geo/encoding/v2raygeo" @@ -11,6 +13,8 @@ import ( "github.com/maxmind/mmdbwriter/inserter" "github.com/maxmind/mmdbwriter/mmdbtype" "github.com/sagernet/sing/common" + "golang.org/x/exp/constraints" + "golang.org/x/exp/slices" ) func V2RayIPToSing(geoipList []*v2raygeo.GeoIP, output io.Writer) error { @@ -62,33 +66,83 @@ func V2RayIPToMetaV0(geoipList []*v2raygeo.GeoIP, output io.Writer) error { return err } + included := make([]netip.Prefix, 0, 4*len(geoipList)) + codeMap := make(map[netip.Prefix][]string, 4*len(geoipList)) for _, geoipEntry := range geoipList { + code := strings.ToLower(geoipEntry.CountryCode) for _, cidrEntry := range geoipEntry.Cidr { - ipAddress := net.IP(cidrEntry.Ip) - if ip4 := ipAddress.To4(); ip4 != nil { - ipAddress = ip4 + addr, ok := netip.AddrFromSlice(cidrEntry.Ip) + if !ok { + return fmt.Errorf("bad IP address: %v", cidrEntry.Ip) } - ipNet := net.IPNet{ - IP: ipAddress, - Mask: net.CIDRMask(int(cidrEntry.Prefix), len(ipAddress)*8), + addr = addr.Unmap() + prefix := netip.PrefixFrom(addr, int(cidrEntry.Prefix)) + included = append(included, prefix) + codeMap[prefix] = append(codeMap[prefix], code) + } + } + included = common.Uniq(included) + slices.SortFunc(included, func(a, b netip.Prefix) int { + // sort in ascending order + return cmpCompare(a.Bits(), b.Bits()) + }) + + for _, prefix := range included { + ipAddress := net.IP(prefix.Addr().AsSlice()) + ipNet := net.IPNet{ + IP: ipAddress, + Mask: net.CIDRMask(prefix.Bits(), len(ipAddress)*8), + } + codes := codeMap[prefix] + _, record := writer.Get(ipAddress) + switch typedRecord := record.(type) { + case nil: + if len(codes) == 1 { + record = mmdbtype.String(codes[0]) + } else { + record = mmdbtype.Slice(common.Map(codes, func(it string) mmdbtype.DataType { + return mmdbtype.String(it) + })) } - _, record := writer.Get(ipAddress) - switch typedRecord := record.(type) { - case nil: - record = mmdbtype.String(strings.ToLower(geoipEntry.CountryCode)) - case mmdbtype.String: - record = mmdbtype.Slice{record, mmdbtype.String(strings.ToLower(geoipEntry.CountryCode))} - case mmdbtype.Slice: - record = append(typedRecord, mmdbtype.String(strings.ToLower(geoipEntry.CountryCode))) - default: - panic("bad record type") + case mmdbtype.String: + recordSlice := make(mmdbtype.Slice, 0, 1+len(codes)) + recordSlice = append(recordSlice, typedRecord) + for _, code := range codes { + recordSlice = append(recordSlice, mmdbtype.String(code)) } - err = writer.Insert(&ipNet, record) - if err != nil { - return err + recordSlice = common.Uniq(recordSlice) + if len(recordSlice) == 1 { + record = recordSlice[0] + } else { + record = recordSlice } + case mmdbtype.Slice: + recordSlice := typedRecord + for _, code := range codes { + recordSlice = append(recordSlice, mmdbtype.String(code)) + } + recordSlice = common.Uniq(recordSlice) + record = recordSlice + default: + panic("bad record type") + } + err = writer.Insert(&ipNet, record) + if err != nil { + return err } } return common.Error(writer.WriteTo(output)) } + +// cmpCompare is a copy of cmp.Compare from the Go 1.21 release. +// T cannot be float types. +func cmpCompare[T constraints.Ordered](x, y T) int { + if x < y { + return -1 + } + if x > y { + return +1 + } + return 0 +}