diff --git a/common/utils.go b/common/utils.go index 6f1efdb..9a6a9b5 100644 --- a/common/utils.go +++ b/common/utils.go @@ -353,3 +353,11 @@ func GetTagFromLinkLikePlaintext(link string) (tag string, afterTag string) { // Else tag is the part before colon. return link[:iColon], link[iColon+1:] } + +func BoolToString(b bool) string { + if b { + return "1" + } else { + return "0" + } +} diff --git a/component/outbound/dialer/dialer.go b/component/outbound/dialer/dialer.go index 9193d76..3ee5917 100644 --- a/component/outbound/dialer/dialer.go +++ b/component/outbound/dialer/dialer.go @@ -39,10 +39,11 @@ type GlobalOption struct { CheckInterval time.Duration CheckTolerance time.Duration CheckDnsTcp bool + AllowInsecure bool } type InstanceOption struct { - CheckEnabled bool + CheckEnabled bool } type AliveDialerSetSet map[*AliveDialerSet]int diff --git a/component/outbound/dialer/http/http.go b/component/outbound/dialer/http/http.go index f1a3df5..3ad2f76 100644 --- a/component/outbound/dialer/http/http.go +++ b/component/outbound/dialer/http/http.go @@ -3,6 +3,7 @@ package http import ( "fmt" "github.com/mzz2017/softwind/protocol/http" + "github.com/v2rayA/dae/common" "github.com/v2rayA/dae/component/outbound/dialer" "net" "net/url" @@ -15,24 +16,25 @@ func init() { } type HTTP struct { - Name string `json:"name"` - Server string `json:"server"` - Port int `json:"port"` - Username string `json:"username"` - Password string `json:"password"` - SNI string `json:"sni"` - Protocol string `json:"protocol"` + Name string `json:"name"` + Server string `json:"server"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + SNI string `json:"sni"` + Protocol string `json:"protocol"` + AllowInsecure bool `json:"allowInsecure"` } func NewHTTP(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) { - s, err := ParseHTTPURL(link) + s, err := ParseHTTPURL(link, option) if err != nil { return nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err) } return s.Dialer(option, iOption) } -func ParseHTTPURL(link string) (data *HTTP, err error) { +func ParseHTTPURL(link string, option *dialer.GlobalOption) (data *HTTP, err error) { u, err := url.Parse(link) if err != nil || (u.Scheme != "http" && u.Scheme != "https") { return nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err) @@ -51,13 +53,14 @@ func ParseHTTPURL(link string) (data *HTTP, err error) { return nil, fmt.Errorf("error when parsing port: %w", err) } return &HTTP{ - Name: u.Fragment, - Server: u.Hostname(), - Port: port, - Username: u.User.Username(), - Password: pwd, - SNI: u.Query().Get("sni"), - Protocol: u.Scheme, + Name: u.Fragment, + Server: u.Hostname(), + Port: port, + Username: u.User.Username(), + Password: pwd, + SNI: u.Query().Get("sni"), + Protocol: u.Scheme, + AllowInsecure: option.AllowInsecure, }, nil } @@ -67,7 +70,7 @@ func (s *HTTP) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption if err != nil { return nil, err } - return dialer.NewDialer(d, option, iOption, s.Name, s.Protocol, u.String()), nil + return dialer.NewDialer(d, option, iOption, s.Name, s.Protocol, u.String()), nil } func (s *HTTP) URL() url.URL { @@ -77,7 +80,7 @@ func (s *HTTP) URL() url.URL { Fragment: s.Name, } if s.SNI != "" { - u.RawQuery = url.Values{"sni": []string{s.SNI}}.Encode() + u.RawQuery = url.Values{"sni": []string{s.SNI}, "allowInsecure": []string{common.BoolToString(s.AllowInsecure)}}.Encode() } if s.Username != "" { if s.Password != "" { diff --git a/component/outbound/dialer/trojan/trojan.go b/component/outbound/dialer/trojan/trojan.go index 429cc48..bdc36ec 100644 --- a/component/outbound/dialer/trojan/trojan.go +++ b/component/outbound/dialer/trojan/trojan.go @@ -35,7 +35,7 @@ type Trojan struct { } func NewTrojan(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) { - s, err := ParseTrojanURL(link) + s, err := ParseTrojanURL(link, option) if err != nil { return nil, err } @@ -48,7 +48,8 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOpti Scheme: "tls", Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), RawQuery: url.Values{ - "sni": []string{s.Sni}, + "sni": []string{s.Sni}, + "allowInsecure": []string{common.BoolToString(s.AllowInsecure)}, }.Encode(), } var err error @@ -65,8 +66,8 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOpti Scheme: "ws", Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), RawQuery: url.Values{ - "host": []string{s.Host}, - "path": []string{s.Path}, + "host": []string{s.Host}, + "path": []string{s.Path}, }.Encode(), } if d, err = ws.NewWs(u.String(), d); err != nil { @@ -78,9 +79,10 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOpti serviceName = "GunService" } d = &grpc.Dialer{ - NextDialer: &protocol.DialerConverter{Dialer: d}, - ServiceName: serviceName, - ServerName: s.Sni, + NextDialer: &protocol.DialerConverter{Dialer: d}, + ServiceName: serviceName, + ServerName: s.Sni, + AllowInsecure: s.AllowInsecure, } } if strings.HasPrefix(s.Encryption, "ss;") { @@ -101,17 +103,20 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOpti }); err != nil { return nil, err } - return dialer.NewDialer(d, option, iOption, s.Name, s.Protocol, s.ExportToURL()), nil + return dialer.NewDialer(d, option, iOption, s.Name, s.Protocol, s.ExportToURL()), nil } -func ParseTrojanURL(u string) (data *Trojan, err error) { +func ParseTrojanURL(u string, option *dialer.GlobalOption) (data *Trojan, err error) { //trojan://password@server:port#escape(remarks) t, err := url.Parse(u) if err != nil { err = fmt.Errorf("invalid trojan format") return } - allowInsecure := t.Query().Get("allowInsecure") + allowInsecure, _ := strconv.ParseBool(t.Query().Get("allowInsecure")) + if !allowInsecure && option.AllowInsecure { + allowInsecure = true + } sni := t.Query().Get("peer") if sni == "" { sni = t.Query().Get("sni") @@ -129,7 +134,7 @@ func ParseTrojanURL(u string) (data *Trojan, err error) { Port: port, Password: t.User.Username(), Sni: sni, - AllowInsecure: allowInsecure == "1" || allowInsecure == "true", + AllowInsecure: allowInsecure, Protocol: "trojan", } if t.Query().Get("type") != "" { @@ -145,7 +150,6 @@ func ParseTrojanURL(u string) (data *Trojan, err error) { if data.Type == "grpc" && data.ServiceName == "" { data.ServiceName = data.Path } - data.AllowInsecure = false } return data, nil } diff --git a/component/outbound/dialer/v2ray/v2ray.go b/component/outbound/dialer/v2ray/v2ray.go index 07e6fed..6846eeb 100644 --- a/component/outbound/dialer/v2ray/v2ray.go +++ b/component/outbound/dialer/v2ray/v2ray.go @@ -48,7 +48,7 @@ func NewV2Ray(option *dialer.GlobalOption, iOption dialer.InstanceOption, link s ) switch { case strings.HasPrefix(link, "vmess://"): - s, err = ParseVmessURL(link) + s, err = ParseVmessURL(link, option) if err != nil { return nil, err } @@ -56,7 +56,7 @@ func NewV2Ray(option *dialer.GlobalOption, iOption dialer.InstanceOption, link s return nil, fmt.Errorf("%w: aid: %v, we only support AEAD encryption", dialer.UnexpectedFieldErr, s.Aid) } case strings.HasPrefix(link, "vless://"): - s, err = ParseVlessURL(link) + s, err = ParseVlessURL(link, option) if err != nil { return nil, err } @@ -92,8 +92,9 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio Host: net.JoinHostPort(s.Add, s.Port), Path: s.Path, RawQuery: url.Values{ - "host": []string{s.Host}, - "sni": []string{sni}, + "host": []string{s.Host}, + "sni": []string{sni}, + "allowInsecure": []string{common.BoolToString(s.AllowInsecure)}, }.Encode(), } d, err = ws.NewWs(u.String(), d) @@ -110,7 +111,8 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio Scheme: "tls", Host: net.JoinHostPort(s.Add, s.Port), RawQuery: url.Values{ - "sni": []string{sni}, + "sni": []string{sni}, + "allowInsecure": []string{common.BoolToString(s.AllowInsecure)}, }.Encode(), } d, err = tls.NewTls(u.String(), d) @@ -131,9 +133,10 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio serviceName = "GunService" } d = &grpc.Dialer{ - NextDialer: &protocol.DialerConverter{Dialer: d}, - ServiceName: serviceName, - ServerName: sni, + NextDialer: &protocol.DialerConverter{Dialer: d}, + ServiceName: serviceName, + ServerName: sni, + AllowInsecure: s.AllowInsecure, } default: return nil, fmt.Errorf("%w: network: %v", dialer.UnexpectedFieldErr, s.Net) @@ -147,10 +150,10 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio }); err != nil { return nil, err } - return dialer.NewDialer(d, option, iOption, s.Ps, s.Protocol, s.ExportToURL()), nil + return dialer.NewDialer(d, option, iOption, s.Ps, s.Protocol, s.ExportToURL()), nil } -func ParseVlessURL(vless string) (data *V2Ray, err error) { +func ParseVlessURL(vless string, option *dialer.GlobalOption) (data *V2Ray, err error) { u, err := url.Parse(vless) if err != nil { return nil, err @@ -188,10 +191,13 @@ func ParseVlessURL(vless string) (data *V2Ray, err error) { if data.Type == "mkcp" || data.Type == "kcp" { data.Path = u.Query().Get("seed") } + if option.AllowInsecure { + data.AllowInsecure = true + } return data, nil } -func ParseVmessURL(vmess string) (data *V2Ray, err error) { +func ParseVmessURL(vmess string, option *dialer.GlobalOption) (data *V2Ray, err error) { var info V2Ray // perform base64 decoding and unmarshal to VmessInfo raw, err := common.Base64StdDecode(vmess[8:]) @@ -265,6 +271,9 @@ func ParseVmessURL(vmess string) (data *V2Ray, err error) { info.Aid = "0" } info.Protocol = "vmess" + if option.AllowInsecure { + info.AllowInsecure = true + } return &info, nil } diff --git a/component/outbound/transport/ws/ws.go b/component/outbound/transport/ws/ws.go index 18a2ec9..bbdc33a 100644 --- a/component/outbound/transport/ws/ws.go +++ b/component/outbound/transport/ws/ws.go @@ -8,6 +8,7 @@ import ( "net" "net/http" "net/url" + "strconv" ) // Ws is a base Ws struct @@ -40,18 +41,17 @@ func NewWs(s string, d proxy.Dialer) (*Ws, error) { wsUrl := url.URL{ Scheme: u.Scheme, Host: u.Host, - Path: u.Path, } - t.wsAddr = wsUrl.String() + t.wsAddr = wsUrl.String() + u.Path t.wsDialer = &websocket.Dialer{ - NetDial: d.Dial, + NetDial: d.Dial, //Subprotocols: []string{"binary"}, } if u.Scheme == "wss" { - if u.Query().Get("sni") != "" { - t.wsDialer.TLSClientConfig = &tls.Config{ - ServerName: u.Query().Get("sni"), - } + skipVerify, _ := strconv.ParseBool(u.Query().Get("allowInsecure")) + t.wsDialer.TLSClientConfig = &tls.Config{ + ServerName: u.Query().Get("sni"), + InsecureSkipVerify: skipVerify, } } return t, nil diff --git a/config/config.go b/config/config.go index 0eb88cb..ae4b7ae 100644 --- a/config/config.go +++ b/config/config.go @@ -18,10 +18,7 @@ type Global struct { LogLevel string `mapstructure:"log_level" default:"info"` // 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"` - // We use TcpCheckUrl to check (tcp)*(ipv4/ipv6) connectivity for non-direct and non-DNS packets. - TcpCheckUrl string `mapstructure:"tcp_check_url" default:"http://cp.cloudflare.com"` - // We use UdpCheckDns to check (tcp/udp)*(ipv4/ipv6) connectivity for DNS packets, - // and udp*(ipv4/ipv6) connectivity for all other types of packets. + TcpCheckUrl string `mapstructure:"tcp_check_url" default:"http://cp.cloudflare.com"` UdpCheckDns string `mapstructure:"udp_check_dns" default:"dns.google:53"` CheckInterval time.Duration `mapstructure:"check_interval" default:"30s"` CheckTolerance time.Duration `mapstructure:"check_tolerance" default:"0"` @@ -29,6 +26,7 @@ type Global struct { LanInterface []string `mapstructure:"lan_interface"` LanNatDirect bool `mapstructure:"lan_nat_direct" required:""` WanInterface []string `mapstructure:"wan_interface"` + AllowInsecure bool `mapstructure:"allow_insecure" default:"false"` } type Group struct { diff --git a/control/control_plane.go b/control/control_plane.go index b9f0f00..55527ec 100644 --- a/control/control_plane.go +++ b/control/control_plane.go @@ -200,6 +200,9 @@ func NewControlPlane( checkDnsTcp = true } } + if global.AllowInsecure { + log.Warnln("AllowInsecure is enabled, but it is not recommended. Please make sure you have to turn it on.") + } option := &dialer.GlobalOption{ Log: log, TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl}, @@ -207,6 +210,7 @@ func NewControlPlane( CheckInterval: global.CheckInterval, CheckTolerance: global.CheckTolerance, CheckDnsTcp: checkDnsTcp, + AllowInsecure: global.AllowInsecure, } outbounds := []*outbound.DialerGroup{ outbound.NewDialerGroup(option, consts.OutboundDirect.String(), diff --git a/example.dae b/example.dae index 9d44d3e..0e0d135 100644 --- a/example.dae +++ b/example.dae @@ -42,6 +42,9 @@ global { # The WAN interface to bind. Use it if you want to proxy localhost. # Multiple interfaces split by ",". wan_interface: wlp5s0 + + # Allow insecure TLS certificates. It is not recommended to turn it on unless you have to. + allow_insecure: false } # Subscriptions defined here will be resolved as nodes and merged as a part of the global node pool. diff --git a/go.mod b/go.mod index d5854c5..6c67987 100644 --- a/go.mod +++ b/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-20230212082815-dee826918b06 + github.com/mzz2017/softwind v0.0.0-20230212090240-561c250bc5c4 github.com/safchain/ethtool v0.0.0-20230116090318-67cc41908669 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 diff --git a/go.sum b/go.sum index 28b035b..03fa89e 100644 --- a/go.sum +++ b/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-20230212082815-dee826918b06 h1:cdXO7ciiP2ubeayuHQIWEK3eai/bNW4dAbNgyu8C558= -github.com/mzz2017/softwind v0.0.0-20230212082815-dee826918b06/go.mod h1:K1nXwtBokwEsfOfdT/5zV6R8QabGkyhcR0iuTrRZcYY= +github.com/mzz2017/softwind v0.0.0-20230212090240-561c250bc5c4 h1:f7nqFcMs8LaM/eLRHGeuMk/urv/U+AMNKjFylwowlwU= +github.com/mzz2017/softwind v0.0.0-20230212090240-561c250bc5c4/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=