optimize: url check and log

This commit is contained in:
mzz2017
2023-01-28 15:47:43 +08:00
parent af0f50b308
commit cd4d3ada3e
16 changed files with 112 additions and 82 deletions

View File

@ -64,6 +64,7 @@ func Run() (err error) {
&param.Routing,
param.Global.DnsUpstream,
param.Global.CheckUrl,
param.Global.CheckInterval,
)
if err != nil {
return err

View File

@ -29,6 +29,7 @@ import (
"strconv"
"strings"
"sync"
"time"
)
type ControlPlane struct {
@ -58,6 +59,7 @@ func NewControlPlane(
routingA *config.Routing,
dnsUpstream string,
checkUrl string,
checkInterval time.Duration,
) (c *ControlPlane, err error) {
// Allow the current process to lock memory for eBPF resources.
if err = rlimit.RemoveMemlock(); err != nil {
@ -110,10 +112,12 @@ retryLoadBpf:
if err = bpf.IpprotoHdrsizeMap.Update(uint32(unix.IPPROTO_UDP), int32(0), ebpf.UpdateAny); err != nil {
return nil, err
}
// DialerGroups (outbounds).
option := &dialer.GlobalOption{
Log: log,
CheckUrl: checkUrl,
Log: log,
CheckUrl: checkUrl,
CheckInterval: checkInterval,
}
outbounds := []*outbound.DialerGroup{
outbound.NewDialerGroup(option, consts.OutboundDirect.String(),
@ -133,14 +137,26 @@ retryLoadBpf:
// Filter out groups.
dialerSet := outbound.NewDialerSetFromLinks(option, nodes)
for _, group := range groups {
dialers, err := dialerSet.Filter(group.Param.Filter)
if err != nil {
return nil, fmt.Errorf(`failed to create group "%v": %w`, group.Name, err)
}
// Parse policy.
policy, err := outbound.NewDialerSelectionPolicyFromGroupParam(&group.Param)
if err != nil {
return nil, fmt.Errorf("failed to create group %v: %w", group.Name, err)
}
// Filter nodes.
dialers, err := dialerSet.Filter(group.Param.Filter)
if err != nil {
return nil, fmt.Errorf(`failed to create group "%v": %w`, group.Name, err)
}
// Convert node links to dialers.
log.Infof(`Group "%v" node list:`, group.Name)
for _, d := range dialers {
log.Infoln("\t" + d.Name())
d.ActiveCheck()
}
if len(dialers) == 0 {
log.Infoln("\t<Empty>")
}
// Create dialer group and append it to outbounds.
dialerGroup := outbound.NewDialerGroup(option, group.Name, dialers, *policy)
outbounds = append(outbounds, dialerGroup)
}

View File

@ -6,9 +6,9 @@
package dialer
import (
"github.com/v2rayA/dae/common/consts"
"github.com/mzz2017/softwind/pkg/fastrand"
"github.com/sirupsen/logrus"
"github.com/v2rayA/dae/common/consts"
"sync"
"time"
)
@ -22,7 +22,8 @@ type minLatency struct {
//
// It is thread-safe.
type AliveDialerSet struct {
log *logrus.Logger
log *logrus.Logger
dialerGroupName string
mu sync.Mutex
dialerToIndex map[*Dialer]int // *Dialer -> index of inorderedAliveDialerSet
@ -35,12 +36,14 @@ type AliveDialerSet struct {
func NewAliveDialerSet(
log *logrus.Logger,
dialerGroupName string,
selectionPolicy consts.DialerSelectionPolicy,
dialers []*Dialer,
setAlive bool,
) *AliveDialerSet {
a := &AliveDialerSet{
log: log,
dialerGroupName: dialerGroupName,
dialerToIndex: make(map[*Dialer]int),
dialerToLatency: make(map[*Dialer]time.Duration),
inorderedAliveDialerSet: make([]*Dialer, 0, len(dialers)),
@ -50,10 +53,6 @@ func NewAliveDialerSet(
latency: time.Hour,
},
}
if setAlive && len(dialers) > 0 {
// Use first dialer if no dialer has alive state.
a.minLatency.dialer = dialers[0]
}
for _, d := range dialers {
a.dialerToIndex[d] = -1
}
@ -129,8 +128,9 @@ func (a *AliveDialerSet) SetAlive(dialer *Dialer, alive bool) {
// This dialer is already not alive.
}
}
oldBestDialer := a.minLatency.dialer
if hasLatency {
// Calc minLatency.
a.dialerToLatency[dialer] = latency
if latency < a.minLatency.latency {
a.minLatency.latency = latency
@ -140,6 +140,15 @@ func (a *AliveDialerSet) SetAlive(dialer *Dialer, alive bool) {
a.minLatency.dialer = nil
a.calcMinLatency()
}
if a.minLatency.dialer != oldBestDialer {
a.log.Infof("Group [%v] switched dialer to <%v> (%v): %v", a.dialerGroupName, a.minLatency.dialer.Name(), a.selectionPolicy, a.minLatency.latency)
}
} else {
if alive && a.minLatency.dialer == nil {
// Use first dialer if no dialer has alive state.
a.minLatency.dialer = dialer
a.log.Infof("Group [%v] switched dialer to <%v>", a.dialerGroupName, a.minLatency.dialer.Name())
}
}
}

View File

@ -16,5 +16,5 @@ func (*blockDialer) Dial(network string, addr string) (c net.Conn, err error) {
}
func NewBlockDialer(option *GlobalOption) *Dialer {
return newDialer(&blockDialer{}, option, true, "block", "block", "")
return NewDialer(&blockDialer{}, option, InstanceOption{Check: false}, true, "block", "block", "")
}

View File

@ -24,6 +24,7 @@ var (
type Dialer struct {
*GlobalOption
instanceOption InstanceOption
proxy.Dialer
supportUDP bool
name string
@ -40,37 +41,48 @@ type Dialer struct {
}
type GlobalOption struct {
Log *logrus.Logger
CheckUrl string
Log *logrus.Logger
CheckUrl string
CheckInterval time.Duration
}
type InstanceOption struct {
Check bool
}
// NewDialer is for register in general.
func NewDialer(dialer proxy.Dialer, option *GlobalOption, supportUDP bool, name string, protocol string, link string) *Dialer {
d := newDialer(dialer, option, supportUDP, name, protocol, link)
go d.aliveBackground()
return d
}
// newDialer does not run background tasks.
func newDialer(dialer proxy.Dialer, option *GlobalOption, supportUDP bool, name string, protocol string, link string) *Dialer {
func NewDialer(dialer proxy.Dialer, option *GlobalOption, iOption InstanceOption, supportUDP bool, name string, protocol string, link string) *Dialer {
d := &Dialer{
Dialer: dialer,
GlobalOption: option,
supportUDP: supportUDP,
name: name,
protocol: protocol,
link: link,
Latencies10: NewLatenciesN(10),
Dialer: dialer,
GlobalOption: option,
instanceOption: iOption,
supportUDP: supportUDP,
name: name,
protocol: protocol,
link: link,
Latencies10: NewLatenciesN(10),
// Set a very big cycle to wait for init.
ticker: time.NewTicker(time.Hour),
aliveDialerSetSet: make(map[*AliveDialerSet]int),
}
if iOption.Check {
go d.aliveBackground()
}
return d
}
func (d *Dialer) ActiveCheck() {
d.tickerMu.Lock()
defer d.tickerMu.Unlock()
if d.instanceOption.Check {
return
}
d.instanceOption.Check = true
go d.aliveBackground()
}
func (d *Dialer) aliveBackground() {
timeout := 10 * time.Second
cycle := 15 * time.Second
cycle := d.CheckInterval
// Check once immediately.
go d.Check(timeout, d.CheckUrl)
@ -137,13 +149,14 @@ func (d *Dialer) Check(timeout time.Duration, url string) (ok bool, err error) {
if ok && err == nil {
// No error.
latency := time.Since(start)
d.Log.Debugf("Connectivity Check [%v]: %v", d.name, latency)
d.Latencies10.AppendLatency(latency)
avg, _ := d.Latencies10.AvgLatency()
d.Log.Debugf("Connectivity Check <%v>: last: %v, avg_10: %v", d.name, latency, avg)
alive = true
} else {
// Append timeout if there is any error or unexpected status code.
if err != nil {
d.Log.Debugf("Connectivity Check [%v]: %v", d.name, err.Error())
d.Log.Debugf("Connectivity Check <%v>: %v", d.name, err.Error())
}
d.Latencies10.AppendLatency(timeout)
}

View File

@ -10,9 +10,9 @@ var FullconeDirect = newDirect(true)
func NewDirectDialer(option *GlobalOption, fullcone bool) *Dialer {
if fullcone {
return newDialer(FullconeDirect, option, true, "direct", "direct", "")
return NewDialer(FullconeDirect, option, InstanceOption{Check: false}, true, "direct", "direct", "")
} else {
return newDialer(SymmetricDirect, option, true, "direct", "direct", "")
return NewDialer(SymmetricDirect, option, InstanceOption{Check: false}, true, "direct", "direct", "")
}
}

View File

@ -24,12 +24,12 @@ type HTTP struct {
Protocol string `json:"protocol"`
}
func NewHTTP(option *dialer.GlobalOption, link string) (*dialer.Dialer, error) {
func NewHTTP(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseHTTPURL(link)
if err != nil {
return nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err)
}
return s.Dialer(option)
return s.Dialer(option, iOption)
}
func ParseHTTPURL(link string) (data *HTTP, err error) {
@ -61,13 +61,13 @@ func ParseHTTPURL(link string) (data *HTTP, err error) {
}, nil
}
func (s *HTTP) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, error) {
func (s *HTTP) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
u := s.URL()
d, err := http.NewHTTPProxy(&u, dialer.SymmetricDirect) // HTTP Proxy does not support full-cone.
if err != nil {
return nil, err
}
return dialer.NewDialer(d, option, false, s.Name, s.Protocol, u.String()), nil
return dialer.NewDialer(d, option, iOption, false, s.Name, s.Protocol, u.String()), nil
}
func (s *HTTP) URL() url.URL {

View File

@ -10,7 +10,7 @@ import (
"net/url"
)
type FromLinkCreator func(option *GlobalOption, link string) (dialer *Dialer, err error)
type FromLinkCreator func(gOption *GlobalOption, iOption InstanceOption, link string) (dialer *Dialer, err error)
var fromLinkCreators = make(map[string]FromLinkCreator)
@ -18,13 +18,13 @@ func FromLinkRegister(name string, creator FromLinkCreator) {
fromLinkCreators[name] = creator
}
func NewFromLink(option *GlobalOption, link string) (dialer *Dialer, err error) {
func NewFromLink(gOption *GlobalOption, iOption InstanceOption, link string) (dialer *Dialer, err error) {
u, err := url.Parse(link)
if err != nil {
return nil, err
}
if creator, ok := fromLinkCreators[u.Scheme]; ok {
return creator(option, link)
return creator(gOption, iOption, link)
} else {
return nil, fmt.Errorf("unexpected link type: %v", u.Scheme)
}

View File

@ -33,15 +33,15 @@ type Shadowsocks struct {
Protocol string `json:"protocol"`
}
func NewShadowsocksFromLink(option *dialer.GlobalOption, link string) (*dialer.Dialer, error) {
func NewShadowsocksFromLink(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseSSURL(link)
if err != nil {
return nil, err
}
return s.Dialer(option)
return s.Dialer(option, iOption)
}
func (s *Shadowsocks) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, error) {
func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
// FIXME: support plain/none.
switch s.Cipher {
case "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305":
@ -76,7 +76,7 @@ func (s *Shadowsocks) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, error
}
supportUDP = false
}
return dialer.NewDialer(d, option, supportUDP, s.Name, s.Protocol, s.ExportToURL()), nil
return dialer.NewDialer(d, option, iOption, supportUDP, s.Name, s.Protocol, s.ExportToURL()), nil
}
func ParseSSURL(u string) (data *Shadowsocks, err error) {

View File

@ -30,15 +30,15 @@ type ShadowsocksR struct {
Protocol string `json:"protocol"`
}
func NewShadowsocksR(option *dialer.GlobalOption, link string) (*dialer.Dialer, error) {
func NewShadowsocksR(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseSSRURL(link)
if err != nil {
return nil, err
}
return s.Dialer(option)
return s.Dialer(option, iOption)
}
func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, error) {
func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
u := url.URL{
Scheme: "ssr",
User: url.UserPassword(s.Cipher, s.Password),
@ -54,7 +54,7 @@ func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, erro
if err != nil {
return nil, err
}
return dialer.NewDialer(d, option, false, s.Name, s.Protocol, s.ExportToURL()), nil
return dialer.NewDialer(d, option, iOption, false, s.Name, s.Protocol, s.ExportToURL()), nil
}
func ParseSSRURL(u string) (data *ShadowsocksR, err error) {

View File

@ -26,15 +26,15 @@ type Socks struct {
Protocol string `json:"protocol"`
}
func NewSocks(option *dialer.GlobalOption, link string) (*dialer.Dialer, error) {
func NewSocks(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseSocksURL(link)
if err != nil {
return nil, dialer.InvalidParameterErr
}
return s.Dialer(option)
return s.Dialer(option, iOption)
}
func (s *Socks) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, error) {
func (s *Socks) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
link := s.ExportToURL()
switch s.Protocol {
case "", "socks", "socks5":
@ -42,7 +42,7 @@ func (s *Socks) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, error) {
if err != nil {
return nil, err
}
return dialer.NewDialer(d, option, true, s.Name, s.Protocol, link), nil
return dialer.NewDialer(d, option, iOption, true, s.Name, s.Protocol, link), nil
//case "socks4", "socks4a":
// d, err := socks4.NewSocks4Dialer(link, &proxy.Direct{})
// if err != nil {

View File

@ -34,15 +34,15 @@ type Trojan struct {
Protocol string `json:"protocol"`
}
func NewTrojan(option *dialer.GlobalOption, link string) (*dialer.Dialer, error) {
func NewTrojan(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseTrojanURL(link)
if err != nil {
return nil, err
}
return s.Dialer(option)
return s.Dialer(option, iOption)
}
func (s *Trojan) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, error) {
func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
d := dialer.FullconeDirect // Trojan Proxy supports full-cone.
u := url.URL{
Scheme: "tls",
@ -101,7 +101,7 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption) (*dialer.Dialer, error) {
}); err != nil {
return nil, err
}
return dialer.NewDialer(d, option, true, s.Name, s.Protocol, s.ExportToURL()), nil
return dialer.NewDialer(d, option, iOption, true, s.Name, s.Protocol, s.ExportToURL()), nil
}
func ParseTrojanURL(u string) (data *Trojan, err error) {

View File

@ -41,7 +41,7 @@ type V2Ray struct {
Protocol string `json:"protocol"`
}
func NewV2Ray(option *dialer.GlobalOption, link string) (*dialer.Dialer, error) {
func NewV2Ray(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
var (
s *V2Ray
err error
@ -63,10 +63,10 @@ func NewV2Ray(option *dialer.GlobalOption, link string) (*dialer.Dialer, error)
default:
return nil, dialer.InvalidParameterErr
}
return s.Dialer(option)
return s.Dialer(option, iOption)
}
func (s *V2Ray) Dialer(option *dialer.GlobalOption) (data *dialer.Dialer, err error) {
func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (data *dialer.Dialer, err error) {
var d proxy.Dialer
switch s.Protocol {
case "vmess":
@ -147,7 +147,7 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption) (data *dialer.Dialer, err er
}); err != nil {
return nil, err
}
return dialer.NewDialer(d, option, true, s.Ps, s.Protocol, s.ExportToURL()), nil
return dialer.NewDialer(d, option, iOption, true, s.Ps, s.Protocol, s.ExportToURL()), nil
}
func ParseVlessURL(vless string) (data *V2Ray, err error) {

View File

@ -13,7 +13,6 @@ import (
"github.com/v2rayA/dae/config"
"github.com/v2rayA/dae/pkg/config_parser"
"golang.org/x/net/proxy"
"log"
"net"
"strconv"
)
@ -84,8 +83,9 @@ type DialerGroup struct {
}
func NewDialerGroup(option *dialer.GlobalOption, name string, dialers []*dialer.Dialer, p DialerSelectionPolicy) *DialerGroup {
log := option.Log
var registeredAliveDialerSet bool
a := dialer.NewAliveDialerSet(option.Log, p.Policy, dialers, true)
a := dialer.NewAliveDialerSet(log, name, p.Policy, dialers, true)
switch p.Policy {
case consts.DialerSelectionPolicy_Random,
@ -105,7 +105,7 @@ func NewDialerGroup(option *dialer.GlobalOption, name string, dialers []*dialer.
}
return &DialerGroup{
log: option.Log,
log: log,
Name: name,
Dialers: dialers,
block: dialer.NewBlockDialer(option),
@ -170,6 +170,6 @@ func (g *DialerGroup) Dial(network string, addr string) (c net.Conn, err error)
if err != nil {
return nil, err
}
g.log.Tracef("Group [%v] dial using [%v]", g.Name, d.Name())
g.log.Tracef("Group [%v] dial using <%v>", g.Name, d.Name())
return d.Dial(network, addr)
}

View File

@ -7,7 +7,6 @@ package outbound
import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/v2rayA/dae/component/outbound/dialer"
"github.com/v2rayA/dae/pkg/config_parser"
"regexp"
@ -31,7 +30,7 @@ type DialerSet struct {
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, node)
d, err := dialer.NewFromLink(option, dialer.InstanceOption{Check: false}, node)
if err != nil {
option.Log.Infof("failed to parse node: %v: %v", node, err)
continue
@ -56,7 +55,7 @@ func hit(dialer *dialer.Dialer, filters []*config_parser.Function) (hit bool, er
switch param.Key {
case FilterKey_Name_Regex:
matched, _ := regexp.MatchString(param.Val, dialer.Name())
logrus.Warnln(param.Val, matched, dialer.Name())
//logrus.Warnln(param.Val, matched, dialer.Name())
if matched {
subFilterHit = true
break

View File

@ -4,7 +4,7 @@ global {
# Node connectivity check.
check_url: 'https://connectivitycheck.gstatic.com/generate_204'
check_interval: 15s
check_interval: 30s
# Now only support UDP and IP:Port.
# Please make sure DNS traffic will go through and be forwarded by dae.
@ -38,7 +38,7 @@ group {
disney {
# Pass node names as input of keyword/regex filter.
filter: name(regex:'HK|SG', keyword:'JP') && name(keyword:'disney')
filter: name(regex:'HK|SG|TW', keyword:'JP') && name(keyword:'GCP')
# Select the node with min average of the last 10 latencies from the group for every connection.
policy: min_avg10
@ -46,24 +46,16 @@ group {
netflix {
# Pass node names as input of keyword filter.
filter: name(keyword:netflix)
filter: name(keyword:AWS)
# Select the first node from the group for every connection.
policy: min
}
BT {
# Pass node names as input of keyword filter.
filter: name(regex:'.*')
# Select the first node from the group for every connection.
policy: random
}
}
routing {
domain(geosite:category-ads) -> block
l4proto(udp) && mac('02:42:ac:11:00:02') -> BT
l4proto(udp) && mac('02:42:ac:11:00:03') -> BT
domain(geosite:category-ads) -> block
domain(geosite:disney) -> disney
domain(geosite:netflix) -> netflix