mirror of
https://github.com/daeuniverse/dae.git
synced 2025-07-12 16:59:38 +07:00
feat: support subscription tag and subtag filter (#13)
This commit is contained in:
@ -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. ...
|
||||
|
@ -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
|
||||
}
|
||||
|
17
cmd/run.go
17
cmd/run.go
@ -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,
|
||||
¶m.Routing,
|
||||
¶m.Global,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
2
go.mod
@ -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
4
go.sum
@ -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=
|
||||
|
Reference in New Issue
Block a user