Skip to content

Commit

Permalink
feat: support tcp_check_http_method (daeuniverse#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
mzz2017 authored May 13, 2023
1 parent e5983f0 commit bf1d296
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 29 deletions.
9 changes: 9 additions & 0 deletions common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,12 @@ nextLink:
}
return Deduplicate(defaultIfs), nil
}

func IsValidHttpMethod(method string) bool {
switch method {
case "GET", "POST", "PUT", "PATCH", "DELETE", "COPY", "HEAD", "OPTIONS", "LINK", "UNLINK", "PURGE", "LOCK", "UNLOCK", "PROPFIND", "CONNECT", "TRACE":
return true
default:
return false
}
}
33 changes: 21 additions & 12 deletions component/outbound/dialer/connectivity_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,13 @@ func parseIp46FromList(ip []string) *netutils.Ip46 {
type TcpCheckOption struct {
Url *netutils.URL
*netutils.Ip46
Method string
}

func ParseTcpCheckOption(ctx context.Context, rawURL []string) (opt *TcpCheckOption, err error) {
func ParseTcpCheckOption(ctx context.Context, rawURL []string, method string) (opt *TcpCheckOption, err error) {
if method == "" {
method = http.MethodGet
}
systemDns, err := netutils.SystemDns()
if err != nil {
return nil, err
Expand Down Expand Up @@ -148,8 +152,9 @@ func ParseTcpCheckOption(ctx context.Context, rawURL []string) (opt *TcpCheckOpt
}
}
return &TcpCheckOption{
Url: &netutils.URL{URL: u},
Ip46: ip46,
Url: &netutils.URL{URL: u},
Ip46: ip46,
Method: method,
}, nil
}

Expand Down Expand Up @@ -199,10 +204,11 @@ func ParseCheckDnsOption(ctx context.Context, dnsHostPort []string) (opt *CheckD
}

type TcpCheckOptionRaw struct {
opt *TcpCheckOption
mu sync.Mutex
Log *logrus.Logger
Raw []string
opt *TcpCheckOption
mu sync.Mutex
Log *logrus.Logger
Raw []string
Method string
}

func (c *TcpCheckOptionRaw) Option() (opt *TcpCheckOption, err error) {
Expand All @@ -212,7 +218,7 @@ func (c *TcpCheckOptionRaw) Option() (opt *TcpCheckOption, err error) {
ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second)
defer cancel()
ctx = context.WithValue(ctx, "logger", c.Log)
tcpCheckOption, err := ParseTcpCheckOption(ctx, c.Raw)
tcpCheckOption, err := ParseTcpCheckOption(ctx, c.Raw, c.Method)
if err != nil {
return nil, fmt.Errorf("failed to parse tcp_check_url: %w", err)
}
Expand Down Expand Up @@ -279,7 +285,7 @@ func (d *Dialer) aliveBackground() {
}).Debugln("Skip check due to no DNS record.")
return false, nil
}
return d.HttpCheck(ctx, opt.Url, opt.Ip4)
return d.HttpCheck(ctx, opt.Url, opt.Ip4, opt.Method)
},
}
tcp6CheckOpt := &CheckOption{
Expand All @@ -301,7 +307,7 @@ func (d *Dialer) aliveBackground() {
}).Debugln("Skip check due to no DNS record.")
return false, nil
}
return d.HttpCheck(ctx, opt.Url, opt.Ip6)
return d.HttpCheck(ctx, opt.Url, opt.Ip6, opt.Method)
},
}
tcp4CheckDnsOpt := &CheckOption{
Expand Down Expand Up @@ -529,8 +535,11 @@ func (d *Dialer) Check(timeout time.Duration,
return ok, err
}

func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr) (ok bool, err error) {
func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr, method string) (ok bool, err error) {
// HTTP(S) check.
if method == "" {
method = http.MethodGet
}
cd := &netproxy.ContextDialer{Dialer: d.Dialer}
cli := http.Client{
Transport: &http.Transport{
Expand All @@ -548,7 +557,7 @@ func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr)
},
},
}
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
req, err := http.NewRequestWithContext(ctx, method, u.String(), nil)
if err != nil {
return false, err
}
Expand Down
5 changes: 2 additions & 3 deletions component/sniffing/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package sniffing
import (
"bufio"
"bytes"
"github.com/daeuniverse/dae/common"
"strings"
"unicode"
)
Expand All @@ -27,9 +28,7 @@ func (s *Sniffer) SniffHttp() (d string, err error) {
if !found {
return "", NotApplicableError
}
switch string(method) {
case "GET", "POST", "PUT", "PATCH", "DELETE", "COPY", "HEAD", "OPTIONS", "LINK", "UNLINK", "PURGE", "LOCK", "UNLOCK", "PROPFIND":
default:
if !common.IsValidHttpMethod(string(method)) {
return "", NotApplicableError
}

Expand Down
7 changes: 3 additions & 4 deletions component/sniffing/sniffing_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package sniffing

import (
"fmt"
"github.com/daeuniverse/dae/common"
"testing"

"github.com/mzz2017/softwind/pkg/fastrand"
Expand All @@ -17,7 +18,7 @@ var (
)

func init() {
httpMethods := []string{"GET", "POST", "PUT", "PATCH", "DELETE", "COPY", "HEAD", "OPTIONS", "LINK", "UNLINK", "PURGE", "LOCK", "UNLOCK", "PROPFIND"}
httpMethods := []string{"GET", "POST", "PUT", "PATCH", "DELETE", "COPY", "HEAD", "OPTIONS", "LINK", "UNLINK", "PURGE", "LOCK", "UNLOCK", "PROPFIND", "CONNECT", "TRACE"}
httpMethodSet = make(map[string]struct{})
for _, method := range httpMethods {
httpMethodSet[method] = struct{}{}
Expand All @@ -39,9 +40,7 @@ func BenchmarkStringSwitch(b *testing.B) {
for i := 0; i < b.N; i++ {
var test [5]byte
fastrand.Read(test[:])
switch string(test[:]) {
case "GET", "POST", "PUT", "PATCH", "DELETE", "COPY", "HEAD", "OPTIONS", "LINK", "UNLINK", "PURGE", "LOCK", "UNLOCK", "PROPFIND":
default:
if !common.IsValidHttpMethod(string(test[:])) {
fmt.Sprintf("%v", string(test[:]))
}
}
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Global struct {
// We use DirectTcpCheckUrl to check (tcp)*(ipv4/ipv6) connectivity for direct.
//DirectTcpCheckUrl string `mapstructure:"direct_tcp_check_url" default:"http://www.qualcomm.cn/generate_204"`
TcpCheckUrl []string `mapstructure:"tcp_check_url" default:"http://cp.cloudflare.com,1.1.1.1,2606:4700:4700::1111"`
TcpCheckHttpMethod string `mapstructure:"tcp_check_http_method" default:"CONNECT"` // Use 'CONNECT' because some server implementations bypass accounting for this kind of traffic.
UdpCheckDns []string `mapstructure:"udp_check_dns" default:"dns.google.com:53,8.8.8.8,2001:4860:4860::8888"`
CheckInterval time.Duration `mapstructure:"check_interval" default:"30s"`
CheckTolerance time.Duration `mapstructure:"check_tolerance" default:"0"`
Expand Down
19 changes: 10 additions & 9 deletions config/desc.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,16 @@ var SectionDescription = map[string]Desc{
}

var GlobalDesc = Desc{
"tproxy_port": "tproxy port to listen on. It is NOT a HTTP/SOCKS port, and is just used by eBPF program.\nIn normal case, you do not need to use it.",
"log_level": "Log level: error, warn, info, debug, trace.",
"tcp_check_url": "Node connectivity check.\nHost of URL should have both IPv4 and IPv6 if you have double stack in local.\nConsidering traffic consumption, it is recommended to choose a site with anycast IP and less response.",
"udp_check_dns": "This DNS will be used to check UDP connectivity of nodes. And if dns_upstream below contains tcp, it also be used to check TCP DNS connectivity of nodes.\nThis DNS should have both IPv4 and IPv6 if you have double stack in local.",
"check_interval": "Interval of connectivity check for TCP and UDP",
"check_tolerance": "Group will switch node only when new_latency <= old_latency - tolerance.",
"lan_interface": "The LAN interface to bind. Use it if you want to proxy LAN.",
"wan_interface": "The WAN interface to bind. Use it if you want to proxy localhost. Use \"auto\" to auto detect.",
"allow_insecure": "Allow insecure TLS certificates. It is not recommended to turn it on unless you have to.",
"tproxy_port": "tproxy port to listen on. It is NOT a HTTP/SOCKS port, and is just used by eBPF program.\nIn normal case, you do not need to use it.",
"log_level": "Log level: error, warn, info, debug, trace.",
"tcp_check_url": "Node connectivity check.\nHost of URL should have both IPv4 and IPv6 if you have double stack in local.\nConsidering traffic consumption, it is recommended to choose a site with anycast IP and less response.",
"tcp_check_http_method": "The HTTP request method to `tcp_check_url`. Use 'CONNECT' by default because some server implementations bypass accounting for this kind of traffic.",
"udp_check_dns": "This DNS will be used to check UDP connectivity of nodes. And if dns_upstream below contains tcp, it also be used to check TCP DNS connectivity of nodes.\nThis DNS should have both IPv4 and IPv6 if you have double stack in local.",
"check_interval": "Interval of connectivity check for TCP and UDP",
"check_tolerance": "Group will switch node only when new_latency <= old_latency - tolerance.",
"lan_interface": "The LAN interface to bind. Use it if you want to proxy LAN.",
"wan_interface": "The WAN interface to bind. Use it if you want to proxy localhost. Use \"auto\" to auto detect.",
"allow_insecure": "Allow insecure TLS certificates. It is not recommended to turn it on unless you have to.",
"dial_mode": `Optional values of dial_mode are:
1. "ip". Dial proxy using the IP from DNS directly. This allows your ipv4, ipv6 to choose the optimal path respectively, and makes the IP version requested by the application meet expectations. For example, if you use curl -4 ip.sb, you will request IPv4 via proxy and get a IPv4 echo. And curl -6 ip.sb will request IPv6. This may solve some wierd full-cone problem if your are be your node support that.Sniffing will be disabled in this mode.
2. "domain". Dial proxy using the domain from sniffing. This will relieve DNS pollution problem to a great extent if have impure DNS environment. Generally, this mode brings faster proxy response time because proxy will re-resolve the domain in remote, thus get better IP result to connect. This policy does not impact routing. That is to say, domain rewrite will be after traffic split of routing and dae will not re-route it.
Expand Down
11 changes: 11 additions & 0 deletions config/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package config

import (
"github.com/daeuniverse/dae/common"
"github.com/sirupsen/logrus"
"strings"

"github.com/daeuniverse/dae/common/consts"
Expand All @@ -15,10 +17,19 @@ import (
type patch func(params *Config) error

var patches = []patch{
patchTcpCheckHttpMethod,
patchEmptyDns,
patchMustOutbound,
}

func patchTcpCheckHttpMethod(params *Config) error {
if !common.IsValidHttpMethod(params.Global.TcpCheckHttpMethod) {
logrus.Warnf("Unknown HTTP Method '%v'. Fallback to 'CONNECT'.", params.Global.TcpCheckHttpMethod)
params.Global.TcpCheckHttpMethod = "CONNECT"
}
return nil
}

func patchEmptyDns(params *Config) error {
if params.Dns.Routing.Request.Fallback == nil {
params.Dns.Routing.Request.Fallback = consts.DnsRequestOutboundIndex_AsIs.String()
Expand Down
2 changes: 1 addition & 1 deletion control/control_plane.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func NewControlPlane(
}
option := &dialer.GlobalOption{
Log: log,
TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl, Log: log},
TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl, Log: log, Method: global.TcpCheckHttpMethod},
CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{Raw: global.UdpCheckDns},
CheckInterval: global.CheckInterval,
CheckTolerance: global.CheckTolerance,
Expand Down
4 changes: 4 additions & 0 deletions example.dae
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ global {
#tcp_check_url: 'http://cp.cloudflare.com'
tcp_check_url: 'http://cp.cloudflare.com,1.1.1.1,2606:4700:4700::1111'

# The HTTP request method to `tcp_check_url`. Use 'CONNECT' by default because some server implementations bypass
# accounting for this kind of traffic.
tcp_check_http_method: CONNECT

# This DNS will be used to check UDP connectivity of nodes. And if dns_upstream below contains tcp, it also be used to check
# TCP DNS connectivity of nodes.
# First is URL, others are IP addresses if given.
Expand Down

0 comments on commit bf1d296

Please sign in to comment.