mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-21 09:36:34 -05:00
refactor configure file loader for geo loader and v5
This commit is contained in:
parent
9458963b5a
commit
0f1fac84ca
@ -1,4 +1,4 @@
|
||||
package conf
|
||||
package cfgcommon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -9,6 +9,8 @@ import (
|
||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
||||
|
||||
type StringList []string
|
||||
|
||||
func NewStringList(raw []string) *StringList {
|
@ -1,22 +1,23 @@
|
||||
package conf_test
|
||||
package cfgcommon_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common"
|
||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||
. "github.com/v2fly/v2ray-core/v4/infra/conf"
|
||||
)
|
||||
|
||||
func TestStringListUnmarshalError(t *testing.T) {
|
||||
rawJSON := `1234`
|
||||
list := new(StringList)
|
||||
list := new(cfgcommon.StringList)
|
||||
err := json.Unmarshal([]byte(rawJSON), list)
|
||||
if err == nil {
|
||||
t.Error("expected error, but got nil")
|
||||
@ -25,7 +26,7 @@ func TestStringListUnmarshalError(t *testing.T) {
|
||||
|
||||
func TestStringListLen(t *testing.T) {
|
||||
rawJSON := `"a, b, c, d"`
|
||||
var list StringList
|
||||
var list cfgcommon.StringList
|
||||
err := json.Unmarshal([]byte(rawJSON), &list)
|
||||
common.Must(err)
|
||||
if r := cmp.Diff([]string(list), []string{"a", " b", " c", " d"}); r != "" {
|
||||
@ -35,7 +36,7 @@ func TestStringListLen(t *testing.T) {
|
||||
|
||||
func TestIPParsing(t *testing.T) {
|
||||
rawJSON := "\"8.8.8.8\""
|
||||
var address Address
|
||||
var address cfgcommon.Address
|
||||
err := json.Unmarshal([]byte(rawJSON), &address)
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != "" {
|
||||
@ -45,7 +46,7 @@ func TestIPParsing(t *testing.T) {
|
||||
|
||||
func TestDomainParsing(t *testing.T) {
|
||||
rawJSON := "\"v2fly.org\""
|
||||
var address Address
|
||||
var address cfgcommon.Address
|
||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||
if address.Domain() != "v2fly.org" {
|
||||
t.Error("domain: ", address.Domain())
|
||||
@ -55,7 +56,7 @@ func TestDomainParsing(t *testing.T) {
|
||||
func TestURLParsing(t *testing.T) {
|
||||
{
|
||||
rawJSON := "\"https://dns.google/dns-query\""
|
||||
var address Address
|
||||
var address cfgcommon.Address
|
||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||
if address.Domain() != "https://dns.google/dns-query" {
|
||||
t.Error("URL: ", address.Domain())
|
||||
@ -63,7 +64,7 @@ func TestURLParsing(t *testing.T) {
|
||||
}
|
||||
{
|
||||
rawJSON := "\"https+local://dns.google/dns-query\""
|
||||
var address Address
|
||||
var address cfgcommon.Address
|
||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||
if address.Domain() != "https+local://dns.google/dns-query" {
|
||||
t.Error("URL: ", address.Domain())
|
||||
@ -73,7 +74,7 @@ func TestURLParsing(t *testing.T) {
|
||||
|
||||
func TestInvalidAddressJson(t *testing.T) {
|
||||
rawJSON := "1234"
|
||||
var address Address
|
||||
var address cfgcommon.Address
|
||||
err := json.Unmarshal([]byte(rawJSON), &address)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
@ -81,7 +82,7 @@ func TestInvalidAddressJson(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStringNetwork(t *testing.T) {
|
||||
var network Network
|
||||
var network cfgcommon.Network
|
||||
common.Must(json.Unmarshal([]byte(`"tcp"`), &network))
|
||||
if v := network.Build(); v != net.Network_TCP {
|
||||
t.Error("network: ", v)
|
||||
@ -89,7 +90,7 @@ func TestStringNetwork(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestArrayNetworkList(t *testing.T) {
|
||||
var list NetworkList
|
||||
var list cfgcommon.NetworkList
|
||||
common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list))
|
||||
|
||||
nlist := list.Build()
|
||||
@ -102,7 +103,7 @@ func TestArrayNetworkList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStringNetworkList(t *testing.T) {
|
||||
var list NetworkList
|
||||
var list cfgcommon.NetworkList
|
||||
common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list))
|
||||
|
||||
nlist := list.Build()
|
||||
@ -115,7 +116,7 @@ func TestStringNetworkList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInvalidNetworkJson(t *testing.T) {
|
||||
var list NetworkList
|
||||
var list cfgcommon.NetworkList
|
||||
err := json.Unmarshal([]byte("0"), &list)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
@ -123,10 +124,10 @@ func TestInvalidNetworkJson(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIntPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
var portRange cfgcommon.PortRange
|
||||
common.Must(json.Unmarshal([]byte("1234"), &portRange))
|
||||
|
||||
if r := cmp.Diff(portRange, PortRange{
|
||||
if r := cmp.Diff(portRange, cfgcommon.PortRange{
|
||||
From: 1234, To: 1234,
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
@ -134,7 +135,7 @@ func TestIntPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOverRangeIntPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
var portRange cfgcommon.PortRange
|
||||
err := json.Unmarshal([]byte("70000"), &portRange)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
@ -149,10 +150,10 @@ func TestOverRangeIntPort(t *testing.T) {
|
||||
func TestEnvPort(t *testing.T) {
|
||||
common.Must(os.Setenv("PORT", "1234"))
|
||||
|
||||
var portRange PortRange
|
||||
var portRange cfgcommon.PortRange
|
||||
common.Must(json.Unmarshal([]byte("\"env:PORT\""), &portRange))
|
||||
|
||||
if r := cmp.Diff(portRange, PortRange{
|
||||
if r := cmp.Diff(portRange, cfgcommon.PortRange{
|
||||
From: 1234, To: 1234,
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
@ -160,10 +161,10 @@ func TestEnvPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSingleStringPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
var portRange cfgcommon.PortRange
|
||||
common.Must(json.Unmarshal([]byte("\"1234\""), &portRange))
|
||||
|
||||
if r := cmp.Diff(portRange, PortRange{
|
||||
if r := cmp.Diff(portRange, cfgcommon.PortRange{
|
||||
From: 1234, To: 1234,
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
@ -171,10 +172,10 @@ func TestSingleStringPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStringPairPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
var portRange cfgcommon.PortRange
|
||||
common.Must(json.Unmarshal([]byte("\"1234-5678\""), &portRange))
|
||||
|
||||
if r := cmp.Diff(portRange, PortRange{
|
||||
if r := cmp.Diff(portRange, cfgcommon.PortRange{
|
||||
From: 1234, To: 5678,
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
@ -182,7 +183,7 @@ func TestStringPairPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOverRangeStringPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
var portRange cfgcommon.PortRange
|
||||
err := json.Unmarshal([]byte("\"65536\""), &portRange)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
@ -205,7 +206,7 @@ func TestOverRangeStringPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUserParsing(t *testing.T) {
|
||||
user := new(User)
|
||||
user := new(cfgcommon.User)
|
||||
common.Must(json.Unmarshal([]byte(`{
|
||||
"id": "96edb838-6d68-42ef-a933-25f7ac3a9d09",
|
||||
"email": "love@v2fly.org",
|
||||
@ -223,7 +224,7 @@ func TestUserParsing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInvalidUserJson(t *testing.T) {
|
||||
user := new(User)
|
||||
user := new(cfgcommon.User)
|
||||
err := json.Unmarshal([]byte(`{"email": 1234}`), user)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
9
infra/conf/cfgcommon/errors.generated.go
Normal file
9
infra/conf/cfgcommon/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
||||
package cfgcommon
|
||||
|
||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
42
infra/conf/cfgcommon/session.go
Normal file
42
infra/conf/cfgcommon/session.go
Normal file
@ -0,0 +1,42 @@
|
||||
package cfgcommon
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/geodata"
|
||||
)
|
||||
|
||||
type configureLoadingContext int
|
||||
|
||||
const confContextKey = configureLoadingContext(1)
|
||||
|
||||
type configureLoadingEnvironment struct {
|
||||
geoLoader geodata.Loader
|
||||
}
|
||||
|
||||
func (c *configureLoadingEnvironment) GetGeoLoader() geodata.Loader {
|
||||
if c.geoLoader == nil {
|
||||
var err error
|
||||
c.geoLoader, err = geodata.GetGeoDataLoader("standard")
|
||||
common.Must(err)
|
||||
}
|
||||
return c.geoLoader
|
||||
}
|
||||
|
||||
type ConfigureLoadingEnvironment interface {
|
||||
GetGeoLoader() geodata.Loader
|
||||
}
|
||||
|
||||
func NewConfigureLoadingContext(ctx context.Context) context.Context {
|
||||
environment := &configureLoadingEnvironment{}
|
||||
return context.WithValue(ctx, confContextKey, environment)
|
||||
}
|
||||
|
||||
func GetConfigureLoadingEnvironment(ctx context.Context) ConfigureLoadingEnvironment {
|
||||
return ctx.Value(confContextKey).(ConfigureLoadingEnvironment)
|
||||
}
|
||||
|
||||
func SetGeoDataLoader(ctx context.Context, loader geodata.Loader) {
|
||||
GetConfigureLoadingEnvironment(ctx).(*configureLoadingEnvironment).geoLoader = loader
|
||||
}
|
@ -1,38 +1,45 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/geodata"
|
||||
rule2 "github.com/v2fly/v2ray-core/v4/infra/conf/rule"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/app/dns"
|
||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||
)
|
||||
|
||||
type NameServerConfig struct {
|
||||
Address *Address
|
||||
ClientIP *Address
|
||||
Address *cfgcommon.Address
|
||||
ClientIP *cfgcommon.Address
|
||||
Port uint16
|
||||
SkipFallback bool
|
||||
Domains []string
|
||||
ExpectIPs StringList
|
||||
ExpectIPs cfgcommon.StringList
|
||||
|
||||
cfgctx context.Context
|
||||
}
|
||||
|
||||
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
||||
var address Address
|
||||
var address cfgcommon.Address
|
||||
if err := json.Unmarshal(data, &address); err == nil {
|
||||
c.Address = &address
|
||||
return nil
|
||||
}
|
||||
|
||||
var advanced struct {
|
||||
Address *Address `json:"address"`
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
Port uint16 `json:"port"`
|
||||
SkipFallback bool `json:"skipFallback"`
|
||||
Domains []string `json:"domains"`
|
||||
ExpectIPs StringList `json:"expectIps"`
|
||||
Address *cfgcommon.Address `json:"address"`
|
||||
ClientIP *cfgcommon.Address `json:"clientIp"`
|
||||
Port uint16 `json:"port"`
|
||||
SkipFallback bool `json:"skipFallback"`
|
||||
Domains []string `json:"domains"`
|
||||
ExpectIPs cfgcommon.StringList `json:"expectIps"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &advanced); err == nil {
|
||||
c.Address = advanced.Address
|
||||
@ -63,6 +70,8 @@ func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
|
||||
}
|
||||
|
||||
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
cfgctx := c.cfgctx
|
||||
|
||||
if c.Address == nil {
|
||||
return nil, newError("NameServer address is not specified.")
|
||||
}
|
||||
@ -71,7 +80,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
var originalRules []*dns.NameServer_OriginalRule
|
||||
|
||||
for _, rule := range c.Domains {
|
||||
parsedDomain, err := parseDomainRule(rule)
|
||||
parsedDomain, err := rule2.ParseDomainRule(cfgctx, rule)
|
||||
if err != nil {
|
||||
return nil, newError("invalid domain rule: ", rule).Base(err)
|
||||
}
|
||||
@ -88,7 +97,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
})
|
||||
}
|
||||
|
||||
geoipList, err := toCidrList(c.ExpectIPs)
|
||||
geoipList, err := rule2.ToCidrList(cfgctx, c.ExpectIPs)
|
||||
if err != nil {
|
||||
return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
|
||||
}
|
||||
@ -126,22 +135,24 @@ var typeMap = map[router.Domain_Type]dns.DomainMatchingType{
|
||||
type DNSConfig struct {
|
||||
Servers []*NameServerConfig `json:"servers"`
|
||||
Hosts map[string]*HostAddress `json:"hosts"`
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
ClientIP *cfgcommon.Address `json:"clientIp"`
|
||||
Tag string `json:"tag"`
|
||||
QueryStrategy string `json:"queryStrategy"`
|
||||
DisableCache bool `json:"disableCache"`
|
||||
DisableFallback bool `json:"disableFallback"`
|
||||
|
||||
GeoLoader string `json:"geoLoader"`
|
||||
}
|
||||
|
||||
type HostAddress struct {
|
||||
addr *Address
|
||||
addrs []*Address
|
||||
addr *cfgcommon.Address
|
||||
addrs []*cfgcommon.Address
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||
func (h *HostAddress) UnmarshalJSON(data []byte) error {
|
||||
addr := new(Address)
|
||||
var addrs []*Address
|
||||
addr := new(cfgcommon.Address)
|
||||
var addrs []*cfgcommon.Address
|
||||
switch {
|
||||
case json.Unmarshal(data, &addr) == nil:
|
||||
h.addr = addr
|
||||
@ -181,6 +192,21 @@ func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *DNSConfig) Build() (*dns.Config, error) {
|
||||
cfgctx := cfgcommon.NewConfigureLoadingContext(context.Background())
|
||||
|
||||
if c.GeoLoader == "" {
|
||||
c.GeoLoader = "standard"
|
||||
}
|
||||
|
||||
if loader, err := geodata.GetGeoDataLoader(c.GeoLoader); err == nil {
|
||||
cfgcommon.SetGeoDataLoader(cfgctx, loader)
|
||||
} else {
|
||||
return nil, newError("unable to create geo data loader ").Base(err)
|
||||
}
|
||||
|
||||
cfgEnv := cfgcommon.GetConfigureLoadingEnvironment(cfgctx)
|
||||
geoLoader := cfgEnv.GetGeoLoader()
|
||||
|
||||
config := &dns.Config{
|
||||
Tag: c.Tag,
|
||||
DisableCache: c.DisableCache,
|
||||
@ -205,6 +231,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
||||
}
|
||||
|
||||
for _, server := range c.Servers {
|
||||
server.cfgctx = cfgctx
|
||||
ns, err := server.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to build nameserver").Base(err)
|
||||
@ -238,7 +265,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
||||
if len(listName) == 0 {
|
||||
return nil, newError("empty geosite rule: ", domain)
|
||||
}
|
||||
geositeList, err := loadGeosite(listName)
|
||||
geositeList, err := geoLoader.LoadGeosite(listName)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geosite: ", listName).Base(err)
|
||||
}
|
||||
@ -299,7 +326,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
||||
}
|
||||
filename := kv[0]
|
||||
list := kv[1]
|
||||
geositeList, err := loadGeositeWithAttr(filename, list)
|
||||
geositeList, err := geoLoader.LoadGeositeWithAttr(filename, list)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
|
||||
}
|
||||
|
@ -2,15 +2,16 @@ package conf
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||
"github.com/v2fly/v2ray-core/v4/proxy/dns"
|
||||
)
|
||||
|
||||
type DNSOutboundConfig struct {
|
||||
Network Network `json:"network"`
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Network cfgcommon.Network `json:"network"`
|
||||
Address *cfgcommon.Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
}
|
||||
|
||||
func (c *DNSOutboundConfig) Build() (proto.Message, error) {
|
||||
|
@ -16,6 +16,8 @@ import (
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform"
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf"
|
||||
|
||||
_ "github.com/v2fly/v2ray-core/v4/infra/conf/geodata/standard"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -2,17 +2,18 @@ package conf
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/proxy/dokodemo"
|
||||
)
|
||||
|
||||
type DokodemoConfig struct {
|
||||
Host *Address `json:"address"`
|
||||
PortValue uint16 `json:"port"`
|
||||
NetworkList *NetworkList `json:"network"`
|
||||
TimeoutValue uint32 `json:"timeout"`
|
||||
Redirect bool `json:"followRedirect"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
Host *cfgcommon.Address `json:"address"`
|
||||
PortValue uint16 `json:"port"`
|
||||
NetworkList *cfgcommon.NetworkList `json:"network"`
|
||||
TimeoutValue uint32 `json:"timeout"`
|
||||
Redirect bool `json:"followRedirect"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
}
|
||||
|
||||
func (v *DokodemoConfig) Build() (proto.Message, error) {
|
||||
|
51
infra/conf/geodata/attr.go
Normal file
51
infra/conf/geodata/attr.go
Normal file
@ -0,0 +1,51 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
||||
)
|
||||
|
||||
type AttributeList struct {
|
||||
matcher []AttributeMatcher
|
||||
}
|
||||
|
||||
func (al *AttributeList) Match(domain *router.Domain) bool {
|
||||
for _, matcher := range al.matcher {
|
||||
if !matcher.Match(domain) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (al *AttributeList) IsEmpty() bool {
|
||||
return len(al.matcher) == 0
|
||||
}
|
||||
|
||||
func parseAttrs(attrs []string) *AttributeList {
|
||||
al := new(AttributeList)
|
||||
for _, attr := range attrs {
|
||||
trimmedAttr := strings.ToLower(strings.TrimSpace(attr))
|
||||
if len(trimmedAttr) == 0 {
|
||||
continue
|
||||
}
|
||||
al.matcher = append(al.matcher, BooleanMatcher(trimmedAttr))
|
||||
}
|
||||
return al
|
||||
}
|
||||
|
||||
type AttributeMatcher interface {
|
||||
Match(*router.Domain) bool
|
||||
}
|
||||
|
||||
type BooleanMatcher string
|
||||
|
||||
func (m BooleanMatcher) Match(domain *router.Domain) bool {
|
||||
for _, attr := range domain.Attribute {
|
||||
if strings.EqualFold(attr.GetKey(), string(m)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
9
infra/conf/geodata/errors.generated.go
Normal file
9
infra/conf/geodata/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
||||
package geodata
|
||||
|
||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
83
infra/conf/geodata/geodata.go
Normal file
83
infra/conf/geodata/geodata.go
Normal file
@ -0,0 +1,83 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
||||
)
|
||||
|
||||
type loader struct {
|
||||
LoaderImplementation
|
||||
}
|
||||
|
||||
func (l *loader) LoadGeosite(list string) ([]*router.Domain, error) {
|
||||
return l.LoadGeositeWithAttr("geosite.dat", list)
|
||||
}
|
||||
|
||||
func (l *loader) LoadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
|
||||
parts := strings.Split(siteWithAttr, "@")
|
||||
if len(parts) == 0 {
|
||||
return nil, newError("empty rule")
|
||||
}
|
||||
list := strings.TrimSpace(parts[0])
|
||||
attrVal := parts[1:]
|
||||
|
||||
if len(list) == 0 {
|
||||
return nil, newError("empty listname in rule: ", siteWithAttr)
|
||||
}
|
||||
|
||||
domains, err := l.LoadSite(file, list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrs := parseAttrs(attrVal)
|
||||
if attrs.IsEmpty() {
|
||||
if strings.Contains(siteWithAttr, "@") {
|
||||
newError("empty attribute list: ", siteWithAttr)
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||
hasAttrMatched := false
|
||||
for _, domain := range domains {
|
||||
if attrs.Match(domain) {
|
||||
hasAttrMatched = true
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
if !hasAttrMatched {
|
||||
newError("attribute match no rule: geosite:", siteWithAttr)
|
||||
}
|
||||
|
||||
return filteredDomains, nil
|
||||
}
|
||||
|
||||
func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
return l.LoadIP("geoip.dat", country)
|
||||
}
|
||||
|
||||
var loaders map[string]func() LoaderImplementation
|
||||
|
||||
func RegisterGeoDataLoaderImplementationCreator(name string, loader func() LoaderImplementation) {
|
||||
if loaders == nil {
|
||||
loaders = map[string]func() LoaderImplementation{}
|
||||
}
|
||||
loaders[name] = loader
|
||||
}
|
||||
|
||||
func getGeoDataLoaderImplementation(name string) (LoaderImplementation, error) {
|
||||
if geoLoader, ok := loaders[name]; ok {
|
||||
return geoLoader(), nil
|
||||
}
|
||||
return nil, newError("unable to locate GeoData loader ", name)
|
||||
}
|
||||
|
||||
func GetGeoDataLoader(name string) (Loader, error) {
|
||||
if loadImpl, err := getGeoDataLoaderImplementation(name); err == nil {
|
||||
return &loader{loadImpl}, nil
|
||||
} else { // nolint:golint
|
||||
return nil, err
|
||||
}
|
||||
}
|
17
infra/conf/geodata/geodataproto.go
Normal file
17
infra/conf/geodata/geodataproto.go
Normal file
@ -0,0 +1,17 @@
|
||||
package geodata
|
||||
|
||||
import "github.com/v2fly/v2ray-core/v4/app/router"
|
||||
|
||||
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
||||
|
||||
type LoaderImplementation interface {
|
||||
LoadSite(filename, list string) ([]*router.Domain, error)
|
||||
LoadIP(filename, country string) ([]*router.CIDR, error)
|
||||
}
|
||||
|
||||
type Loader interface {
|
||||
LoaderImplementation
|
||||
LoadGeosite(list string) ([]*router.Domain, error)
|
||||
LoadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error)
|
||||
LoadGeoIP(country string) ([]*router.CIDR, error)
|
||||
}
|
9
infra/conf/geodata/standard/errors.generated.go
Normal file
9
infra/conf/geodata/standard/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
||||
package standard
|
||||
|
||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
69
infra/conf/geodata/standard/standard.go
Normal file
69
infra/conf/geodata/standard/standard.go
Normal file
@ -0,0 +1,69 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/geodata"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
||||
|
||||
func loadIP(filename, country string) ([]*router.CIDR, error) {
|
||||
geoipBytes, err := filesystem.ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, newError("failed to open file: ", filename).Base(err)
|
||||
}
|
||||
var geoipList router.GeoIPList
|
||||
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, geoip := range geoipList.Entry {
|
||||
if strings.EqualFold(geoip.CountryCode, country) {
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, newError("country not found in ", filename, ": ", country)
|
||||
}
|
||||
|
||||
func loadSite(filename, list string) ([]*router.Domain, error) {
|
||||
geositeBytes, err := filesystem.ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, newError("failed to open file: ", filename).Base(err)
|
||||
}
|
||||
var geositeList router.GeoSiteList
|
||||
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, site := range geositeList.Entry {
|
||||
if strings.EqualFold(site.CountryCode, list) {
|
||||
return site.Domain, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, newError("list not found in ", filename, ": ", list)
|
||||
}
|
||||
|
||||
type standardLoader struct {
|
||||
}
|
||||
|
||||
func (d standardLoader) LoadSite(filename, list string) ([]*router.Domain, error) {
|
||||
return loadSite(filename, list)
|
||||
}
|
||||
|
||||
func (d standardLoader) LoadIP(filename, country string) ([]*router.CIDR, error) {
|
||||
return loadIP(filename, country)
|
||||
}
|
||||
|
||||
func init() {
|
||||
geodata.RegisterGeoDataLoaderImplementationCreator("standard", func() geodata.LoaderImplementation {
|
||||
return standardLoader{}
|
||||
})
|
||||
}
|
@ -3,6 +3,8 @@ package conf
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||
@ -47,9 +49,9 @@ func (c *HTTPServerConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type HTTPRemoteConfig struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
Address *cfgcommon.Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
}
|
||||
type HTTPClientConfig struct {
|
||||
Servers []*HTTPRemoteConfig `json:"servers"`
|
||||
|
@ -2,6 +2,7 @@ package conf
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/app/observatory"
|
||||
)
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/geodata"
|
||||
rule2 "github.com/v2fly/v2ray-core/v4/infra/conf/rule"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
||||
)
|
||||
|
||||
type RouterRulesConfig struct {
|
||||
@ -24,9 +24,9 @@ type StrategyConfig struct {
|
||||
}
|
||||
|
||||
type BalancingRule struct {
|
||||
Tag string `json:"tag"`
|
||||
Selectors StringList `json:"selector"`
|
||||
Strategy StrategyConfig `json:"strategy"`
|
||||
Tag string `json:"tag"`
|
||||
Selectors cfgcommon.StringList `json:"selector"`
|
||||
Strategy StrategyConfig `json:"strategy"`
|
||||
}
|
||||
|
||||
func (r *BalancingRule) Build() (*router.BalancingRule, error) {
|
||||
@ -61,6 +61,7 @@ type RouterConfig struct {
|
||||
Balancers []*BalancingRule `json:"balancers"`
|
||||
|
||||
DomainMatcher string `json:"domainMatcher"`
|
||||
GeoLoader string `json:"geoLoader"`
|
||||
}
|
||||
|
||||
func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
|
||||
@ -87,6 +88,18 @@ func (c *RouterConfig) Build() (*router.Config, error) {
|
||||
config := new(router.Config)
|
||||
config.DomainStrategy = c.getDomainStrategy()
|
||||
|
||||
cfgctx := cfgcommon.NewConfigureLoadingContext(context.Background())
|
||||
|
||||
if c.GeoLoader == "" {
|
||||
c.GeoLoader = "standard"
|
||||
}
|
||||
|
||||
if loader, err := geodata.GetGeoDataLoader(c.GeoLoader); err == nil {
|
||||
cfgcommon.SetGeoDataLoader(cfgctx, loader)
|
||||
} else {
|
||||
return nil, newError("unable to create geo data loader ").Base(err)
|
||||
}
|
||||
|
||||
var rawRuleList []json.RawMessage
|
||||
if c != nil {
|
||||
rawRuleList = c.RuleList
|
||||
@ -97,7 +110,7 @@ func (c *RouterConfig) Build() (*router.Config, error) {
|
||||
}
|
||||
|
||||
for _, rawRule := range rawRuleList {
|
||||
rule, err := ParseRule(rawRule)
|
||||
rule, err := rule2.ParseRule(cfgctx, rawRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -117,499 +130,3 @@ func (c *RouterConfig) Build() (*router.Config, error) {
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type RouterRule struct {
|
||||
Type string `json:"type"`
|
||||
OutboundTag string `json:"outboundTag"`
|
||||
BalancerTag string `json:"balancerTag"`
|
||||
|
||||
DomainMatcher string `json:"domainMatcher"`
|
||||
}
|
||||
|
||||
func ParseIP(s string) (*router.CIDR, error) {
|
||||
var addr, mask string
|
||||
i := strings.Index(s, "/")
|
||||
if i < 0 {
|
||||
addr = s
|
||||
} else {
|
||||
addr = s[:i]
|
||||
mask = s[i+1:]
|
||||
}
|
||||
ip := net.ParseAddress(addr)
|
||||
switch ip.Family() {
|
||||
case net.AddressFamilyIPv4:
|
||||
bits := uint32(32)
|
||||
if len(mask) > 0 {
|
||||
bits64, err := strconv.ParseUint(mask, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newError("invalid network mask for router: ", mask).Base(err)
|
||||
}
|
||||
bits = uint32(bits64)
|
||||
}
|
||||
if bits > 32 {
|
||||
return nil, newError("invalid network mask for router: ", bits)
|
||||
}
|
||||
return &router.CIDR{
|
||||
Ip: []byte(ip.IP()),
|
||||
Prefix: bits,
|
||||
}, nil
|
||||
case net.AddressFamilyIPv6:
|
||||
bits := uint32(128)
|
||||
if len(mask) > 0 {
|
||||
bits64, err := strconv.ParseUint(mask, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newError("invalid network mask for router: ", mask).Base(err)
|
||||
}
|
||||
bits = uint32(bits64)
|
||||
}
|
||||
if bits > 128 {
|
||||
return nil, newError("invalid network mask for router: ", bits)
|
||||
}
|
||||
return &router.CIDR{
|
||||
Ip: []byte(ip.IP()),
|
||||
Prefix: bits,
|
||||
}, nil
|
||||
default:
|
||||
return nil, newError("unsupported address for router: ", s)
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
return loadIP("geoip.dat", country)
|
||||
}
|
||||
|
||||
func loadIP(filename, country string) ([]*router.CIDR, error) {
|
||||
geoipBytes, err := filesystem.ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, newError("failed to open file: ", filename).Base(err)
|
||||
}
|
||||
var geoipList router.GeoIPList
|
||||
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, geoip := range geoipList.Entry {
|
||||
if strings.EqualFold(geoip.CountryCode, country) {
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, newError("country not found in ", filename, ": ", country)
|
||||
}
|
||||
|
||||
func loadSite(filename, list string) ([]*router.Domain, error) {
|
||||
geositeBytes, err := filesystem.ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, newError("failed to open file: ", filename).Base(err)
|
||||
}
|
||||
var geositeList router.GeoSiteList
|
||||
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, site := range geositeList.Entry {
|
||||
if strings.EqualFold(site.CountryCode, list) {
|
||||
return site.Domain, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, newError("list not found in ", filename, ": ", list)
|
||||
}
|
||||
|
||||
type AttributeMatcher interface {
|
||||
Match(*router.Domain) bool
|
||||
}
|
||||
|
||||
type BooleanMatcher string
|
||||
|
||||
func (m BooleanMatcher) Match(domain *router.Domain) bool {
|
||||
for _, attr := range domain.Attribute {
|
||||
if strings.EqualFold(attr.GetKey(), string(m)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AttributeList struct {
|
||||
matcher []AttributeMatcher
|
||||
}
|
||||
|
||||
func (al *AttributeList) Match(domain *router.Domain) bool {
|
||||
for _, matcher := range al.matcher {
|
||||
if !matcher.Match(domain) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (al *AttributeList) IsEmpty() bool {
|
||||
return len(al.matcher) == 0
|
||||
}
|
||||
|
||||
func parseAttrs(attrs []string) *AttributeList {
|
||||
al := new(AttributeList)
|
||||
for _, attr := range attrs {
|
||||
trimmedAttr := strings.ToLower(strings.TrimSpace(attr))
|
||||
if len(trimmedAttr) == 0 {
|
||||
continue
|
||||
}
|
||||
al.matcher = append(al.matcher, BooleanMatcher(trimmedAttr))
|
||||
}
|
||||
return al
|
||||
}
|
||||
|
||||
func loadGeosite(list string) ([]*router.Domain, error) {
|
||||
return loadGeositeWithAttr("geosite.dat", list)
|
||||
}
|
||||
|
||||
func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
|
||||
parts := strings.Split(siteWithAttr, "@")
|
||||
if len(parts) == 0 {
|
||||
return nil, newError("empty rule")
|
||||
}
|
||||
list := strings.TrimSpace(parts[0])
|
||||
attrVal := parts[1:]
|
||||
|
||||
if len(list) == 0 {
|
||||
return nil, newError("empty listname in rule: ", siteWithAttr)
|
||||
}
|
||||
|
||||
domains, err := loadSite(file, list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrs := parseAttrs(attrVal)
|
||||
if attrs.IsEmpty() {
|
||||
if strings.Contains(siteWithAttr, "@") {
|
||||
newError("empty attribute list: ", siteWithAttr)
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||
hasAttrMatched := false
|
||||
for _, domain := range domains {
|
||||
if attrs.Match(domain) {
|
||||
hasAttrMatched = true
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
if !hasAttrMatched {
|
||||
newError("attribute match no rule: geosite:", siteWithAttr)
|
||||
}
|
||||
|
||||
return filteredDomains, nil
|
||||
}
|
||||
|
||||
func parseDomainRule(domain string) ([]*router.Domain, error) {
|
||||
if strings.HasPrefix(domain, "geosite:") {
|
||||
list := domain[8:]
|
||||
if len(list) == 0 {
|
||||
return nil, newError("empty listname in rule: ", domain)
|
||||
}
|
||||
domains, err := loadGeosite(list)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geosite: ", list).Base(err)
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
var isExtDatFile = 0
|
||||
{
|
||||
const prefix = "ext:"
|
||||
if strings.HasPrefix(domain, prefix) {
|
||||
isExtDatFile = len(prefix)
|
||||
}
|
||||
const prefixQualified = "ext-domain:"
|
||||
if strings.HasPrefix(domain, prefixQualified) {
|
||||
isExtDatFile = len(prefixQualified)
|
||||
}
|
||||
}
|
||||
|
||||
if isExtDatFile != 0 {
|
||||
kv := strings.Split(domain[isExtDatFile:], ":")
|
||||
if len(kv) != 2 {
|
||||
return nil, newError("invalid external resource: ", domain)
|
||||
}
|
||||
filename := kv[0]
|
||||
list := kv[1]
|
||||
domains, err := loadGeositeWithAttr(filename, list)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load external geosite: ", list, " from ", filename).Base(err)
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
domainRule := new(router.Domain)
|
||||
switch {
|
||||
case strings.HasPrefix(domain, "regexp:"):
|
||||
regexpVal := domain[7:]
|
||||
if len(regexpVal) == 0 {
|
||||
return nil, newError("empty regexp type of rule: ", domain)
|
||||
}
|
||||
domainRule.Type = router.Domain_Regex
|
||||
domainRule.Value = regexpVal
|
||||
|
||||
case strings.HasPrefix(domain, "domain:"):
|
||||
domainName := domain[7:]
|
||||
if len(domainName) == 0 {
|
||||
return nil, newError("empty domain type of rule: ", domain)
|
||||
}
|
||||
domainRule.Type = router.Domain_Domain
|
||||
domainRule.Value = domainName
|
||||
|
||||
case strings.HasPrefix(domain, "full:"):
|
||||
fullVal := domain[5:]
|
||||
if len(fullVal) == 0 {
|
||||
return nil, newError("empty full domain type of rule: ", domain)
|
||||
}
|
||||
domainRule.Type = router.Domain_Full
|
||||
domainRule.Value = fullVal
|
||||
|
||||
case strings.HasPrefix(domain, "keyword:"):
|
||||
keywordVal := domain[8:]
|
||||
if len(keywordVal) == 0 {
|
||||
return nil, newError("empty keyword type of rule: ", domain)
|
||||
}
|
||||
domainRule.Type = router.Domain_Plain
|
||||
domainRule.Value = keywordVal
|
||||
|
||||
case strings.HasPrefix(domain, "dotless:"):
|
||||
domainRule.Type = router.Domain_Regex
|
||||
switch substr := domain[8:]; {
|
||||
case substr == "":
|
||||
domainRule.Value = "^[^.]*$"
|
||||
case !strings.Contains(substr, "."):
|
||||
domainRule.Value = "^[^.]*" + substr + "[^.]*$"
|
||||
default:
|
||||
return nil, newError("substr in dotless rule should not contain a dot: ", substr)
|
||||
}
|
||||
|
||||
default:
|
||||
domainRule.Type = router.Domain_Plain
|
||||
domainRule.Value = domain
|
||||
}
|
||||
return []*router.Domain{domainRule}, nil
|
||||
}
|
||||
|
||||
func toCidrList(ips StringList) ([]*router.GeoIP, error) {
|
||||
var geoipList []*router.GeoIP
|
||||
var customCidrs []*router.CIDR
|
||||
|
||||
for _, ip := range ips {
|
||||
if strings.HasPrefix(ip, "geoip:") {
|
||||
country := ip[6:]
|
||||
isReverseMatch := false
|
||||
if strings.HasPrefix(ip, "geoip:!") {
|
||||
country = ip[7:]
|
||||
isReverseMatch = true
|
||||
}
|
||||
if len(country) == 0 {
|
||||
return nil, newError("empty country name in rule")
|
||||
}
|
||||
geoip, err := loadGeoIP(country)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geoip: ", country).Base(err)
|
||||
}
|
||||
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
CountryCode: strings.ToUpper(country),
|
||||
Cidr: geoip,
|
||||
ReverseMatch: isReverseMatch,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var isExtDatFile = 0
|
||||
{
|
||||
const prefix = "ext:"
|
||||
if strings.HasPrefix(ip, prefix) {
|
||||
isExtDatFile = len(prefix)
|
||||
}
|
||||
const prefixQualified = "ext-ip:"
|
||||
if strings.HasPrefix(ip, prefixQualified) {
|
||||
isExtDatFile = len(prefixQualified)
|
||||
}
|
||||
}
|
||||
|
||||
if isExtDatFile != 0 {
|
||||
kv := strings.Split(ip[isExtDatFile:], ":")
|
||||
if len(kv) != 2 {
|
||||
return nil, newError("invalid external resource: ", ip)
|
||||
}
|
||||
|
||||
filename := kv[0]
|
||||
country := kv[1]
|
||||
if len(filename) == 0 || len(country) == 0 {
|
||||
return nil, newError("empty filename or empty country in rule")
|
||||
}
|
||||
|
||||
isReverseMatch := false
|
||||
if strings.HasPrefix(country, "!") {
|
||||
country = country[1:]
|
||||
isReverseMatch = true
|
||||
}
|
||||
geoip, err := loadIP(filename, country)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geoip: ", country, " from ", filename).Base(err)
|
||||
}
|
||||
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
CountryCode: strings.ToUpper(filename + "_" + country),
|
||||
Cidr: geoip,
|
||||
ReverseMatch: isReverseMatch,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ipRule, err := ParseIP(ip)
|
||||
if err != nil {
|
||||
return nil, newError("invalid IP: ", ip).Base(err)
|
||||
}
|
||||
customCidrs = append(customCidrs, ipRule)
|
||||
}
|
||||
|
||||
if len(customCidrs) > 0 {
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
Cidr: customCidrs,
|
||||
})
|
||||
}
|
||||
|
||||
return geoipList, nil
|
||||
}
|
||||
|
||||
func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
type RawFieldRule struct {
|
||||
RouterRule
|
||||
Domain *StringList `json:"domain"`
|
||||
Domains *StringList `json:"domains"`
|
||||
IP *StringList `json:"ip"`
|
||||
Port *PortList `json:"port"`
|
||||
Network *NetworkList `json:"network"`
|
||||
SourceIP *StringList `json:"source"`
|
||||
SourcePort *PortList `json:"sourcePort"`
|
||||
User *StringList `json:"user"`
|
||||
InboundTag *StringList `json:"inboundTag"`
|
||||
Protocols *StringList `json:"protocol"`
|
||||
Attributes string `json:"attrs"`
|
||||
}
|
||||
rawFieldRule := new(RawFieldRule)
|
||||
err := json.Unmarshal(msg, rawFieldRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rule := new(router.RoutingRule)
|
||||
switch {
|
||||
case len(rawFieldRule.OutboundTag) > 0:
|
||||
rule.TargetTag = &router.RoutingRule_Tag{
|
||||
Tag: rawFieldRule.OutboundTag,
|
||||
}
|
||||
case len(rawFieldRule.BalancerTag) > 0:
|
||||
rule.TargetTag = &router.RoutingRule_BalancingTag{
|
||||
BalancingTag: rawFieldRule.BalancerTag,
|
||||
}
|
||||
default:
|
||||
return nil, newError("neither outboundTag nor balancerTag is specified in routing rule")
|
||||
}
|
||||
|
||||
if rawFieldRule.DomainMatcher != "" {
|
||||
rule.DomainMatcher = rawFieldRule.DomainMatcher
|
||||
}
|
||||
|
||||
if rawFieldRule.Domain != nil {
|
||||
for _, domain := range *rawFieldRule.Domain {
|
||||
rules, err := parseDomainRule(domain)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
||||
}
|
||||
rule.Domain = append(rule.Domain, rules...)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.Domains != nil {
|
||||
for _, domain := range *rawFieldRule.Domains {
|
||||
rules, err := parseDomainRule(domain)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
||||
}
|
||||
rule.Domain = append(rule.Domain, rules...)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.IP != nil {
|
||||
geoipList, err := toCidrList(*rawFieldRule.IP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.Geoip = geoipList
|
||||
}
|
||||
|
||||
if rawFieldRule.Port != nil {
|
||||
rule.PortList = rawFieldRule.Port.Build()
|
||||
}
|
||||
|
||||
if rawFieldRule.Network != nil {
|
||||
rule.Networks = rawFieldRule.Network.Build()
|
||||
}
|
||||
|
||||
if rawFieldRule.SourceIP != nil {
|
||||
geoipList, err := toCidrList(*rawFieldRule.SourceIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.SourceGeoip = geoipList
|
||||
}
|
||||
|
||||
if rawFieldRule.SourcePort != nil {
|
||||
rule.SourcePortList = rawFieldRule.SourcePort.Build()
|
||||
}
|
||||
|
||||
if rawFieldRule.User != nil {
|
||||
for _, s := range *rawFieldRule.User {
|
||||
rule.UserEmail = append(rule.UserEmail, s)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.InboundTag != nil {
|
||||
for _, s := range *rawFieldRule.InboundTag {
|
||||
rule.InboundTag = append(rule.InboundTag, s)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.Protocols != nil {
|
||||
for _, s := range *rawFieldRule.Protocols {
|
||||
rule.Protocol = append(rule.Protocol, s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rawFieldRule.Attributes) > 0 {
|
||||
rule.Attributes = rawFieldRule.Attributes
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
rawRule := new(RouterRule)
|
||||
err := json.Unmarshal(msg, rawRule)
|
||||
if err != nil {
|
||||
return nil, newError("invalid router rule").Base(err)
|
||||
}
|
||||
if strings.EqualFold(rawRule.Type, "field") {
|
||||
fieldrule, err := parseFieldRule(msg)
|
||||
if err != nil {
|
||||
return nil, newError("invalid field rule").Base(err)
|
||||
}
|
||||
return fieldrule, nil
|
||||
}
|
||||
|
||||
return nil, newError("unknown router rule type: ", rawRule.Type)
|
||||
}
|
||||
|
@ -2,67 +2,16 @@ package conf_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
||||
"github.com/v2fly/v2ray-core/v4/common"
|
||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform"
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
||||
. "github.com/v2fly/v2ray-core/v4/infra/conf"
|
||||
)
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
common.Must(err)
|
||||
|
||||
tempPath := filepath.Join(wd, "..", "..", "testing", "temp")
|
||||
geoipPath := filepath.Join(tempPath, "geoip.dat")
|
||||
|
||||
os.Setenv("v2ray.location.asset", tempPath)
|
||||
|
||||
common.Must(os.MkdirAll(tempPath, 0755))
|
||||
|
||||
if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||
if _, err := os.Stat(geoipPath); err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||
geoipBytes, err := common.FetchHTTPContent(geoipURL)
|
||||
common.Must(err)
|
||||
common.Must(filesystem.WriteFile(geoipPath, geoipBytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go:linkname toCidrList github.com/v2fly/v2ray-core/v4/infra/conf.toCidrList
|
||||
func toCidrList(ips StringList) ([]*router.GeoIP, error)
|
||||
|
||||
func TestToCidrList(t *testing.T) {
|
||||
t.Log(os.Getenv("v2ray.location.asset"))
|
||||
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoiptestrouter.dat"), platform.GetAssetLocation("geoip.dat")))
|
||||
|
||||
ips := StringList([]string{
|
||||
"geoip:us",
|
||||
"geoip:cn",
|
||||
"geoip:!cn",
|
||||
"ext:geoiptestrouter.dat:!cn",
|
||||
"ext:geoiptestrouter.dat:ca",
|
||||
"ext-ip:geoiptestrouter.dat:!cn",
|
||||
"ext-ip:geoiptestrouter.dat:!ca",
|
||||
})
|
||||
|
||||
_, err := toCidrList(ips)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse geoip list, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterConfig(t *testing.T) {
|
||||
createParser := func() func(string) (proto.Message, error) {
|
||||
return func(s string) (proto.Message, error) {
|
||||
|
9
infra/conf/rule/errors.generated.go
Normal file
9
infra/conf/rule/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
||||
package rule
|
||||
|
||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
394
infra/conf/rule/rule.go
Normal file
394
infra/conf/rule/rule.go
Normal file
@ -0,0 +1,394 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
||||
|
||||
func parseDomainRule(ctx context.Context, domain string) ([]*router.Domain, error) {
|
||||
cfgEnv := cfgcommon.GetConfigureLoadingEnvironment(ctx)
|
||||
geoLoader := cfgEnv.GetGeoLoader()
|
||||
|
||||
if strings.HasPrefix(domain, "geosite:") {
|
||||
list := domain[8:]
|
||||
if len(list) == 0 {
|
||||
return nil, newError("empty listname in rule: ", domain)
|
||||
}
|
||||
domains, err := geoLoader.LoadGeosite(list)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geosite: ", list).Base(err)
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
var isExtDatFile = 0
|
||||
{
|
||||
const prefix = "ext:"
|
||||
if strings.HasPrefix(domain, prefix) {
|
||||
isExtDatFile = len(prefix)
|
||||
}
|
||||
const prefixQualified = "ext-domain:"
|
||||
if strings.HasPrefix(domain, prefixQualified) {
|
||||
isExtDatFile = len(prefixQualified)
|
||||
}
|
||||
}
|
||||
|
||||
if isExtDatFile != 0 {
|
||||
kv := strings.Split(domain[isExtDatFile:], ":")
|
||||
if len(kv) != 2 {
|
||||
return nil, newError("invalid external resource: ", domain)
|
||||
}
|
||||
filename := kv[0]
|
||||
list := kv[1]
|
||||
domains, err := geoLoader.LoadGeositeWithAttr(filename, list)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load external geosite: ", list, " from ", filename).Base(err)
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
domainRule := new(router.Domain)
|
||||
switch {
|
||||
case strings.HasPrefix(domain, "regexp:"):
|
||||
regexpVal := domain[7:]
|
||||
if len(regexpVal) == 0 {
|
||||
return nil, newError("empty regexp type of rule: ", domain)
|
||||
}
|
||||
domainRule.Type = router.Domain_Regex
|
||||
domainRule.Value = regexpVal
|
||||
|
||||
case strings.HasPrefix(domain, "domain:"):
|
||||
domainName := domain[7:]
|
||||
if len(domainName) == 0 {
|
||||
return nil, newError("empty domain type of rule: ", domain)
|
||||
}
|
||||
domainRule.Type = router.Domain_Domain
|
||||
domainRule.Value = domainName
|
||||
|
||||
case strings.HasPrefix(domain, "full:"):
|
||||
fullVal := domain[5:]
|
||||
if len(fullVal) == 0 {
|
||||
return nil, newError("empty full domain type of rule: ", domain)
|
||||
}
|
||||
domainRule.Type = router.Domain_Full
|
||||
domainRule.Value = fullVal
|
||||
|
||||
case strings.HasPrefix(domain, "keyword:"):
|
||||
keywordVal := domain[8:]
|
||||
if len(keywordVal) == 0 {
|
||||
return nil, newError("empty keyword type of rule: ", domain)
|
||||
}
|
||||
domainRule.Type = router.Domain_Plain
|
||||
domainRule.Value = keywordVal
|
||||
|
||||
case strings.HasPrefix(domain, "dotless:"):
|
||||
domainRule.Type = router.Domain_Regex
|
||||
switch substr := domain[8:]; {
|
||||
case substr == "":
|
||||
domainRule.Value = "^[^.]*$"
|
||||
case !strings.Contains(substr, "."):
|
||||
domainRule.Value = "^[^.]*" + substr + "[^.]*$"
|
||||
default:
|
||||
return nil, newError("substr in dotless rule should not contain a dot: ", substr)
|
||||
}
|
||||
|
||||
default:
|
||||
domainRule.Type = router.Domain_Plain
|
||||
domainRule.Value = domain
|
||||
}
|
||||
return []*router.Domain{domainRule}, nil
|
||||
}
|
||||
|
||||
func toCidrList(ctx context.Context, ips cfgcommon.StringList) ([]*router.GeoIP, error) {
|
||||
cfgEnv := cfgcommon.GetConfigureLoadingEnvironment(ctx)
|
||||
geoLoader := cfgEnv.GetGeoLoader()
|
||||
|
||||
var geoipList []*router.GeoIP
|
||||
var customCidrs []*router.CIDR
|
||||
|
||||
for _, ip := range ips {
|
||||
if strings.HasPrefix(ip, "geoip:") {
|
||||
country := ip[6:]
|
||||
isReverseMatch := false
|
||||
if strings.HasPrefix(ip, "geoip:!") {
|
||||
country = ip[7:]
|
||||
isReverseMatch = true
|
||||
}
|
||||
if len(country) == 0 {
|
||||
return nil, newError("empty country name in rule")
|
||||
}
|
||||
geoip, err := geoLoader.LoadGeoIP(country)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geoip: ", country).Base(err)
|
||||
}
|
||||
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
CountryCode: strings.ToUpper(country),
|
||||
Cidr: geoip,
|
||||
ReverseMatch: isReverseMatch,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var isExtDatFile = 0
|
||||
{
|
||||
const prefix = "ext:"
|
||||
if strings.HasPrefix(ip, prefix) {
|
||||
isExtDatFile = len(prefix)
|
||||
}
|
||||
const prefixQualified = "ext-ip:"
|
||||
if strings.HasPrefix(ip, prefixQualified) {
|
||||
isExtDatFile = len(prefixQualified)
|
||||
}
|
||||
}
|
||||
|
||||
if isExtDatFile != 0 {
|
||||
kv := strings.Split(ip[isExtDatFile:], ":")
|
||||
if len(kv) != 2 {
|
||||
return nil, newError("invalid external resource: ", ip)
|
||||
}
|
||||
|
||||
filename := kv[0]
|
||||
country := kv[1]
|
||||
if len(filename) == 0 || len(country) == 0 {
|
||||
return nil, newError("empty filename or empty country in rule")
|
||||
}
|
||||
|
||||
isReverseMatch := false
|
||||
if strings.HasPrefix(country, "!") {
|
||||
country = country[1:]
|
||||
isReverseMatch = true
|
||||
}
|
||||
geoip, err := geoLoader.LoadIP(filename, country)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geoip: ", country, " from ", filename).Base(err)
|
||||
}
|
||||
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
CountryCode: strings.ToUpper(filename + "_" + country),
|
||||
Cidr: geoip,
|
||||
ReverseMatch: isReverseMatch,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ipRule, err := ParseIP(ip)
|
||||
if err != nil {
|
||||
return nil, newError("invalid IP: ", ip).Base(err)
|
||||
}
|
||||
customCidrs = append(customCidrs, ipRule)
|
||||
}
|
||||
|
||||
if len(customCidrs) > 0 {
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
Cidr: customCidrs,
|
||||
})
|
||||
}
|
||||
|
||||
return geoipList, nil
|
||||
}
|
||||
|
||||
func parseFieldRule(ctx context.Context, msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
type RawFieldRule struct {
|
||||
RouterRule
|
||||
Domain *cfgcommon.StringList `json:"domain"`
|
||||
Domains *cfgcommon.StringList `json:"domains"`
|
||||
IP *cfgcommon.StringList `json:"ip"`
|
||||
Port *cfgcommon.PortList `json:"port"`
|
||||
Network *cfgcommon.NetworkList `json:"network"`
|
||||
SourceIP *cfgcommon.StringList `json:"source"`
|
||||
SourcePort *cfgcommon.PortList `json:"sourcePort"`
|
||||
User *cfgcommon.StringList `json:"user"`
|
||||
InboundTag *cfgcommon.StringList `json:"inboundTag"`
|
||||
Protocols *cfgcommon.StringList `json:"protocol"`
|
||||
Attributes string `json:"attrs"`
|
||||
}
|
||||
rawFieldRule := new(RawFieldRule)
|
||||
err := json.Unmarshal(msg, rawFieldRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rule := new(router.RoutingRule)
|
||||
switch {
|
||||
case len(rawFieldRule.OutboundTag) > 0:
|
||||
rule.TargetTag = &router.RoutingRule_Tag{
|
||||
Tag: rawFieldRule.OutboundTag,
|
||||
}
|
||||
case len(rawFieldRule.BalancerTag) > 0:
|
||||
rule.TargetTag = &router.RoutingRule_BalancingTag{
|
||||
BalancingTag: rawFieldRule.BalancerTag,
|
||||
}
|
||||
default:
|
||||
return nil, newError("neither outboundTag nor balancerTag is specified in routing rule")
|
||||
}
|
||||
|
||||
if rawFieldRule.DomainMatcher != "" {
|
||||
rule.DomainMatcher = rawFieldRule.DomainMatcher
|
||||
}
|
||||
|
||||
if rawFieldRule.Domain != nil {
|
||||
for _, domain := range *rawFieldRule.Domain {
|
||||
rules, err := parseDomainRule(ctx, domain)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
||||
}
|
||||
rule.Domain = append(rule.Domain, rules...)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.Domains != nil {
|
||||
for _, domain := range *rawFieldRule.Domains {
|
||||
rules, err := parseDomainRule(ctx, domain)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
||||
}
|
||||
rule.Domain = append(rule.Domain, rules...)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.IP != nil {
|
||||
geoipList, err := toCidrList(ctx, *rawFieldRule.IP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.Geoip = geoipList
|
||||
}
|
||||
|
||||
if rawFieldRule.Port != nil {
|
||||
rule.PortList = rawFieldRule.Port.Build()
|
||||
}
|
||||
|
||||
if rawFieldRule.Network != nil {
|
||||
rule.Networks = rawFieldRule.Network.Build()
|
||||
}
|
||||
|
||||
if rawFieldRule.SourceIP != nil {
|
||||
geoipList, err := toCidrList(ctx, *rawFieldRule.SourceIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.SourceGeoip = geoipList
|
||||
}
|
||||
|
||||
if rawFieldRule.SourcePort != nil {
|
||||
rule.SourcePortList = rawFieldRule.SourcePort.Build()
|
||||
}
|
||||
|
||||
if rawFieldRule.User != nil {
|
||||
for _, s := range *rawFieldRule.User {
|
||||
rule.UserEmail = append(rule.UserEmail, s)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.InboundTag != nil {
|
||||
for _, s := range *rawFieldRule.InboundTag {
|
||||
rule.InboundTag = append(rule.InboundTag, s)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.Protocols != nil {
|
||||
for _, s := range *rawFieldRule.Protocols {
|
||||
rule.Protocol = append(rule.Protocol, s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rawFieldRule.Attributes) > 0 {
|
||||
rule.Attributes = rawFieldRule.Attributes
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func ParseRule(ctx context.Context, msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
rawRule := new(RouterRule)
|
||||
err := json.Unmarshal(msg, rawRule)
|
||||
if err != nil {
|
||||
return nil, newError("invalid router rule").Base(err)
|
||||
}
|
||||
if strings.EqualFold(rawRule.Type, "field") {
|
||||
fieldrule, err := parseFieldRule(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, newError("invalid field rule").Base(err)
|
||||
}
|
||||
return fieldrule, nil
|
||||
}
|
||||
|
||||
return nil, newError("unknown router rule type: ", rawRule.Type)
|
||||
}
|
||||
|
||||
func ParseIP(s string) (*router.CIDR, error) {
|
||||
var addr, mask string
|
||||
i := strings.Index(s, "/")
|
||||
if i < 0 {
|
||||
addr = s
|
||||
} else {
|
||||
addr = s[:i]
|
||||
mask = s[i+1:]
|
||||
}
|
||||
ip := net.ParseAddress(addr)
|
||||
switch ip.Family() {
|
||||
case net.AddressFamilyIPv4:
|
||||
bits := uint32(32)
|
||||
if len(mask) > 0 {
|
||||
bits64, err := strconv.ParseUint(mask, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newError("invalid network mask for router: ", mask).Base(err)
|
||||
}
|
||||
bits = uint32(bits64)
|
||||
}
|
||||
if bits > 32 {
|
||||
return nil, newError("invalid network mask for router: ", bits)
|
||||
}
|
||||
return &router.CIDR{
|
||||
Ip: []byte(ip.IP()),
|
||||
Prefix: bits,
|
||||
}, nil
|
||||
case net.AddressFamilyIPv6:
|
||||
bits := uint32(128)
|
||||
if len(mask) > 0 {
|
||||
bits64, err := strconv.ParseUint(mask, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newError("invalid network mask for router: ", mask).Base(err)
|
||||
}
|
||||
bits = uint32(bits64)
|
||||
}
|
||||
if bits > 128 {
|
||||
return nil, newError("invalid network mask for router: ", bits)
|
||||
}
|
||||
return &router.CIDR{
|
||||
Ip: []byte(ip.IP()),
|
||||
Prefix: bits,
|
||||
}, nil
|
||||
default:
|
||||
return nil, newError("unsupported address for router: ", s)
|
||||
}
|
||||
}
|
||||
|
||||
func ParseDomainRule(ctx context.Context, domain string) ([]*router.Domain, error) {
|
||||
return parseDomainRule(ctx, domain)
|
||||
}
|
||||
|
||||
func ToCidrList(ctx context.Context, ips cfgcommon.StringList) ([]*router.GeoIP, error) {
|
||||
return toCidrList(ctx, ips)
|
||||
}
|
||||
|
||||
type RouterRule struct {
|
||||
Type string `json:"type"`
|
||||
OutboundTag string `json:"outboundTag"`
|
||||
BalancerTag string `json:"balancerTag"`
|
||||
|
||||
DomainMatcher string `json:"domainMatcher"`
|
||||
}
|
72
infra/conf/rule/rule_test.go
Normal file
72
infra/conf/rule/rule_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
package rule_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common"
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform"
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/geodata"
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/rule"
|
||||
|
||||
_ "github.com/v2fly/v2ray-core/v4/infra/conf/geodata/standard"
|
||||
)
|
||||
|
||||
const (
|
||||
geoipURL = "https://raw.githubusercontent.com/v2fly/geoip/release/geoip.dat"
|
||||
)
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
common.Must(err)
|
||||
|
||||
tempPath := filepath.Join(wd, "..", "..", "testing", "temp")
|
||||
geoipPath := filepath.Join(tempPath, "geoip.dat")
|
||||
|
||||
os.Setenv("v2ray.location.asset", tempPath)
|
||||
|
||||
common.Must(os.MkdirAll(tempPath, 0755))
|
||||
|
||||
if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||
if _, err := os.Stat(geoipPath); err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||
geoipBytes, err := common.FetchHTTPContent(geoipURL)
|
||||
common.Must(err)
|
||||
common.Must(filesystem.WriteFile(geoipPath, geoipBytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCidrList(t *testing.T) {
|
||||
t.Log(os.Getenv("v2ray.location.asset"))
|
||||
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoiptestrouter.dat"), platform.GetAssetLocation("geoip.dat")))
|
||||
|
||||
ips := cfgcommon.StringList([]string{
|
||||
"geoip:us",
|
||||
"geoip:cn",
|
||||
"geoip:!cn",
|
||||
"ext:geoiptestrouter.dat:!cn",
|
||||
"ext:geoiptestrouter.dat:ca",
|
||||
"ext-ip:geoiptestrouter.dat:!cn",
|
||||
"ext-ip:geoiptestrouter.dat:!ca",
|
||||
})
|
||||
|
||||
cfgctx := cfgcommon.NewConfigureLoadingContext(context.Background())
|
||||
|
||||
if loader, err := geodata.GetGeoDataLoader("standard"); err == nil {
|
||||
cfgcommon.SetGeoDataLoader(cfgctx, loader)
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := rule.ToCidrList(cfgctx, ips)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse geoip list, got %s", err)
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ package conf
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||
@ -26,13 +28,13 @@ func cipherFromString(c string) shadowsocks.CipherType {
|
||||
}
|
||||
|
||||
type ShadowsocksServerConfig struct {
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
UDP bool `json:"udp"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
NetworkList *NetworkList `json:"network"`
|
||||
IVCheck bool `json:"ivCheck"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
UDP bool `json:"udp"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
NetworkList *cfgcommon.NetworkList `json:"network"`
|
||||
IVCheck bool `json:"ivCheck"`
|
||||
}
|
||||
|
||||
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
||||
@ -62,14 +64,14 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type ShadowsocksServerTarget struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Ota bool `json:"ota"`
|
||||
Level byte `json:"level"`
|
||||
IVCheck bool `json:"ivCheck"`
|
||||
Address *cfgcommon.Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Ota bool `json:"ota"`
|
||||
Level byte `json:"level"`
|
||||
IVCheck bool `json:"ivCheck"`
|
||||
}
|
||||
|
||||
type ShadowsocksClientConfig struct {
|
||||
|
@ -3,6 +3,8 @@ package conf
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||
@ -28,12 +30,12 @@ const (
|
||||
)
|
||||
|
||||
type SocksServerConfig struct {
|
||||
AuthMethod string `json:"auth"`
|
||||
Accounts []*SocksAccount `json:"accounts"`
|
||||
UDP bool `json:"udp"`
|
||||
Host *Address `json:"ip"`
|
||||
Timeout uint32 `json:"timeout"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
AuthMethod string `json:"auth"`
|
||||
Accounts []*SocksAccount `json:"accounts"`
|
||||
UDP bool `json:"udp"`
|
||||
Host *cfgcommon.Address `json:"ip"`
|
||||
Timeout uint32 `json:"timeout"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
}
|
||||
|
||||
func (v *SocksServerConfig) Build() (proto.Message, error) {
|
||||
@ -66,9 +68,9 @@ func (v *SocksServerConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type SocksRemoteConfig struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
Address *cfgcommon.Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
}
|
||||
type SocksClientConfig struct {
|
||||
Servers []*SocksRemoteConfig `json:"servers"`
|
||||
|
@ -3,6 +3,8 @@ package conf
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/transport/internet/headers/http"
|
||||
@ -57,13 +59,13 @@ func (DTLSAuthenticator) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type AuthenticatorRequest struct {
|
||||
Version string `json:"version"`
|
||||
Method string `json:"method"`
|
||||
Path StringList `json:"path"`
|
||||
Headers map[string]*StringList `json:"headers"`
|
||||
Version string `json:"version"`
|
||||
Method string `json:"method"`
|
||||
Path cfgcommon.StringList `json:"path"`
|
||||
Headers map[string]*cfgcommon.StringList `json:"headers"`
|
||||
}
|
||||
|
||||
func sortMapKeys(m map[string]*StringList) []string {
|
||||
func sortMapKeys(m map[string]*cfgcommon.StringList) []string {
|
||||
var keys []string
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
@ -133,10 +135,10 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
||||
}
|
||||
|
||||
type AuthenticatorResponse struct {
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Reason string `json:"reason"`
|
||||
Headers map[string]*StringList `json:"headers"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Reason string `json:"reason"`
|
||||
Headers map[string]*cfgcommon.StringList `json:"headers"`
|
||||
}
|
||||
|
||||
func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
||||
@ -170,8 +172,8 @@ func (c *WebSocketConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
Host *StringList `json:"host"`
|
||||
Path string `json:"path"`
|
||||
Host *cfgcommon.StringList `json:"host"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
@ -292,13 +294,13 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
Insecure bool `json:"allowInsecure"`
|
||||
Certs []*TLSCertConfig `json:"certificates"`
|
||||
ServerName string `json:"serverName"`
|
||||
ALPN *StringList `json:"alpn"`
|
||||
EnableSessionResumption bool `json:"enableSessionResumption"`
|
||||
DisableSystemRoot bool `json:"disableSystemRoot"`
|
||||
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
|
||||
Insecure bool `json:"allowInsecure"`
|
||||
Certs []*TLSCertConfig `json:"certificates"`
|
||||
ServerName string `json:"serverName"`
|
||||
ALPN *cfgcommon.StringList `json:"alpn"`
|
||||
EnableSessionResumption bool `json:"enableSessionResumption"`
|
||||
DisableSystemRoot bool `json:"disableSystemRoot"`
|
||||
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||
@ -16,11 +18,11 @@ import (
|
||||
|
||||
// TrojanServerTarget is configuration of a single trojan server
|
||||
type TrojanServerTarget struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Level byte `json:"level"`
|
||||
Address *cfgcommon.Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Level byte `json:"level"`
|
||||
}
|
||||
|
||||
// TrojanClientConfig is configuration of trojan servers
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
core "github.com/v2fly/v2ray-core/v4"
|
||||
"github.com/v2fly/v2ray-core/v4/app/dispatcher"
|
||||
"github.com/v2fly/v2ray-core/v4/app/proxyman"
|
||||
@ -58,9 +60,9 @@ func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) {
|
||||
}
|
||||
|
||||
type SniffingConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
DestOverride *StringList `json:"destOverride"`
|
||||
MetadataOnly bool `json:"metadataOnly"`
|
||||
Enabled bool `json:"enabled"`
|
||||
DestOverride *cfgcommon.StringList `json:"destOverride"`
|
||||
MetadataOnly bool `json:"metadataOnly"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
@ -148,13 +150,13 @@ func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, e
|
||||
|
||||
type InboundDetourConfig struct {
|
||||
Protocol string `json:"protocol"`
|
||||
PortRange *PortRange `json:"port"`
|
||||
ListenOn *Address `json:"listen"`
|
||||
PortRange *cfgcommon.PortRange `json:"port"`
|
||||
ListenOn *cfgcommon.Address `json:"listen"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
Tag string `json:"tag"`
|
||||
Allocation *InboundDetourAllocationConfig `json:"allocate"`
|
||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||
DomainOverride *StringList `json:"domainOverride"`
|
||||
DomainOverride *cfgcommon.StringList `json:"domainOverride"`
|
||||
SniffingConfig *SniffingConfig `json:"sniffing"`
|
||||
}
|
||||
|
||||
@ -253,13 +255,13 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
||||
}
|
||||
|
||||
type OutboundDetourConfig struct {
|
||||
Protocol string `json:"protocol"`
|
||||
SendThrough *Address `json:"sendThrough"`
|
||||
Tag string `json:"tag"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||
ProxySettings *ProxyConfig `json:"proxySettings"`
|
||||
MuxSettings *MuxConfig `json:"mux"`
|
||||
Protocol string `json:"protocol"`
|
||||
SendThrough *cfgcommon.Address `json:"sendThrough"`
|
||||
Tag string `json:"tag"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||
ProxySettings *ProxyConfig `json:"proxySettings"`
|
||||
MuxSettings *MuxConfig `json:"mux"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
@ -617,7 +619,7 @@ func (c *Config) Build() (*core.Config, error) {
|
||||
|
||||
// Backward compatibility.
|
||||
if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 {
|
||||
inbounds[0].PortRange = &PortRange{
|
||||
inbounds[0].PortRange = &cfgcommon.PortRange{
|
||||
From: uint32(c.Port),
|
||||
To: uint32(c.Port),
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||
@ -120,9 +122,9 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type VLessOutboundVnext struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
Address *cfgcommon.Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
}
|
||||
|
||||
type VLessOutboundConfig struct {
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||
@ -115,9 +117,9 @@ func (c *VMessInboundConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type VMessOutboundTarget struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
Address *cfgcommon.Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
}
|
||||
type VMessOutboundConfig struct {
|
||||
Receivers []*VMessOutboundTarget `json:"vnext"`
|
||||
|
@ -67,6 +67,9 @@ import (
|
||||
_ "github.com/v2fly/v2ray-core/v4/transport/internet/headers/wechat"
|
||||
_ "github.com/v2fly/v2ray-core/v4/transport/internet/headers/wireguard"
|
||||
|
||||
// Geo loaders
|
||||
_ "github.com/v2fly/v2ray-core/v4/infra/conf/geodata/standard"
|
||||
|
||||
// JSON config support. Choose only one from the two below.
|
||||
// The following line loads JSON from v2ctl
|
||||
// _ "github.com/v2fly/v2ray-core/v4/main/json"
|
||||
|
Loading…
Reference in New Issue
Block a user