mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-22 10:08:15 -05:00
396 lines
10 KiB
Go
396 lines
10 KiB
Go
package rule
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/v2fly/v2ray-core/v5/app/router"
|
|
"github.com/v2fly/v2ray-core/v5/app/router/routercommon"
|
|
"github.com/v2fly/v2ray-core/v5/common/net"
|
|
"github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon"
|
|
)
|
|
|
|
//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
|
|
|
|
func parseDomainRule(ctx context.Context, domain string) ([]*routercommon.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
|
|
}
|
|
|
|
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(routercommon.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 = routercommon.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 = routercommon.Domain_RootDomain
|
|
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 = routercommon.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 = routercommon.Domain_Plain
|
|
domainRule.Value = keywordVal
|
|
|
|
case strings.HasPrefix(domain, "dotless:"):
|
|
domainRule.Type = routercommon.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 = routercommon.Domain_Plain
|
|
domainRule.Value = domain
|
|
}
|
|
return []*routercommon.Domain{domainRule}, nil
|
|
}
|
|
|
|
func toCidrList(ctx context.Context, ips cfgcommon.StringList) ([]*routercommon.GeoIP, error) {
|
|
cfgEnv := cfgcommon.GetConfigureLoadingEnvironment(ctx)
|
|
geoLoader := cfgEnv.GetGeoLoader()
|
|
|
|
var geoipList []*routercommon.GeoIP
|
|
var customCidrs []*routercommon.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, &routercommon.GeoIP{
|
|
CountryCode: strings.ToUpper(country),
|
|
Cidr: geoip,
|
|
InverseMatch: isReverseMatch,
|
|
})
|
|
|
|
continue
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
isInverseMatch := false
|
|
if strings.HasPrefix(country, "!") {
|
|
country = country[1:]
|
|
isInverseMatch = 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, &routercommon.GeoIP{
|
|
CountryCode: strings.ToUpper(filename + "_" + country),
|
|
Cidr: geoip,
|
|
InverseMatch: isInverseMatch,
|
|
})
|
|
|
|
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, &routercommon.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) (*routercommon.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 &routercommon.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 &routercommon.CIDR{
|
|
Ip: []byte(ip.IP()),
|
|
Prefix: bits,
|
|
}, nil
|
|
default:
|
|
return nil, newError("unsupported address for router: ", s)
|
|
}
|
|
}
|
|
|
|
func ParseDomainRule(ctx context.Context, domain string) ([]*routercommon.Domain, error) {
|
|
return parseDomainRule(ctx, domain)
|
|
}
|
|
|
|
func ToCidrList(ctx context.Context, ips cfgcommon.StringList) ([]*routercommon.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"`
|
|
}
|