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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -9,6 +9,8 @@ import (
|
|||||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
||||||
|
|
||||||
type StringList []string
|
type StringList []string
|
||||||
|
|
||||||
func NewStringList(raw []string) *StringList {
|
func NewStringList(raw []string) *StringList {
|
@ -1,22 +1,23 @@
|
|||||||
package conf_test
|
package cfgcommon_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common"
|
"github.com/v2fly/v2ray-core/v4/common"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||||
. "github.com/v2fly/v2ray-core/v4/infra/conf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStringListUnmarshalError(t *testing.T) {
|
func TestStringListUnmarshalError(t *testing.T) {
|
||||||
rawJSON := `1234`
|
rawJSON := `1234`
|
||||||
list := new(StringList)
|
list := new(cfgcommon.StringList)
|
||||||
err := json.Unmarshal([]byte(rawJSON), list)
|
err := json.Unmarshal([]byte(rawJSON), list)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error, but got nil")
|
t.Error("expected error, but got nil")
|
||||||
@ -25,7 +26,7 @@ func TestStringListUnmarshalError(t *testing.T) {
|
|||||||
|
|
||||||
func TestStringListLen(t *testing.T) {
|
func TestStringListLen(t *testing.T) {
|
||||||
rawJSON := `"a, b, c, d"`
|
rawJSON := `"a, b, c, d"`
|
||||||
var list StringList
|
var list cfgcommon.StringList
|
||||||
err := json.Unmarshal([]byte(rawJSON), &list)
|
err := json.Unmarshal([]byte(rawJSON), &list)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if r := cmp.Diff([]string(list), []string{"a", " b", " c", " d"}); r != "" {
|
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) {
|
func TestIPParsing(t *testing.T) {
|
||||||
rawJSON := "\"8.8.8.8\""
|
rawJSON := "\"8.8.8.8\""
|
||||||
var address Address
|
var address cfgcommon.Address
|
||||||
err := json.Unmarshal([]byte(rawJSON), &address)
|
err := json.Unmarshal([]byte(rawJSON), &address)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != "" {
|
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) {
|
func TestDomainParsing(t *testing.T) {
|
||||||
rawJSON := "\"v2fly.org\""
|
rawJSON := "\"v2fly.org\""
|
||||||
var address Address
|
var address cfgcommon.Address
|
||||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||||
if address.Domain() != "v2fly.org" {
|
if address.Domain() != "v2fly.org" {
|
||||||
t.Error("domain: ", address.Domain())
|
t.Error("domain: ", address.Domain())
|
||||||
@ -55,7 +56,7 @@ func TestDomainParsing(t *testing.T) {
|
|||||||
func TestURLParsing(t *testing.T) {
|
func TestURLParsing(t *testing.T) {
|
||||||
{
|
{
|
||||||
rawJSON := "\"https://dns.google/dns-query\""
|
rawJSON := "\"https://dns.google/dns-query\""
|
||||||
var address Address
|
var address cfgcommon.Address
|
||||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||||
if address.Domain() != "https://dns.google/dns-query" {
|
if address.Domain() != "https://dns.google/dns-query" {
|
||||||
t.Error("URL: ", address.Domain())
|
t.Error("URL: ", address.Domain())
|
||||||
@ -63,7 +64,7 @@ func TestURLParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
rawJSON := "\"https+local://dns.google/dns-query\""
|
rawJSON := "\"https+local://dns.google/dns-query\""
|
||||||
var address Address
|
var address cfgcommon.Address
|
||||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||||
if address.Domain() != "https+local://dns.google/dns-query" {
|
if address.Domain() != "https+local://dns.google/dns-query" {
|
||||||
t.Error("URL: ", address.Domain())
|
t.Error("URL: ", address.Domain())
|
||||||
@ -73,7 +74,7 @@ func TestURLParsing(t *testing.T) {
|
|||||||
|
|
||||||
func TestInvalidAddressJson(t *testing.T) {
|
func TestInvalidAddressJson(t *testing.T) {
|
||||||
rawJSON := "1234"
|
rawJSON := "1234"
|
||||||
var address Address
|
var address cfgcommon.Address
|
||||||
err := json.Unmarshal([]byte(rawJSON), &address)
|
err := json.Unmarshal([]byte(rawJSON), &address)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
@ -81,7 +82,7 @@ func TestInvalidAddressJson(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStringNetwork(t *testing.T) {
|
func TestStringNetwork(t *testing.T) {
|
||||||
var network Network
|
var network cfgcommon.Network
|
||||||
common.Must(json.Unmarshal([]byte(`"tcp"`), &network))
|
common.Must(json.Unmarshal([]byte(`"tcp"`), &network))
|
||||||
if v := network.Build(); v != net.Network_TCP {
|
if v := network.Build(); v != net.Network_TCP {
|
||||||
t.Error("network: ", v)
|
t.Error("network: ", v)
|
||||||
@ -89,7 +90,7 @@ func TestStringNetwork(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestArrayNetworkList(t *testing.T) {
|
func TestArrayNetworkList(t *testing.T) {
|
||||||
var list NetworkList
|
var list cfgcommon.NetworkList
|
||||||
common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list))
|
common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list))
|
||||||
|
|
||||||
nlist := list.Build()
|
nlist := list.Build()
|
||||||
@ -102,7 +103,7 @@ func TestArrayNetworkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStringNetworkList(t *testing.T) {
|
func TestStringNetworkList(t *testing.T) {
|
||||||
var list NetworkList
|
var list cfgcommon.NetworkList
|
||||||
common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list))
|
common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list))
|
||||||
|
|
||||||
nlist := list.Build()
|
nlist := list.Build()
|
||||||
@ -115,7 +116,7 @@ func TestStringNetworkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidNetworkJson(t *testing.T) {
|
func TestInvalidNetworkJson(t *testing.T) {
|
||||||
var list NetworkList
|
var list cfgcommon.NetworkList
|
||||||
err := json.Unmarshal([]byte("0"), &list)
|
err := json.Unmarshal([]byte("0"), &list)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
@ -123,10 +124,10 @@ func TestInvalidNetworkJson(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIntPort(t *testing.T) {
|
func TestIntPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange cfgcommon.PortRange
|
||||||
common.Must(json.Unmarshal([]byte("1234"), &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,
|
From: 1234, To: 1234,
|
||||||
}); r != "" {
|
}); r != "" {
|
||||||
t.Error(r)
|
t.Error(r)
|
||||||
@ -134,7 +135,7 @@ func TestIntPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOverRangeIntPort(t *testing.T) {
|
func TestOverRangeIntPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange cfgcommon.PortRange
|
||||||
err := json.Unmarshal([]byte("70000"), &portRange)
|
err := json.Unmarshal([]byte("70000"), &portRange)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
@ -149,10 +150,10 @@ func TestOverRangeIntPort(t *testing.T) {
|
|||||||
func TestEnvPort(t *testing.T) {
|
func TestEnvPort(t *testing.T) {
|
||||||
common.Must(os.Setenv("PORT", "1234"))
|
common.Must(os.Setenv("PORT", "1234"))
|
||||||
|
|
||||||
var portRange PortRange
|
var portRange cfgcommon.PortRange
|
||||||
common.Must(json.Unmarshal([]byte("\"env:PORT\""), &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,
|
From: 1234, To: 1234,
|
||||||
}); r != "" {
|
}); r != "" {
|
||||||
t.Error(r)
|
t.Error(r)
|
||||||
@ -160,10 +161,10 @@ func TestEnvPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSingleStringPort(t *testing.T) {
|
func TestSingleStringPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange cfgcommon.PortRange
|
||||||
common.Must(json.Unmarshal([]byte("\"1234\""), &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,
|
From: 1234, To: 1234,
|
||||||
}); r != "" {
|
}); r != "" {
|
||||||
t.Error(r)
|
t.Error(r)
|
||||||
@ -171,10 +172,10 @@ func TestSingleStringPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStringPairPort(t *testing.T) {
|
func TestStringPairPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange cfgcommon.PortRange
|
||||||
common.Must(json.Unmarshal([]byte("\"1234-5678\""), &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,
|
From: 1234, To: 5678,
|
||||||
}); r != "" {
|
}); r != "" {
|
||||||
t.Error(r)
|
t.Error(r)
|
||||||
@ -182,7 +183,7 @@ func TestStringPairPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOverRangeStringPort(t *testing.T) {
|
func TestOverRangeStringPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange cfgcommon.PortRange
|
||||||
err := json.Unmarshal([]byte("\"65536\""), &portRange)
|
err := json.Unmarshal([]byte("\"65536\""), &portRange)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
@ -205,7 +206,7 @@ func TestOverRangeStringPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserParsing(t *testing.T) {
|
func TestUserParsing(t *testing.T) {
|
||||||
user := new(User)
|
user := new(cfgcommon.User)
|
||||||
common.Must(json.Unmarshal([]byte(`{
|
common.Must(json.Unmarshal([]byte(`{
|
||||||
"id": "96edb838-6d68-42ef-a933-25f7ac3a9d09",
|
"id": "96edb838-6d68-42ef-a933-25f7ac3a9d09",
|
||||||
"email": "love@v2fly.org",
|
"email": "love@v2fly.org",
|
||||||
@ -223,7 +224,7 @@ func TestUserParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidUserJson(t *testing.T) {
|
func TestInvalidUserJson(t *testing.T) {
|
||||||
user := new(User)
|
user := new(cfgcommon.User)
|
||||||
err := json.Unmarshal([]byte(`{"email": 1234}`), user)
|
err := json.Unmarshal([]byte(`{"email": 1234}`), user)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
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
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"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/dns"
|
||||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
"github.com/v2fly/v2ray-core/v4/app/router"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NameServerConfig struct {
|
type NameServerConfig struct {
|
||||||
Address *Address
|
Address *cfgcommon.Address
|
||||||
ClientIP *Address
|
ClientIP *cfgcommon.Address
|
||||||
Port uint16
|
Port uint16
|
||||||
SkipFallback bool
|
SkipFallback bool
|
||||||
Domains []string
|
Domains []string
|
||||||
ExpectIPs StringList
|
ExpectIPs cfgcommon.StringList
|
||||||
|
|
||||||
|
cfgctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
||||||
var address Address
|
var address cfgcommon.Address
|
||||||
if err := json.Unmarshal(data, &address); err == nil {
|
if err := json.Unmarshal(data, &address); err == nil {
|
||||||
c.Address = &address
|
c.Address = &address
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var advanced struct {
|
var advanced struct {
|
||||||
Address *Address `json:"address"`
|
Address *cfgcommon.Address `json:"address"`
|
||||||
ClientIP *Address `json:"clientIp"`
|
ClientIP *cfgcommon.Address `json:"clientIp"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
SkipFallback bool `json:"skipFallback"`
|
SkipFallback bool `json:"skipFallback"`
|
||||||
Domains []string `json:"domains"`
|
Domains []string `json:"domains"`
|
||||||
ExpectIPs StringList `json:"expectIps"`
|
ExpectIPs cfgcommon.StringList `json:"expectIps"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &advanced); err == nil {
|
if err := json.Unmarshal(data, &advanced); err == nil {
|
||||||
c.Address = advanced.Address
|
c.Address = advanced.Address
|
||||||
@ -63,6 +70,8 @@ func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||||
|
cfgctx := c.cfgctx
|
||||||
|
|
||||||
if c.Address == nil {
|
if c.Address == nil {
|
||||||
return nil, newError("NameServer address is not specified.")
|
return nil, newError("NameServer address is not specified.")
|
||||||
}
|
}
|
||||||
@ -71,7 +80,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
|||||||
var originalRules []*dns.NameServer_OriginalRule
|
var originalRules []*dns.NameServer_OriginalRule
|
||||||
|
|
||||||
for _, rule := range c.Domains {
|
for _, rule := range c.Domains {
|
||||||
parsedDomain, err := parseDomainRule(rule)
|
parsedDomain, err := rule2.ParseDomainRule(cfgctx, rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid domain rule: ", rule).Base(err)
|
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 {
|
if err != nil {
|
||||||
return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
|
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 {
|
type DNSConfig struct {
|
||||||
Servers []*NameServerConfig `json:"servers"`
|
Servers []*NameServerConfig `json:"servers"`
|
||||||
Hosts map[string]*HostAddress `json:"hosts"`
|
Hosts map[string]*HostAddress `json:"hosts"`
|
||||||
ClientIP *Address `json:"clientIp"`
|
ClientIP *cfgcommon.Address `json:"clientIp"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
QueryStrategy string `json:"queryStrategy"`
|
QueryStrategy string `json:"queryStrategy"`
|
||||||
DisableCache bool `json:"disableCache"`
|
DisableCache bool `json:"disableCache"`
|
||||||
DisableFallback bool `json:"disableFallback"`
|
DisableFallback bool `json:"disableFallback"`
|
||||||
|
|
||||||
|
GeoLoader string `json:"geoLoader"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HostAddress struct {
|
type HostAddress struct {
|
||||||
addr *Address
|
addr *cfgcommon.Address
|
||||||
addrs []*Address
|
addrs []*cfgcommon.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||||
func (h *HostAddress) UnmarshalJSON(data []byte) error {
|
func (h *HostAddress) UnmarshalJSON(data []byte) error {
|
||||||
addr := new(Address)
|
addr := new(cfgcommon.Address)
|
||||||
var addrs []*Address
|
var addrs []*cfgcommon.Address
|
||||||
switch {
|
switch {
|
||||||
case json.Unmarshal(data, &addr) == nil:
|
case json.Unmarshal(data, &addr) == nil:
|
||||||
h.addr = addr
|
h.addr = addr
|
||||||
@ -181,6 +192,21 @@ func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
|
|||||||
|
|
||||||
// Build implements Buildable
|
// Build implements Buildable
|
||||||
func (c *DNSConfig) Build() (*dns.Config, error) {
|
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{
|
config := &dns.Config{
|
||||||
Tag: c.Tag,
|
Tag: c.Tag,
|
||||||
DisableCache: c.DisableCache,
|
DisableCache: c.DisableCache,
|
||||||
@ -205,6 +231,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, server := range c.Servers {
|
for _, server := range c.Servers {
|
||||||
|
server.cfgctx = cfgctx
|
||||||
ns, err := server.Build()
|
ns, err := server.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to build nameserver").Base(err)
|
return nil, newError("failed to build nameserver").Base(err)
|
||||||
@ -238,7 +265,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
return nil, newError("empty geosite rule: ", domain)
|
return nil, newError("empty geosite rule: ", domain)
|
||||||
}
|
}
|
||||||
geositeList, err := loadGeosite(listName)
|
geositeList, err := geoLoader.LoadGeosite(listName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to load geosite: ", listName).Base(err)
|
return nil, newError("failed to load geosite: ", listName).Base(err)
|
||||||
}
|
}
|
||||||
@ -299,7 +326,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
}
|
}
|
||||||
filename := kv[0]
|
filename := kv[0]
|
||||||
list := kv[1]
|
list := kv[1]
|
||||||
geositeList, err := loadGeositeWithAttr(filename, list)
|
geositeList, err := geoLoader.LoadGeositeWithAttr(filename, list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
|
return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,16 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
"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/common/net"
|
||||||
"github.com/v2fly/v2ray-core/v4/proxy/dns"
|
"github.com/v2fly/v2ray-core/v4/proxy/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DNSOutboundConfig struct {
|
type DNSOutboundConfig struct {
|
||||||
Network Network `json:"network"`
|
Network cfgcommon.Network `json:"network"`
|
||||||
Address *Address `json:"address"`
|
Address *cfgcommon.Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSOutboundConfig) Build() (proto.Message, error) {
|
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"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
"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"
|
||||||
|
|
||||||
|
_ "github.com/v2fly/v2ray-core/v4/infra/conf/geodata/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -2,17 +2,18 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/proxy/dokodemo"
|
"github.com/v2fly/v2ray-core/v4/proxy/dokodemo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DokodemoConfig struct {
|
type DokodemoConfig struct {
|
||||||
Host *Address `json:"address"`
|
Host *cfgcommon.Address `json:"address"`
|
||||||
PortValue uint16 `json:"port"`
|
PortValue uint16 `json:"port"`
|
||||||
NetworkList *NetworkList `json:"network"`
|
NetworkList *cfgcommon.NetworkList `json:"network"`
|
||||||
TimeoutValue uint32 `json:"timeout"`
|
TimeoutValue uint32 `json:"timeout"`
|
||||||
Redirect bool `json:"followRedirect"`
|
Redirect bool `json:"followRedirect"`
|
||||||
UserLevel uint32 `json:"userLevel"`
|
UserLevel uint32 `json:"userLevel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DokodemoConfig) Build() (proto.Message, error) {
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||||
@ -47,9 +49,9 @@ func (c *HTTPServerConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPRemoteConfig struct {
|
type HTTPRemoteConfig struct {
|
||||||
Address *Address `json:"address"`
|
Address *cfgcommon.Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
Users []json.RawMessage `json:"users"`
|
Users []json.RawMessage `json:"users"`
|
||||||
}
|
}
|
||||||
type HTTPClientConfig struct {
|
type HTTPClientConfig struct {
|
||||||
Servers []*HTTPRemoteConfig `json:"servers"`
|
Servers []*HTTPRemoteConfig `json:"servers"`
|
||||||
|
@ -2,6 +2,7 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/app/observatory"
|
"github.com/v2fly/v2ray-core/v4/app/observatory"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"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/app/router"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RouterRulesConfig struct {
|
type RouterRulesConfig struct {
|
||||||
@ -24,9 +24,9 @@ type StrategyConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BalancingRule struct {
|
type BalancingRule struct {
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Selectors StringList `json:"selector"`
|
Selectors cfgcommon.StringList `json:"selector"`
|
||||||
Strategy StrategyConfig `json:"strategy"`
|
Strategy StrategyConfig `json:"strategy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BalancingRule) Build() (*router.BalancingRule, error) {
|
func (r *BalancingRule) Build() (*router.BalancingRule, error) {
|
||||||
@ -61,6 +61,7 @@ type RouterConfig struct {
|
|||||||
Balancers []*BalancingRule `json:"balancers"`
|
Balancers []*BalancingRule `json:"balancers"`
|
||||||
|
|
||||||
DomainMatcher string `json:"domainMatcher"`
|
DomainMatcher string `json:"domainMatcher"`
|
||||||
|
GeoLoader string `json:"geoLoader"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
|
func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
|
||||||
@ -87,6 +88,18 @@ func (c *RouterConfig) Build() (*router.Config, error) {
|
|||||||
config := new(router.Config)
|
config := new(router.Config)
|
||||||
config.DomainStrategy = c.getDomainStrategy()
|
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
|
var rawRuleList []json.RawMessage
|
||||||
if c != nil {
|
if c != nil {
|
||||||
rawRuleList = c.RuleList
|
rawRuleList = c.RuleList
|
||||||
@ -97,7 +110,7 @@ func (c *RouterConfig) Build() (*router.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, rawRule := range rawRuleList {
|
for _, rawRule := range rawRuleList {
|
||||||
rule, err := ParseRule(rawRule)
|
rule, err := rule2.ParseRule(cfgctx, rawRule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -117,499 +130,3 @@ func (c *RouterConfig) Build() (*router.Config, error) {
|
|||||||
}
|
}
|
||||||
return config, nil
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/app/router"
|
"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/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"
|
. "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) {
|
func TestRouterConfig(t *testing.T) {
|
||||||
createParser := func() func(string) (proto.Message, error) {
|
createParser := func() func(string) (proto.Message, error) {
|
||||||
return func(s 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 (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||||
@ -26,13 +28,13 @@ func cipherFromString(c string) shadowsocks.CipherType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ShadowsocksServerConfig struct {
|
type ShadowsocksServerConfig struct {
|
||||||
Cipher string `json:"method"`
|
Cipher string `json:"method"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
UDP bool `json:"udp"`
|
UDP bool `json:"udp"`
|
||||||
Level byte `json:"level"`
|
Level byte `json:"level"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
NetworkList *NetworkList `json:"network"`
|
NetworkList *cfgcommon.NetworkList `json:"network"`
|
||||||
IVCheck bool `json:"ivCheck"`
|
IVCheck bool `json:"ivCheck"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
||||||
@ -62,14 +64,14 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ShadowsocksServerTarget struct {
|
type ShadowsocksServerTarget struct {
|
||||||
Address *Address `json:"address"`
|
Address *cfgcommon.Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
Cipher string `json:"method"`
|
Cipher string `json:"method"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Ota bool `json:"ota"`
|
Ota bool `json:"ota"`
|
||||||
Level byte `json:"level"`
|
Level byte `json:"level"`
|
||||||
IVCheck bool `json:"ivCheck"`
|
IVCheck bool `json:"ivCheck"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShadowsocksClientConfig struct {
|
type ShadowsocksClientConfig struct {
|
||||||
|
@ -3,6 +3,8 @@ package conf
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||||
@ -28,12 +30,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SocksServerConfig struct {
|
type SocksServerConfig struct {
|
||||||
AuthMethod string `json:"auth"`
|
AuthMethod string `json:"auth"`
|
||||||
Accounts []*SocksAccount `json:"accounts"`
|
Accounts []*SocksAccount `json:"accounts"`
|
||||||
UDP bool `json:"udp"`
|
UDP bool `json:"udp"`
|
||||||
Host *Address `json:"ip"`
|
Host *cfgcommon.Address `json:"ip"`
|
||||||
Timeout uint32 `json:"timeout"`
|
Timeout uint32 `json:"timeout"`
|
||||||
UserLevel uint32 `json:"userLevel"`
|
UserLevel uint32 `json:"userLevel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *SocksServerConfig) Build() (proto.Message, error) {
|
func (v *SocksServerConfig) Build() (proto.Message, error) {
|
||||||
@ -66,9 +68,9 @@ func (v *SocksServerConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SocksRemoteConfig struct {
|
type SocksRemoteConfig struct {
|
||||||
Address *Address `json:"address"`
|
Address *cfgcommon.Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
Users []json.RawMessage `json:"users"`
|
Users []json.RawMessage `json:"users"`
|
||||||
}
|
}
|
||||||
type SocksClientConfig struct {
|
type SocksClientConfig struct {
|
||||||
Servers []*SocksRemoteConfig `json:"servers"`
|
Servers []*SocksRemoteConfig `json:"servers"`
|
||||||
|
@ -3,6 +3,8 @@ package conf
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/transport/internet/headers/http"
|
"github.com/v2fly/v2ray-core/v4/transport/internet/headers/http"
|
||||||
@ -57,13 +59,13 @@ func (DTLSAuthenticator) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthenticatorRequest struct {
|
type AuthenticatorRequest struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Path StringList `json:"path"`
|
Path cfgcommon.StringList `json:"path"`
|
||||||
Headers map[string]*StringList `json:"headers"`
|
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
|
var keys []string
|
||||||
for key := range m {
|
for key := range m {
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
@ -133,10 +135,10 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthenticatorResponse struct {
|
type AuthenticatorResponse struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
Headers map[string]*StringList `json:"headers"`
|
Headers map[string]*cfgcommon.StringList `json:"headers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
|
func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
"github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
|
||||||
@ -170,8 +172,8 @@ func (c *WebSocketConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
Host *StringList `json:"host"`
|
Host *cfgcommon.StringList `json:"host"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@ -292,13 +294,13 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
Insecure bool `json:"allowInsecure"`
|
Insecure bool `json:"allowInsecure"`
|
||||||
Certs []*TLSCertConfig `json:"certificates"`
|
Certs []*TLSCertConfig `json:"certificates"`
|
||||||
ServerName string `json:"serverName"`
|
ServerName string `json:"serverName"`
|
||||||
ALPN *StringList `json:"alpn"`
|
ALPN *cfgcommon.StringList `json:"alpn"`
|
||||||
EnableSessionResumption bool `json:"enableSessionResumption"`
|
EnableSessionResumption bool `json:"enableSessionResumption"`
|
||||||
DisableSystemRoot bool `json:"disableSystemRoot"`
|
DisableSystemRoot bool `json:"disableSystemRoot"`
|
||||||
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
|
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||||
@ -16,11 +18,11 @@ import (
|
|||||||
|
|
||||||
// TrojanServerTarget is configuration of a single trojan server
|
// TrojanServerTarget is configuration of a single trojan server
|
||||||
type TrojanServerTarget struct {
|
type TrojanServerTarget struct {
|
||||||
Address *Address `json:"address"`
|
Address *cfgcommon.Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Level byte `json:"level"`
|
Level byte `json:"level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrojanClientConfig is configuration of trojan servers
|
// TrojanClientConfig is configuration of trojan servers
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
core "github.com/v2fly/v2ray-core/v4"
|
core "github.com/v2fly/v2ray-core/v4"
|
||||||
"github.com/v2fly/v2ray-core/v4/app/dispatcher"
|
"github.com/v2fly/v2ray-core/v4/app/dispatcher"
|
||||||
"github.com/v2fly/v2ray-core/v4/app/proxyman"
|
"github.com/v2fly/v2ray-core/v4/app/proxyman"
|
||||||
@ -58,9 +60,9 @@ func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SniffingConfig struct {
|
type SniffingConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
DestOverride *StringList `json:"destOverride"`
|
DestOverride *cfgcommon.StringList `json:"destOverride"`
|
||||||
MetadataOnly bool `json:"metadataOnly"`
|
MetadataOnly bool `json:"metadataOnly"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@ -148,13 +150,13 @@ func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, e
|
|||||||
|
|
||||||
type InboundDetourConfig struct {
|
type InboundDetourConfig struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
PortRange *PortRange `json:"port"`
|
PortRange *cfgcommon.PortRange `json:"port"`
|
||||||
ListenOn *Address `json:"listen"`
|
ListenOn *cfgcommon.Address `json:"listen"`
|
||||||
Settings *json.RawMessage `json:"settings"`
|
Settings *json.RawMessage `json:"settings"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Allocation *InboundDetourAllocationConfig `json:"allocate"`
|
Allocation *InboundDetourAllocationConfig `json:"allocate"`
|
||||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||||
DomainOverride *StringList `json:"domainOverride"`
|
DomainOverride *cfgcommon.StringList `json:"domainOverride"`
|
||||||
SniffingConfig *SniffingConfig `json:"sniffing"`
|
SniffingConfig *SniffingConfig `json:"sniffing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,13 +255,13 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OutboundDetourConfig struct {
|
type OutboundDetourConfig struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
SendThrough *Address `json:"sendThrough"`
|
SendThrough *cfgcommon.Address `json:"sendThrough"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Settings *json.RawMessage `json:"settings"`
|
Settings *json.RawMessage `json:"settings"`
|
||||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||||
ProxySettings *ProxyConfig `json:"proxySettings"`
|
ProxySettings *ProxyConfig `json:"proxySettings"`
|
||||||
MuxSettings *MuxConfig `json:"mux"`
|
MuxSettings *MuxConfig `json:"mux"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@ -617,7 +619,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
|
|
||||||
// Backward compatibility.
|
// Backward compatibility.
|
||||||
if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 {
|
if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 {
|
||||||
inbounds[0].PortRange = &PortRange{
|
inbounds[0].PortRange = &cfgcommon.PortRange{
|
||||||
From: uint32(c.Port),
|
From: uint32(c.Port),
|
||||||
To: uint32(c.Port),
|
To: uint32(c.Port),
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/net"
|
"github.com/v2fly/v2ray-core/v4/common/net"
|
||||||
@ -120,9 +122,9 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VLessOutboundVnext struct {
|
type VLessOutboundVnext struct {
|
||||||
Address *Address `json:"address"`
|
Address *cfgcommon.Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
Users []json.RawMessage `json:"users"`
|
Users []json.RawMessage `json:"users"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VLessOutboundConfig struct {
|
type VLessOutboundConfig struct {
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
"github.com/v2fly/v2ray-core/v4/common/protocol"
|
||||||
@ -115,9 +117,9 @@ func (c *VMessInboundConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VMessOutboundTarget struct {
|
type VMessOutboundTarget struct {
|
||||||
Address *Address `json:"address"`
|
Address *cfgcommon.Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
Users []json.RawMessage `json:"users"`
|
Users []json.RawMessage `json:"users"`
|
||||||
}
|
}
|
||||||
type VMessOutboundConfig struct {
|
type VMessOutboundConfig struct {
|
||||||
Receivers []*VMessOutboundTarget `json:"vnext"`
|
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/wechat"
|
||||||
_ "github.com/v2fly/v2ray-core/v4/transport/internet/headers/wireguard"
|
_ "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.
|
// JSON config support. Choose only one from the two below.
|
||||||
// The following line loads JSON from v2ctl
|
// The following line loads JSON from v2ctl
|
||||||
// _ "github.com/v2fly/v2ray-core/v4/main/json"
|
// _ "github.com/v2fly/v2ray-core/v4/main/json"
|
||||||
|
Loading…
Reference in New Issue
Block a user