feat: support subscription tag and subtag filter (#13)

This commit is contained in:
mzz
2023-02-10 11:04:16 +08:00
committed by GitHub
parent 636b749776
commit c54a5a0c30
9 changed files with 89 additions and 37 deletions

View File

@ -121,5 +121,4 @@ See [example.dae](https://github.com/v2rayA/dae/blob/main/example.dae).
But the problem is, after the Linux network stack, before entering the network card, we modify the source IP of this packet, causing the Linux network stack to only make a simple checksum, and the NIC also assumes that this packet is not sent from local, so no further checksum completing.
1. MACv2 extension extraction.
1. Log to userspace.
1. Subscription section supports key. And support to filter by subscription key.
1. ...

View File

@ -94,7 +94,7 @@ func resolveFile(u *url.URL, configDir string) (b []byte, err error) {
if err = common.EnsureFileInSubDir(path, configDir); err != nil {
return nil, err
}
/// Read and resolve
/// Read and resolve.
f, err := os.Open(path)
if err != nil {
return nil, err
@ -131,10 +131,25 @@ func resolveFile(u *url.URL, configDir string) (b []byte, err error) {
return bytes.TrimSpace(b), err
}
func ResolveSubscription(log *logrus.Logger, configDir string, subscription string) (nodes []string, err error) {
func ResolveSubscription(log *logrus.Logger, configDir string, subscription string) (tag string, nodes []string, err error) {
/// Get tag.
iColon := strings.Index(subscription, ":")
if iColon == -1 {
goto parseUrl
}
// If first colon is like "://" in "scheme://linkbody", no tag is present.
if strings.HasPrefix(subscription[iColon:], "://") {
goto parseUrl
}
// Else tag is the part before colon.
tag = subscription[:iColon]
subscription = subscription[iColon+1:]
/// Parse url.
parseUrl:
u, err := url.Parse(subscription)
if err != nil {
return nil, fmt.Errorf("failed to parse subscription \"%v\": %w", subscription, err)
return tag, nil, fmt.Errorf("failed to parse subscription \"%v\": %w", subscription, err)
}
log.Debugf("ResolveSubscription: %v", subscription)
var (
@ -145,25 +160,25 @@ func ResolveSubscription(log *logrus.Logger, configDir string, subscription stri
case "file":
b, err = resolveFile(u, configDir)
if err != nil {
return nil, err
return "", nil, err
}
goto resolve
default:
}
resp, err = http.Get(subscription)
if err != nil {
return nil, err
return "", nil, err
}
defer resp.Body.Close()
b, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
return "", nil, err
}
resolve:
if nodes, err = resolveSubscriptionAsSIP008(log, b); err == nil {
return nodes, nil
return tag, nodes, nil
} else {
log.Debugln(err)
}
return resolveSubscriptionAsBase64(log, b), nil
return tag, resolveSubscriptionAsBase64(log, b), nil
}

View File

@ -54,17 +54,22 @@ func init() {
func Run(log *logrus.Logger, param *config.Params) (err error) {
/// Get tag -> nodeList mapping.
tagToNodeList := map[string][]string{}
if len(param.Node) > 0 {
tagToNodeList[""] = append(tagToNodeList[""], param.Node...)
}
// Resolve subscriptions to nodes.
nodeList := make([]string, len(param.Node))
copy(nodeList, param.Node)
for _, sub := range param.Subscription {
nodes, err := internal.ResolveSubscription(log, filepath.Dir(cfgFile), sub)
tag, nodes, err := internal.ResolveSubscription(log, filepath.Dir(cfgFile), sub)
if err != nil {
log.Warnf(`failed to resolve subscription "%v": %v`, sub, err)
}
nodeList = append(nodeList, nodes...)
if len(nodes) > 0 {
tagToNodeList[tag] = append(tagToNodeList[tag], nodes...)
}
}
if len(nodeList) == 0 {
if len(tagToNodeList) == 0 {
return fmt.Errorf("no node found, which could because all subscription resolving failed")
}
@ -75,7 +80,7 @@ func Run(log *logrus.Logger, param *config.Params) (err error) {
// New ControlPlane.
t, err := control.NewControlPlane(
log,
nodeList,
tagToNodeList,
param.Group,
&param.Routing,
&param.Global,

View File

@ -14,36 +14,47 @@ import (
)
const (
FilterInput_Name = "name"
FilterInput_Link = "link"
FilterInput_Name = "name"
FilterInput_SubscriptionTag = "subtag"
FilterInput_Link = "link"
)
const (
FilterKey_Name_Regex = "regex"
FilterKey_Name_Keyword = "keyword"
FilterInput_SubscriptionTag_Regex = "regex"
)
type DialerSet struct {
Dialers []*dialer.Dialer
dialers []*dialer.Dialer
nodeToTagMap map[*dialer.Dialer]string
}
func NewDialerSetFromLinks(option *dialer.GlobalOption, nodes []string) *DialerSet {
s := &DialerSet{Dialers: make([]*dialer.Dialer, 0, len(nodes))}
for _, node := range nodes {
d, err := dialer.NewFromLink(option, dialer.InstanceOption{CheckEnabled: false}, node)
if err != nil {
option.Log.Infof("failed to parse node: %v: %v", node, err)
continue
func NewDialerSetFromLinks(option *dialer.GlobalOption, tagToNodeList map[string][]string) *DialerSet {
s := &DialerSet{
dialers: make([]*dialer.Dialer, 0),
nodeToTagMap: make(map[*dialer.Dialer]string),
}
for subscriptionTag, nodes := range tagToNodeList {
for _, node := range nodes {
d, err := dialer.NewFromLink(option, dialer.InstanceOption{CheckEnabled: false}, node)
if err != nil {
option.Log.Infof("failed to parse node: %v: %v", node, err)
continue
}
s.dialers = append(s.dialers, d)
s.nodeToTagMap[d] = subscriptionTag
}
s.Dialers = append(s.Dialers, d)
}
return s
}
func filterHit(dialer *dialer.Dialer, filters []*config_parser.Function) (hit bool, err error) {
func (s *DialerSet) filterHit(dialer *dialer.Dialer, filters []*config_parser.Function) (hit bool, err error) {
// Example
// filter: name(regex:'^.*hk.*$', keyword:'sg') && name(keyword:'disney')
// filter: !name(regex: 'HK|TW|SG') && name(keyword: disney)
// filter: subtag(my_sub, regex:^my_, regex:my_)
// And
for _, filter := range filters {
@ -72,6 +83,28 @@ func filterHit(dialer *dialer.Dialer, filters []*config_parser.Function) (hit bo
return false, fmt.Errorf(`unsupported filter key "%v" in "filter: %v()"`, param.Key, filter.Name)
}
}
case FilterInput_SubscriptionTag:
// Or
for _, param := range filter.Params {
switch param.Key {
case FilterInput_SubscriptionTag_Regex:
matched, _ := regexp.MatchString(param.Val, s.nodeToTagMap[dialer])
//logrus.Warnln(param.Val, matched, dialer.Name())
if matched {
subFilterHit = true
break
}
case "":
// Full
if s.nodeToTagMap[dialer] == param.Val {
subFilterHit = true
break
}
default:
return false, fmt.Errorf(`unsupported filter key "%v" in "filter: %v()"`, param.Key, filter.Name)
}
}
default:
return false, fmt.Errorf(`unsupported filter input type: "%v"`, filter.Name)
}
@ -84,8 +117,8 @@ func filterHit(dialer *dialer.Dialer, filters []*config_parser.Function) (hit bo
}
func (s *DialerSet) Filter(filters []*config_parser.Function) (dialers []*dialer.Dialer, err error) {
for _, d := range s.Dialers {
hit, err := filterHit(d, filters)
for _, d := range s.dialers {
hit, err := s.filterHit(d, filters)
if err != nil {
return nil, err
}

View File

@ -56,7 +56,7 @@ type ControlPlane struct {
func NewControlPlane(
log *logrus.Logger,
nodes []string,
tagToNodeList map[string][]string,
groups []config.Group,
routingA *config.Routing,
global *config.Global,
@ -214,7 +214,7 @@ func NewControlPlane(
}
// Filter out groups.
dialerSet := outbound.NewDialerSetFromLinks(option, nodes)
dialerSet := outbound.NewDialerSetFromLinks(option, tagToNodeList)
for _, group := range groups {
// Parse policy.
policy, err := outbound.NewDialerSelectionPolicyFromGroupParam(&group.Param)
@ -304,7 +304,7 @@ func NewControlPlane(
/// DNS upstream
c.dnsUpstream.FinishInitCallback = c.finishInitDnsUpstreamResolve
// Try to invoke once to avoid dns leaking at the very beginning.
_, _ = c.dnsUpstream.Upstream()
_, _ = c.dnsUpstream.GetUpstream()
return c, nil
}

View File

@ -106,7 +106,7 @@ type DnsUpstreamRaw struct {
init bool
}
func (u *DnsUpstreamRaw) Upstream() (_ *DnsUpstream, err error) {
func (u *DnsUpstreamRaw) GetUpstream() (_ *DnsUpstream, err error) {
u.mu.Lock()
defer u.mu.Unlock()
if !u.init {

View File

@ -179,7 +179,7 @@ func (c *ControlPlane) handlePkt(data []byte, src, dst netip.AddrPort, outboundI
// For DNS request, modify dst to dns upstream.
// NOTICE: We might modify l4proto and ipversion.
dnsUpstream, err := c.dnsUpstream.Upstream()
dnsUpstream, err := c.dnsUpstream.GetUpstream()
if err != nil {
return err
}

2
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/json-iterator/go v1.1.12
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/mzz2017/softwind v0.0.0-20230209162720-f6e9c7790fb1
github.com/mzz2017/softwind v0.0.0-20230210023945-506610874f4c
github.com/safchain/ethtool v0.0.0-20230116090318-67cc41908669
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1

4
go.sum
View File

@ -69,8 +69,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mzz2017/disk-bloom v1.0.1 h1:rEF9MiXd9qMW3ibRpqcerLXULoTgRlM21yqqJl1B90M=
github.com/mzz2017/disk-bloom v1.0.1/go.mod h1:JLHETtUu44Z6iBmsqzkOtFlRvXSlKnxjwiBRDapizDI=
github.com/mzz2017/softwind v0.0.0-20230209162720-f6e9c7790fb1 h1:91iNjZEiaDIDedusC5Five6KbEbt2ItwvFE/DylPMWo=
github.com/mzz2017/softwind v0.0.0-20230209162720-f6e9c7790fb1/go.mod h1:K1nXwtBokwEsfOfdT/5zV6R8QabGkyhcR0iuTrRZcYY=
github.com/mzz2017/softwind v0.0.0-20230210023945-506610874f4c h1:NoVnf5PsBMiuZRq+XewAWq61dgypkGfNpPZzDjw9bgY=
github.com/mzz2017/softwind v0.0.0-20230210023945-506610874f4c/go.mod h1:K1nXwtBokwEsfOfdT/5zV6R8QabGkyhcR0iuTrRZcYY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=