mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-20 00:50:06 +08:00
Some checks failed
Test / test (1.20, macos-13) (push) Waiting to run
Test / test (1.20, macos-latest) (push) Waiting to run
Test / test (1.20, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.20, windows-latest) (push) Waiting to run
Test / test (1.21, macos-13) (push) Waiting to run
Test / test (1.21, macos-latest) (push) Waiting to run
Test / test (1.21, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.21, windows-latest) (push) Waiting to run
Test / test (1.22, macos-13) (push) Waiting to run
Test / test (1.22, macos-latest) (push) Waiting to run
Test / test (1.22, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.22, windows-latest) (push) Waiting to run
Test / test (1.23, macos-13) (push) Waiting to run
Test / test (1.23, macos-latest) (push) Waiting to run
Test / test (1.23, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.23, windows-latest) (push) Waiting to run
Test / test (1.24, macos-13) (push) Waiting to run
Test / test (1.24, macos-latest) (push) Waiting to run
Test / test (1.24, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.24, windows-latest) (push) Waiting to run
Test / test (1.20, ubuntu-latest) (push) Failing after 1s
Test / test (1.21, ubuntu-latest) (push) Failing after 1s
Test / test (1.22, ubuntu-latest) (push) Failing after 1s
Test / test (1.23, ubuntu-latest) (push) Failing after 1s
Test / test (1.24, ubuntu-latest) (push) Failing after 1s
Trigger CMFA Update / trigger-CMFA-update (push) Failing after 1s
The DNS resolution of the overall UDP part has been delayed to the connection initiation stage. During the rule matching process, it will only be triggered when the IP rule without no-resolve is matched. For direct and wireguard outbound, the same logic as the TCP part will be followed, that is, when direct-nameserver (or DNS configured by wireguard) exists, the result of the matching process will be discarded and the domain name will be re-resolved. This re-resolution logic is only effective for fakeip. For reject and DNS outbound, no resolution is required. For other outbound, resolution will still be performed when the connection is initiated, and the domain name will not be sent directly to the remote server at present.
613 lines
16 KiB
Go
613 lines
16 KiB
Go
package outbound
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/metacubex/mihomo/common/convert"
|
|
N "github.com/metacubex/mihomo/common/net"
|
|
"github.com/metacubex/mihomo/common/utils"
|
|
"github.com/metacubex/mihomo/component/ca"
|
|
"github.com/metacubex/mihomo/component/dialer"
|
|
"github.com/metacubex/mihomo/component/ech"
|
|
"github.com/metacubex/mihomo/component/proxydialer"
|
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
|
C "github.com/metacubex/mihomo/constant"
|
|
"github.com/metacubex/mihomo/transport/gun"
|
|
"github.com/metacubex/mihomo/transport/vless"
|
|
"github.com/metacubex/mihomo/transport/vmess"
|
|
|
|
vmessSing "github.com/metacubex/sing-vmess"
|
|
"github.com/metacubex/sing-vmess/packetaddr"
|
|
M "github.com/metacubex/sing/common/metadata"
|
|
)
|
|
|
|
const (
|
|
// max packet length
|
|
maxLength = 1024 << 3
|
|
)
|
|
|
|
type Vless struct {
|
|
*Base
|
|
client *vless.Client
|
|
option *VlessOption
|
|
|
|
// for gun mux
|
|
gunTLSConfig *tls.Config
|
|
gunConfig *gun.Config
|
|
transport *gun.TransportWrap
|
|
|
|
realityConfig *tlsC.RealityConfig
|
|
echConfig *ech.Config
|
|
}
|
|
|
|
type VlessOption struct {
|
|
BasicOption
|
|
Name string `proxy:"name"`
|
|
Server string `proxy:"server"`
|
|
Port int `proxy:"port"`
|
|
UUID string `proxy:"uuid"`
|
|
Flow string `proxy:"flow,omitempty"`
|
|
TLS bool `proxy:"tls,omitempty"`
|
|
ALPN []string `proxy:"alpn,omitempty"`
|
|
UDP bool `proxy:"udp,omitempty"`
|
|
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
|
XUDP bool `proxy:"xudp,omitempty"`
|
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
|
Network string `proxy:"network,omitempty"`
|
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
|
WSPath string `proxy:"ws-path,omitempty"`
|
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
ServerName string `proxy:"servername,omitempty"`
|
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
|
}
|
|
|
|
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
|
switch v.option.Network {
|
|
case "ws":
|
|
host, port, _ := net.SplitHostPort(v.addr)
|
|
wsOpts := &vmess.WebsocketConfig{
|
|
Host: host,
|
|
Port: port,
|
|
Path: v.option.WSOpts.Path,
|
|
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
|
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
|
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
|
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
|
ClientFingerprint: v.option.ClientFingerprint,
|
|
ECHConfig: v.echConfig,
|
|
Headers: http.Header{},
|
|
}
|
|
|
|
if len(v.option.WSOpts.Headers) != 0 {
|
|
for key, value := range v.option.WSOpts.Headers {
|
|
wsOpts.Headers.Add(key, value)
|
|
}
|
|
}
|
|
if v.option.TLS {
|
|
wsOpts.TLS = true
|
|
tlsConfig := &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
ServerName: host,
|
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
|
NextProtos: []string{"http/1.1"},
|
|
}
|
|
|
|
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v.option.ServerName != "" {
|
|
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
|
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
|
wsOpts.TLSConfig.ServerName = host
|
|
}
|
|
} else {
|
|
if host := wsOpts.Headers.Get("Host"); host == "" {
|
|
wsOpts.Headers.Set("Host", convert.RandHost())
|
|
convert.SetUserAgent(wsOpts.Headers)
|
|
}
|
|
}
|
|
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
|
|
case "http":
|
|
// readability first, so just copy default TLS logic
|
|
c, err = v.streamTLSConn(ctx, c, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
host, _, _ := net.SplitHostPort(v.addr)
|
|
httpOpts := &vmess.HTTPConfig{
|
|
Host: host,
|
|
Method: v.option.HTTPOpts.Method,
|
|
Path: v.option.HTTPOpts.Path,
|
|
Headers: v.option.HTTPOpts.Headers,
|
|
}
|
|
|
|
c = vmess.StreamHTTPConn(c, httpOpts)
|
|
case "h2":
|
|
c, err = v.streamTLSConn(ctx, c, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
h2Opts := &vmess.H2Config{
|
|
Hosts: v.option.HTTP2Opts.Host,
|
|
Path: v.option.HTTP2Opts.Path,
|
|
}
|
|
|
|
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
|
|
case "grpc":
|
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
|
|
default:
|
|
// default tcp network
|
|
// handle TLS
|
|
c, err = v.streamTLSConn(ctx, c, false)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return v.streamConnContext(ctx, c, metadata)
|
|
}
|
|
|
|
func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
|
|
if ctx.Done() != nil {
|
|
done := N.SetupContextForConn(ctx, c)
|
|
defer done(&err)
|
|
}
|
|
if metadata.NetWork == C.UDP {
|
|
if v.option.PacketAddr {
|
|
metadata = &C.Metadata{
|
|
NetWork: C.UDP,
|
|
Host: packetaddr.SeqPacketMagicAddress,
|
|
DstPort: 443,
|
|
}
|
|
} else {
|
|
metadata = &C.Metadata{ // a clear metadata only contains ip
|
|
NetWork: C.UDP,
|
|
DstIP: metadata.DstIP,
|
|
DstPort: metadata.DstPort,
|
|
}
|
|
}
|
|
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
|
if v.option.PacketAddr {
|
|
conn = packetaddr.NewBindConn(conn)
|
|
}
|
|
} else {
|
|
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, false))
|
|
}
|
|
if err != nil {
|
|
conn = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
|
|
if v.option.TLS {
|
|
host, _, _ := net.SplitHostPort(v.addr)
|
|
|
|
tlsOpts := vmess.TLSConfig{
|
|
Host: host,
|
|
SkipCertVerify: v.option.SkipCertVerify,
|
|
FingerPrint: v.option.Fingerprint,
|
|
ClientFingerprint: v.option.ClientFingerprint,
|
|
ECH: v.echConfig,
|
|
Reality: v.realityConfig,
|
|
NextProtos: v.option.ALPN,
|
|
}
|
|
|
|
if isH2 {
|
|
tlsOpts.NextProtos = []string{"h2"}
|
|
}
|
|
|
|
if v.option.ServerName != "" {
|
|
tlsOpts.Host = v.option.ServerName
|
|
}
|
|
|
|
return vmess.StreamTLSConn(ctx, conn, &tlsOpts)
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
// DialContext implements C.ProxyAdapter
|
|
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
var c net.Conn
|
|
// gun transport
|
|
if v.transport != nil {
|
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func(c net.Conn) {
|
|
safeConnClose(c, err)
|
|
}(c)
|
|
|
|
c, err = v.streamConnContext(ctx, c, metadata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewConn(c, v), nil
|
|
}
|
|
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata)
|
|
}
|
|
|
|
// DialContextWithDialer implements C.ProxyAdapter
|
|
func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
if len(v.option.DialerProxy) > 0 {
|
|
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
|
}
|
|
defer func(c net.Conn) {
|
|
safeConnClose(c, err)
|
|
}(c)
|
|
|
|
c, err = v.StreamConnContext(ctx, c, metadata)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
|
}
|
|
return NewConn(c, v), err
|
|
}
|
|
|
|
// ListenPacketContext implements C.ProxyAdapter
|
|
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
|
return nil, err
|
|
}
|
|
var c net.Conn
|
|
// gun transport
|
|
if v.transport != nil {
|
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func(c net.Conn) {
|
|
safeConnClose(c, err)
|
|
}(c)
|
|
|
|
c, err = v.streamConnContext(ctx, c, metadata)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new vless client error: %v", err)
|
|
}
|
|
|
|
return v.ListenPacketOnStreamConn(ctx, c, metadata)
|
|
}
|
|
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata)
|
|
}
|
|
|
|
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
if len(v.option.DialerProxy) > 0 {
|
|
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
|
}
|
|
defer func(c net.Conn) {
|
|
safeConnClose(c, err)
|
|
}(c)
|
|
|
|
c, err = v.StreamConnContext(ctx, c, metadata)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new vless client error: %v", err)
|
|
}
|
|
|
|
return v.ListenPacketOnStreamConn(ctx, c, metadata)
|
|
}
|
|
|
|
// SupportWithDialer implements C.ProxyAdapter
|
|
func (v *Vless) SupportWithDialer() C.NetWork {
|
|
return C.ALLNet
|
|
}
|
|
|
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
|
func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v.option.XUDP {
|
|
var globalID [8]byte
|
|
if metadata.SourceValid() {
|
|
globalID = utils.GlobalID(metadata.SourceAddress())
|
|
}
|
|
return newPacketConn(N.NewThreadSafePacketConn(
|
|
vmessSing.NewXUDPConn(c,
|
|
globalID,
|
|
M.SocksaddrFromNet(metadata.UDPAddr())),
|
|
), v), nil
|
|
} else if v.option.PacketAddr {
|
|
return newPacketConn(N.NewThreadSafePacketConn(
|
|
packetaddr.NewConn(&vlessPacketConn{
|
|
Conn: c, rAddr: metadata.UDPAddr(),
|
|
}, M.SocksaddrFromNet(metadata.UDPAddr())),
|
|
), v), nil
|
|
}
|
|
return newPacketConn(N.NewThreadSafePacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), v), nil
|
|
}
|
|
|
|
// SupportUOT implements C.ProxyAdapter
|
|
func (v *Vless) SupportUOT() bool {
|
|
return true
|
|
}
|
|
|
|
// ProxyInfo implements C.ProxyAdapter
|
|
func (v *Vless) ProxyInfo() C.ProxyInfo {
|
|
info := v.Base.ProxyInfo()
|
|
info.DialerProxy = v.option.DialerProxy
|
|
return info
|
|
}
|
|
|
|
// Close implements C.ProxyAdapter
|
|
func (v *Vless) Close() error {
|
|
if v.transport != nil {
|
|
return v.transport.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
|
var addrType byte
|
|
var addr []byte
|
|
switch metadata.AddrType() {
|
|
case C.AtypIPv4:
|
|
addrType = vless.AtypIPv4
|
|
addr = make([]byte, net.IPv4len)
|
|
copy(addr[:], metadata.DstIP.AsSlice())
|
|
case C.AtypIPv6:
|
|
addrType = vless.AtypIPv6
|
|
addr = make([]byte, net.IPv6len)
|
|
copy(addr[:], metadata.DstIP.AsSlice())
|
|
case C.AtypDomainName:
|
|
addrType = vless.AtypDomainName
|
|
addr = make([]byte, len(metadata.Host)+1)
|
|
addr[0] = byte(len(metadata.Host))
|
|
copy(addr[1:], metadata.Host)
|
|
}
|
|
|
|
return &vless.DstAddr{
|
|
UDP: metadata.NetWork == C.UDP,
|
|
AddrType: addrType,
|
|
Addr: addr,
|
|
Port: metadata.DstPort,
|
|
Mux: metadata.NetWork == C.UDP && xudp,
|
|
}
|
|
}
|
|
|
|
type vlessPacketConn struct {
|
|
net.Conn
|
|
rAddr net.Addr
|
|
remain int
|
|
mux sync.Mutex
|
|
cache [2]byte
|
|
}
|
|
|
|
func (c *vlessPacketConn) writePacket(payload []byte) (int, error) {
|
|
binary.BigEndian.PutUint16(c.cache[:], uint16(len(payload)))
|
|
|
|
if _, err := c.Conn.Write(c.cache[:]); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return c.Conn.Write(payload)
|
|
}
|
|
|
|
func (c *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
|
total := len(b)
|
|
if total == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
if total <= maxLength {
|
|
return c.writePacket(b)
|
|
}
|
|
|
|
offset := 0
|
|
|
|
for offset < total {
|
|
cursor := offset + maxLength
|
|
if cursor > total {
|
|
cursor = total
|
|
}
|
|
|
|
n, err := c.writePacket(b[offset:cursor])
|
|
if err != nil {
|
|
return offset + n, err
|
|
}
|
|
|
|
offset = cursor
|
|
if offset == total {
|
|
break
|
|
}
|
|
}
|
|
|
|
return total, nil
|
|
}
|
|
|
|
func (c *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
if c.remain > 0 {
|
|
length := len(b)
|
|
if c.remain < length {
|
|
length = c.remain
|
|
}
|
|
|
|
n, err := c.Conn.Read(b[:length])
|
|
if err != nil {
|
|
return 0, c.rAddr, err
|
|
}
|
|
|
|
c.remain -= n
|
|
return n, c.rAddr, nil
|
|
}
|
|
|
|
if _, err := c.Conn.Read(b[:2]); err != nil {
|
|
return 0, c.rAddr, err
|
|
}
|
|
|
|
total := int(binary.BigEndian.Uint16(b[:2]))
|
|
if total == 0 {
|
|
return 0, c.rAddr, nil
|
|
}
|
|
|
|
length := len(b)
|
|
if length > total {
|
|
length = total
|
|
}
|
|
|
|
if _, err := io.ReadFull(c.Conn, b[:length]); err != nil {
|
|
return 0, c.rAddr, errors.New("read packet error")
|
|
}
|
|
|
|
c.remain = total - length
|
|
|
|
return length, c.rAddr, nil
|
|
}
|
|
|
|
func NewVless(option VlessOption) (*Vless, error) {
|
|
var addons *vless.Addons
|
|
if option.Network != "ws" && len(option.Flow) >= 16 {
|
|
option.Flow = option.Flow[:16]
|
|
if option.Flow != vless.XRV {
|
|
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
|
}
|
|
addons = &vless.Addons{
|
|
Flow: option.Flow,
|
|
}
|
|
}
|
|
|
|
switch option.PacketEncoding {
|
|
case "packetaddr", "packet":
|
|
option.PacketAddr = true
|
|
option.XUDP = false
|
|
default: // https://github.com/XTLS/Xray-core/pull/1567#issuecomment-1407305458
|
|
if !option.PacketAddr {
|
|
option.XUDP = true
|
|
}
|
|
}
|
|
if option.XUDP {
|
|
option.PacketAddr = false
|
|
}
|
|
|
|
client, err := vless.NewClient(option.UUID, addons)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v := &Vless{
|
|
Base: &Base{
|
|
name: option.Name,
|
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
tp: C.Vless,
|
|
udp: option.UDP,
|
|
xudp: option.XUDP,
|
|
tfo: option.TFO,
|
|
mpTcp: option.MPTCP,
|
|
iface: option.Interface,
|
|
rmark: option.RoutingMark,
|
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
},
|
|
client: client,
|
|
option: &option,
|
|
}
|
|
|
|
v.realityConfig, err = v.option.RealityOpts.Parse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v.echConfig, err = v.option.ECHOpts.Parse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch option.Network {
|
|
case "h2":
|
|
if len(option.HTTP2Opts.Host) == 0 {
|
|
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
|
}
|
|
case "grpc":
|
|
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
var err error
|
|
var cDialer C.Dialer = dialer.NewDialer(v.DialOptions()...)
|
|
if len(v.option.DialerProxy) > 0 {
|
|
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
gunConfig := &gun.Config{
|
|
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
|
Host: v.option.ServerName,
|
|
ClientFingerprint: v.option.ClientFingerprint,
|
|
}
|
|
if option.ServerName == "" {
|
|
gunConfig.Host = v.addr
|
|
}
|
|
var tlsConfig *tls.Config
|
|
if option.TLS {
|
|
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
|
ServerName: v.option.ServerName,
|
|
}, v.option.Fingerprint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if option.ServerName == "" {
|
|
host, _, _ := net.SplitHostPort(v.addr)
|
|
tlsConfig.ServerName = host
|
|
}
|
|
}
|
|
|
|
v.gunTLSConfig = tlsConfig
|
|
v.gunConfig = gunConfig
|
|
|
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
|
|
}
|
|
|
|
return v, nil
|
|
}
|