mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-05-05 03:38:40 +00:00
Refactor: GeoSite & GeoIP
This commit is contained in:
parent
8382b29922
commit
b11429eaee
54 changed files with 2110 additions and 1633 deletions
90
common/matcher/geoip/conf.go
Normal file
90
common/matcher/geoip/conf.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package geoip
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
)
|
||||
|
||||
var (
|
||||
FileCache = make(map[string][]byte)
|
||||
IPCache = make(map[string]*GeoIP)
|
||||
)
|
||||
|
||||
func LoadGeoIP(code string) ([]*CIDR, error) {
|
||||
return LoadIPFile("geoip.dat", code)
|
||||
}
|
||||
|
||||
func LoadIPFile(file, code string) ([]*CIDR, error) {
|
||||
index := file + ":" + code
|
||||
if IPCache[index] == nil {
|
||||
bs, err := loadFile(file)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load file: ", file).Base(err)
|
||||
}
|
||||
bs = find(bs, []byte(code))
|
||||
if bs == nil {
|
||||
return nil, newError("code not found in ", file, ": ", code)
|
||||
}
|
||||
var geoipdat GeoIP
|
||||
if err := proto.Unmarshal(bs, &geoipdat); err != nil {
|
||||
return nil, newError("error unmarshal IP in ", file, ": ", code).Base(err)
|
||||
}
|
||||
defer runtime.GC() // or debug.FreeOSMemory()
|
||||
return geoipdat.Cidr, nil // do not cache geoip
|
||||
IPCache[index] = &geoipdat
|
||||
}
|
||||
return IPCache[index].Cidr, nil
|
||||
}
|
||||
|
||||
func loadFile(file string) ([]byte, error) {
|
||||
if FileCache[file] == nil {
|
||||
bs, err := filesystem.ReadAsset(file)
|
||||
if err != nil {
|
||||
return nil, newError("failed to open file: ", file).Base(err)
|
||||
}
|
||||
if len(bs) == 0 {
|
||||
return nil, newError("empty file: ", file)
|
||||
}
|
||||
// Do not cache file, may save RAM when there
|
||||
// are many files, but consume CPU each time.
|
||||
return bs, nil
|
||||
FileCache[file] = bs
|
||||
}
|
||||
return FileCache[file], nil
|
||||
}
|
||||
|
||||
func find(data, code []byte) []byte {
|
||||
codeL := len(code)
|
||||
if codeL == 0 {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
dataL := len(data)
|
||||
if dataL < 2 {
|
||||
return nil
|
||||
}
|
||||
x, y := proto.DecodeVarint(data[1:])
|
||||
if x == 0 && y == 0 {
|
||||
return nil
|
||||
}
|
||||
headL, bodyL := 1+y, int(x)
|
||||
dataL -= headL
|
||||
if dataL < bodyL {
|
||||
return nil
|
||||
}
|
||||
data = data[headL:]
|
||||
if int(data[1]) == codeL {
|
||||
for i := 0; i < codeL && data[2+i] == code[i]; i++ {
|
||||
if i+1 == codeL {
|
||||
return data[:bodyL]
|
||||
}
|
||||
}
|
||||
}
|
||||
if dataL == bodyL {
|
||||
return nil
|
||||
}
|
||||
data = data[bodyL:]
|
||||
}
|
||||
}
|
40
common/matcher/geoip/crid.go
Normal file
40
common/matcher/geoip/crid.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package geoip
|
||||
|
||||
// CIDRList is an alias of []*CIDR to provide sort.Interface.
|
||||
type CIDRList []*CIDR
|
||||
|
||||
// Len implements sort.Interface.
|
||||
func (l *CIDRList) Len() int {
|
||||
return len(*l)
|
||||
}
|
||||
|
||||
// Less implements sort.Interface.
|
||||
func (l *CIDRList) Less(i int, j int) bool {
|
||||
ci := (*l)[i]
|
||||
cj := (*l)[j]
|
||||
|
||||
if len(ci.Ip) < len(cj.Ip) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(ci.Ip) > len(cj.Ip) {
|
||||
return false
|
||||
}
|
||||
|
||||
for k := 0; k < len(ci.Ip); k++ {
|
||||
if ci.Ip[k] < cj.Ip[k] {
|
||||
return true
|
||||
}
|
||||
|
||||
if ci.Ip[k] > cj.Ip[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return ci.Prefix < cj.Prefix
|
||||
}
|
||||
|
||||
// Swap implements sort.Interface.
|
||||
func (l *CIDRList) Swap(i int, j int) {
|
||||
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
|
||||
}
|
9
common/matcher/geoip/errors.generated.go
Normal file
9
common/matcher/geoip/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package geoip
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
193
common/matcher/geoip/geoip.go
Normal file
193
common/matcher/geoip/geoip.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package geoip
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sort"
|
||||
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
type ipv6 struct {
|
||||
a uint64
|
||||
b uint64
|
||||
}
|
||||
|
||||
type GeoIPMatcher struct {
|
||||
countryCode string
|
||||
ip4 []uint32
|
||||
prefix4 []uint8
|
||||
ip6 []ipv6
|
||||
prefix6 []uint8
|
||||
}
|
||||
|
||||
func normalize4(ip uint32, prefix uint8) uint32 {
|
||||
return (ip >> (32 - prefix)) << (32 - prefix)
|
||||
}
|
||||
|
||||
func normalize6(ip ipv6, prefix uint8) ipv6 {
|
||||
if prefix <= 64 {
|
||||
ip.a = (ip.a >> (64 - prefix)) << (64 - prefix)
|
||||
ip.b = 0
|
||||
} else {
|
||||
ip.b = (ip.b >> (128 - prefix)) << (128 - prefix)
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
|
||||
ip4Count := 0
|
||||
ip6Count := 0
|
||||
|
||||
for _, cidr := range cidrs {
|
||||
ip := cidr.Ip
|
||||
switch len(ip) {
|
||||
case 4:
|
||||
ip4Count++
|
||||
case 16:
|
||||
ip6Count++
|
||||
default:
|
||||
return newError("unexpect ip length: ", len(ip))
|
||||
}
|
||||
}
|
||||
|
||||
cidrList := CIDRList(cidrs)
|
||||
sort.Sort(&cidrList)
|
||||
|
||||
m.ip4 = make([]uint32, 0, ip4Count)
|
||||
m.prefix4 = make([]uint8, 0, ip4Count)
|
||||
m.ip6 = make([]ipv6, 0, ip6Count)
|
||||
m.prefix6 = make([]uint8, 0, ip6Count)
|
||||
|
||||
for _, cidr := range cidrs {
|
||||
ip := cidr.Ip
|
||||
prefix := uint8(cidr.Prefix)
|
||||
switch len(ip) {
|
||||
case 4:
|
||||
m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix))
|
||||
m.prefix4 = append(m.prefix4, prefix)
|
||||
case 16:
|
||||
ip6 := ipv6{
|
||||
a: binary.BigEndian.Uint64(ip[0:8]),
|
||||
b: binary.BigEndian.Uint64(ip[8:16]),
|
||||
}
|
||||
ip6 = normalize6(ip6, prefix)
|
||||
|
||||
m.ip6 = append(m.ip6, ip6)
|
||||
m.prefix6 = append(m.prefix6, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GeoIPMatcher) match4(ip uint32) bool {
|
||||
if len(m.ip4) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if ip < m.ip4[0] {
|
||||
return false
|
||||
}
|
||||
|
||||
size := uint32(len(m.ip4))
|
||||
l := uint32(0)
|
||||
r := size
|
||||
for l < r {
|
||||
x := ((l + r) >> 1)
|
||||
if ip < m.ip4[x] {
|
||||
r = x
|
||||
continue
|
||||
}
|
||||
|
||||
nip := normalize4(ip, m.prefix4[x])
|
||||
if nip == m.ip4[x] {
|
||||
return true
|
||||
}
|
||||
|
||||
l = x + 1
|
||||
}
|
||||
|
||||
return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1]
|
||||
}
|
||||
|
||||
func less6(a ipv6, b ipv6) bool {
|
||||
return a.a < b.a || (a.a == b.a && a.b < b.b)
|
||||
}
|
||||
|
||||
func (m *GeoIPMatcher) match6(ip ipv6) bool {
|
||||
if len(m.ip6) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if less6(ip, m.ip6[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
size := uint32(len(m.ip6))
|
||||
l := uint32(0)
|
||||
r := size
|
||||
for l < r {
|
||||
x := (l + r) / 2
|
||||
if less6(ip, m.ip6[x]) {
|
||||
r = x
|
||||
continue
|
||||
}
|
||||
|
||||
if normalize6(ip, m.prefix6[x]) == m.ip6[x] {
|
||||
return true
|
||||
}
|
||||
|
||||
l = x + 1
|
||||
}
|
||||
|
||||
return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1]
|
||||
}
|
||||
|
||||
// Match returns true if the given ip is included by the GeoIP.
|
||||
func (m *GeoIPMatcher) Match(ip net.IP) bool {
|
||||
switch len(ip) {
|
||||
case 4:
|
||||
return m.match4(binary.BigEndian.Uint32(ip))
|
||||
case 16:
|
||||
return m.match6(ipv6{
|
||||
a: binary.BigEndian.Uint64(ip[0:8]),
|
||||
b: binary.BigEndian.Uint64(ip[8:16]),
|
||||
})
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code.
|
||||
type GeoIPMatcherContainer struct {
|
||||
matchers []*GeoIPMatcher
|
||||
}
|
||||
|
||||
// Add adds a new GeoIP set into the container.
|
||||
// If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one.
|
||||
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
||||
if len(geoip.CountryCode) > 0 {
|
||||
for _, m := range c.matchers {
|
||||
if m.countryCode == geoip.CountryCode {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m := &GeoIPMatcher{
|
||||
countryCode: geoip.CountryCode,
|
||||
}
|
||||
if err := m.Init(geoip.Cidr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(geoip.CountryCode) > 0 {
|
||||
c.matchers = append(c.matchers, m)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var (
|
||||
GlobalGeoIPContainer GeoIPMatcherContainer
|
||||
)
|
307
common/matcher/geoip/geoip.pb.go
Normal file
307
common/matcher/geoip/geoip.pb.go
Normal file
|
@ -0,0 +1,307 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.15.6
|
||||
// source: common/matcher/geoip/geoip.proto
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
// IP for routing decision, in CIDR form.
|
||||
type CIDR struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// IP address, should be either 4 or 16 bytes.
|
||||
Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||
// Number of leading ones in the network mask.
|
||||
Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CIDR) Reset() {
|
||||
*x = CIDR{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CIDR) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CIDR) ProtoMessage() {}
|
||||
|
||||
func (x *CIDR) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CIDR.ProtoReflect.Descriptor instead.
|
||||
func (*CIDR) Descriptor() ([]byte, []int) {
|
||||
return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *CIDR) GetIp() []byte {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CIDR) GetPrefix() uint32 {
|
||||
if x != nil {
|
||||
return x.Prefix
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type GeoIP struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
|
||||
Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GeoIP) Reset() {
|
||||
*x = GeoIP{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GeoIP) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoIP) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIP) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIP) Descriptor() ([]byte, []int) {
|
||||
return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetCountryCode() string {
|
||||
if x != nil {
|
||||
return x.CountryCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetCidr() []*CIDR {
|
||||
if x != nil {
|
||||
return x.Cidr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GeoIPList struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GeoIPList) Reset() {
|
||||
*x = GeoIPList{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GeoIPList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoIPList) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIPList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIPList) Descriptor() ([]byte, []int) {
|
||||
return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *GeoIPList) GetEntry() []*GeoIP {
|
||||
if x != nil {
|
||||
return x.Entry
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_common_matcher_geoip_geoip_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_common_matcher_geoip_geoip_proto_rawDesc = []byte{
|
||||
0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
|
||||
0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x12, 0x19, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||
0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x22, 0x2e, 0x0a,
|
||||
0x04, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x5f, 0x0a,
|
||||
0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72,
|
||||
0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x63, 0x69, 0x64,
|
||||
0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65,
|
||||
0x6f, 0x69, 0x70, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x22, 0x43,
|
||||
0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x05, 0x65,
|
||||
0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
|
||||
0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x65, 0x6e,
|
||||
0x74, 0x72, 0x79, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67,
|
||||
0x65, 0x6f, 0x69, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
|
||||
0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
|
||||
0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0xaa, 0x02, 0x19, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f,
|
||||
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f,
|
||||
0x49, 0x50, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_common_matcher_geoip_geoip_proto_rawDescOnce sync.Once
|
||||
file_common_matcher_geoip_geoip_proto_rawDescData = file_common_matcher_geoip_geoip_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_common_matcher_geoip_geoip_proto_rawDescGZIP() []byte {
|
||||
file_common_matcher_geoip_geoip_proto_rawDescOnce.Do(func() {
|
||||
file_common_matcher_geoip_geoip_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_matcher_geoip_geoip_proto_rawDescData)
|
||||
})
|
||||
return file_common_matcher_geoip_geoip_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_common_matcher_geoip_geoip_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_common_matcher_geoip_geoip_proto_goTypes = []interface{}{
|
||||
(*CIDR)(nil), // 0: xray.common.matcher.geoip.CIDR
|
||||
(*GeoIP)(nil), // 1: xray.common.matcher.geoip.GeoIP
|
||||
(*GeoIPList)(nil), // 2: xray.common.matcher.geoip.GeoIPList
|
||||
}
|
||||
var file_common_matcher_geoip_geoip_proto_depIdxs = []int32{
|
||||
0, // 0: xray.common.matcher.geoip.GeoIP.cidr:type_name -> xray.common.matcher.geoip.CIDR
|
||||
1, // 1: xray.common.matcher.geoip.GeoIPList.entry:type_name -> xray.common.matcher.geoip.GeoIP
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_common_matcher_geoip_geoip_proto_init() }
|
||||
func file_common_matcher_geoip_geoip_proto_init() {
|
||||
if File_common_matcher_geoip_geoip_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_common_matcher_geoip_geoip_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CIDR); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_common_matcher_geoip_geoip_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GeoIP); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_common_matcher_geoip_geoip_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GeoIPList); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_common_matcher_geoip_geoip_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_common_matcher_geoip_geoip_proto_goTypes,
|
||||
DependencyIndexes: file_common_matcher_geoip_geoip_proto_depIdxs,
|
||||
MessageInfos: file_common_matcher_geoip_geoip_proto_msgTypes,
|
||||
}.Build()
|
||||
File_common_matcher_geoip_geoip_proto = out.File
|
||||
file_common_matcher_geoip_geoip_proto_rawDesc = nil
|
||||
file_common_matcher_geoip_geoip_proto_goTypes = nil
|
||||
file_common_matcher_geoip_geoip_proto_depIdxs = nil
|
||||
}
|
25
common/matcher/geoip/geoip.proto
Normal file
25
common/matcher/geoip/geoip.proto
Normal file
|
@ -0,0 +1,25 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package xray.common.matcher.geoip;
|
||||
option csharp_namespace = "Xray.Common.Matcher.GeoIP";
|
||||
option go_package = "github.com/xtls/xray-core/common/matcher/geoip";
|
||||
option java_package = "com.xray.common.matcher.geoip";
|
||||
option java_multiple_files = true;
|
||||
|
||||
// IP for routing decision, in CIDR form.
|
||||
message CIDR {
|
||||
// IP address, should be either 4 or 16 bytes.
|
||||
bytes ip = 1;
|
||||
|
||||
// Number of leading ones in the network mask.
|
||||
uint32 prefix = 2;
|
||||
}
|
||||
|
||||
message GeoIP {
|
||||
string country_code = 1;
|
||||
repeated CIDR cidr = 2;
|
||||
}
|
||||
|
||||
message GeoIPList {
|
||||
repeated GeoIP entry = 1;
|
||||
}
|
195
common/matcher/geoip/geoip_test.go
Normal file
195
common/matcher/geoip/geoip_test.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
package geoip_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/xtls/xray-core/common"
|
||||
. "github.com/xtls/xray-core/common/matcher/geoip"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
)
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
common.Must(err)
|
||||
|
||||
if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geoip.dat")))
|
||||
}
|
||||
if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geosite.dat")))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPMatcherContainer(t *testing.T) {
|
||||
container := &GeoIPMatcherContainer{}
|
||||
|
||||
m1, err := container.Add(&GeoIP{
|
||||
CountryCode: "CN",
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
m2, err := container.Add(&GeoIP{
|
||||
CountryCode: "US",
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
m3, err := container.Add(&GeoIP{
|
||||
CountryCode: "CN",
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
if m1 != m3 {
|
||||
t.Error("expect same matcher for same geoip, but not")
|
||||
}
|
||||
|
||||
if m1 == m2 {
|
||||
t.Error("expect different matcher for different geoip, but actually same")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher(t *testing.T) {
|
||||
cidrList := CIDRList{
|
||||
{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{100, 64, 0, 0}, Prefix: 10},
|
||||
{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{169, 254, 0, 0}, Prefix: 16},
|
||||
{Ip: []byte{172, 16, 0, 0}, Prefix: 12},
|
||||
{Ip: []byte{192, 0, 0, 0}, Prefix: 24},
|
||||
{Ip: []byte{192, 0, 2, 0}, Prefix: 24},
|
||||
{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
{Ip: []byte{192, 18, 0, 0}, Prefix: 15},
|
||||
{Ip: []byte{198, 51, 100, 0}, Prefix: 24},
|
||||
{Ip: []byte{203, 0, 113, 0}, Prefix: 24},
|
||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
|
||||
}
|
||||
|
||||
matcher := &GeoIPMatcher{}
|
||||
common.Must(matcher.Init(cidrList))
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "192.168.1.1",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "192.0.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "192.0.1.0",
|
||||
Output: false,
|
||||
}, {
|
||||
Input: "0.1.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "1.0.0.1",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "8.8.8.7",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "91.108.255.254",
|
||||
Output: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
ip := net.ParseAddress(testCase.Input).IP()
|
||||
actual := matcher.Match(ip)
|
||||
if actual != testCase.Output {
|
||||
t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher4CN(t *testing.T) {
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher := &GeoIPMatcher{}
|
||||
common.Must(matcher.Init(ips))
|
||||
|
||||
if matcher.Match([]byte{8, 8, 8, 8}) {
|
||||
t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher6US(t *testing.T) {
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
matcher := &GeoIPMatcher{}
|
||||
common.Must(matcher.Init(ips))
|
||||
|
||||
if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
|
||||
t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not")
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoIP(country string) ([]*CIDR, error) {
|
||||
geoipBytes, err := filesystem.ReadAsset("geoip.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var geoipList GeoIPList
|
||||
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, geoip := range geoipList.Entry {
|
||||
if geoip.CountryCode == country {
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
}
|
||||
|
||||
panic("country not found: " + country)
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher := &GeoIPMatcher{}
|
||||
common.Must(matcher.Init(ips))
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matcher.Match([]byte{8, 8, 8, 8})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher6US(b *testing.B) {
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
matcher := &GeoIPMatcher{}
|
||||
common.Must(matcher.Init(ips))
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP())
|
||||
}
|
||||
}
|
47
common/matcher/geoip/matcher.go
Normal file
47
common/matcher/geoip/matcher.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package geoip
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
|
||||
type MultiGeoIPMatcher struct {
|
||||
matchers []*GeoIPMatcher
|
||||
onSource bool
|
||||
}
|
||||
|
||||
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
|
||||
var matchers []*GeoIPMatcher
|
||||
for _, geoip := range geoips {
|
||||
matcher, err := GlobalGeoIPContainer.Add(geoip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
matcher := &MultiGeoIPMatcher{
|
||||
matchers: matchers,
|
||||
onSource: onSource,
|
||||
}
|
||||
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
// Apply implements Condition.
|
||||
func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
|
||||
var ips []net.IP
|
||||
if m.onSource {
|
||||
ips = ctx.GetSourceIPs()
|
||||
} else {
|
||||
ips = ctx.GetTargetIPs()
|
||||
}
|
||||
for _, ip := range ips {
|
||||
for _, matcher := range m.matchers {
|
||||
if matcher.Match(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue