github.com/gospider007/requests@v0.0.0-20240506025355-c73d46169a23/dial.go (about)

     1  package requests
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"crypto/tls"
     7  	"errors"
     8  	"io"
     9  	"net"
    10  	"net/textproto"
    11  	"net/url"
    12  	"sync"
    13  	"time"
    14  
    15  	"net/http"
    16  
    17  	"github.com/gospider007/gtls"
    18  	"github.com/gospider007/ja3"
    19  	"github.com/gospider007/tools"
    20  	utls "github.com/refraction-networking/utls"
    21  )
    22  
    23  type DialClient struct {
    24  	dialer      *net.Dialer
    25  	dnsIpData   sync.Map
    26  	dns         *net.UDPAddr
    27  	getAddrType func(host string) gtls.AddrType
    28  }
    29  type msgClient struct {
    30  	time time.Time
    31  	host string
    32  }
    33  
    34  type DialOption struct {
    35  	DialTimeout time.Duration
    36  	KeepAlive   time.Duration
    37  	LocalAddr   *net.TCPAddr  //network card ip
    38  	AddrType    gtls.AddrType //first ip type
    39  	Dns         *net.UDPAddr
    40  	GetAddrType func(host string) gtls.AddrType
    41  }
    42  
    43  func NewDialer(option DialOption) *net.Dialer {
    44  	if option.KeepAlive == 0 {
    45  		option.KeepAlive = time.Second * 30
    46  	}
    47  	if option.DialTimeout == 0 {
    48  		option.DialTimeout = time.Second * 15
    49  	}
    50  	dialer := &net.Dialer{
    51  		Timeout:   option.DialTimeout,
    52  		KeepAlive: option.KeepAlive,
    53  		LocalAddr: option.LocalAddr,
    54  	}
    55  	if option.LocalAddr != nil {
    56  		dialer.LocalAddr = option.LocalAddr
    57  	}
    58  	if option.Dns != nil {
    59  		dialer.Resolver = &net.Resolver{
    60  			PreferGo: true,
    61  			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
    62  				return (&net.Dialer{
    63  					Timeout:   option.DialTimeout,
    64  					KeepAlive: option.KeepAlive,
    65  				}).DialContext(ctx, network, option.Dns.String())
    66  			},
    67  		}
    68  	}
    69  	dialer.SetMultipathTCP(true)
    70  	return dialer
    71  }
    72  func NewDail(option DialOption) *DialClient {
    73  	return &DialClient{
    74  		dialer:      NewDialer(option),
    75  		dns:         option.Dns,
    76  		getAddrType: option.GetAddrType,
    77  	}
    78  }
    79  func (obj *DialClient) DialContext(ctx context.Context, ctxData *reqCtxData, network string, addr string) (net.Conn, error) {
    80  	if ctxData == nil {
    81  		ctxData = &reqCtxData{}
    82  	}
    83  	host, port, err := net.SplitHostPort(addr)
    84  	if err != nil {
    85  		return nil, tools.WrapError(err, "addrToIp error,SplitHostPort")
    86  	}
    87  	var dialer *net.Dialer
    88  	if _, ipInt := gtls.ParseHost(host); ipInt == 0 { //domain
    89  		host, ok := obj.loadHost(host)
    90  		if !ok { //dns parse
    91  			dialer = obj.getDialer(ctxData, true)
    92  			var addrType gtls.AddrType
    93  			if ctxData.addrType != 0 {
    94  				addrType = ctxData.addrType
    95  			} else if obj.getAddrType != nil {
    96  				addrType = obj.getAddrType(host)
    97  			}
    98  			ips, err := dialer.Resolver.LookupIPAddr(ctx, host)
    99  			if err != nil {
   100  				return nil, err
   101  			}
   102  			if host, err = obj.addrToIp(host, ips, addrType); err != nil {
   103  				return nil, err
   104  			}
   105  			addr = net.JoinHostPort(host, port)
   106  		}
   107  	}
   108  	if dialer == nil {
   109  		dialer = obj.getDialer(ctxData, false)
   110  	}
   111  	return dialer.DialContext(ctx, network, addr)
   112  }
   113  func (obj *DialClient) DialContextWithProxy(ctx context.Context, ctxData *reqCtxData, network string, scheme string, addr string, host string, proxyUrl *url.URL, tlsConfig *tls.Config) (net.Conn, error) {
   114  	if ctxData == nil {
   115  		ctxData = &reqCtxData{}
   116  	}
   117  	if proxyUrl == nil {
   118  		return obj.DialContext(ctx, ctxData, network, addr)
   119  	}
   120  	if proxyUrl.Port() == "" {
   121  		if proxyUrl.Scheme == "http" {
   122  			proxyUrl.Host = net.JoinHostPort(proxyUrl.Hostname(), "80")
   123  		} else if proxyUrl.Scheme == "https" {
   124  			proxyUrl.Host = net.JoinHostPort(proxyUrl.Hostname(), "443")
   125  		}
   126  	}
   127  	switch proxyUrl.Scheme {
   128  	case "http", "https":
   129  		conn, err := obj.DialContext(ctx, ctxData, network, net.JoinHostPort(proxyUrl.Hostname(), proxyUrl.Port()))
   130  		if err != nil {
   131  			return conn, err
   132  		} else if proxyUrl.Scheme == "https" {
   133  			if conn, err = obj.addTls(ctx, conn, proxyUrl.Host, true, tlsConfig); err != nil {
   134  				return conn, err
   135  			}
   136  		}
   137  		return conn, obj.clientVerifyHttps(ctx, scheme, proxyUrl, addr, host, conn)
   138  	case "socks5":
   139  		return obj.Socks5Proxy(ctx, ctxData, network, addr, proxyUrl)
   140  	default:
   141  		return nil, errors.New("proxyUrl Scheme error")
   142  	}
   143  }
   144  func (obj *DialClient) loadHost(host string) (string, bool) {
   145  	msgDataAny, ok := obj.dnsIpData.Load(host)
   146  	if ok {
   147  		msgdata := msgDataAny.(msgClient)
   148  		if time.Since(msgdata.time) < time.Second*60*5 {
   149  			return msgdata.host, true
   150  		}
   151  	}
   152  	return host, false
   153  }
   154  func (obj *DialClient) addrToIp(host string, ips []net.IPAddr, addrType gtls.AddrType) (string, error) {
   155  	ip, err := obj.lookupIPAddr(ips, addrType)
   156  	if err != nil {
   157  		return host, tools.WrapError(err, "addrToIp error,lookupIPAddr")
   158  	}
   159  	obj.dnsIpData.Store(host, msgClient{time: time.Now(), host: ip.String()})
   160  	return ip.String(), nil
   161  }
   162  func (obj *DialClient) clientVerifySocks5(proxyUrl *url.URL, addr string, conn net.Conn) (err error) {
   163  	if _, err = conn.Write([]byte{5, 2, 0, 2}); err != nil {
   164  		return
   165  	}
   166  	readCon := make([]byte, 4)
   167  	if _, err = io.ReadFull(conn, readCon[:2]); err != nil {
   168  		return
   169  	}
   170  	switch readCon[1] {
   171  	case 2:
   172  		if proxyUrl.User == nil {
   173  			err = errors.New("socks5 need auth")
   174  			return
   175  		}
   176  		pwd, pwdOk := proxyUrl.User.Password()
   177  		if !pwdOk {
   178  			err = errors.New("socks5 auth error")
   179  			return
   180  		}
   181  		usr := proxyUrl.User.Username()
   182  
   183  		if usr == "" {
   184  			err = errors.New("socks5 auth user format error")
   185  			return
   186  		}
   187  		if _, err = conn.Write(append(
   188  			append(
   189  				[]byte{1, byte(len(usr))},
   190  				tools.StringToBytes(usr)...,
   191  			),
   192  			append(
   193  				[]byte{byte(len(pwd))},
   194  				tools.StringToBytes(pwd)...,
   195  			)...,
   196  		)); err != nil {
   197  			return
   198  		}
   199  		if _, err = io.ReadFull(conn, readCon[:2]); err != nil {
   200  			return
   201  		}
   202  		switch readCon[1] {
   203  		case 0:
   204  		default:
   205  			err = errors.New("socks5 auth error")
   206  			return
   207  		}
   208  	case 0:
   209  	default:
   210  		err = errors.New("not support auth format")
   211  		return
   212  	}
   213  	var host string
   214  	var port int
   215  	if host, port, err = gtls.SplitHostPort(addr); err != nil {
   216  		return
   217  	}
   218  	writeCon := []byte{5, 1, 0}
   219  	ip, ipInt := gtls.ParseHost(host)
   220  	switch ipInt {
   221  	case 4:
   222  		writeCon = append(writeCon, 1)
   223  		writeCon = append(writeCon, ip...)
   224  	case 6:
   225  		writeCon = append(writeCon, 4)
   226  		writeCon = append(writeCon, ip...)
   227  	case 0:
   228  		if len(host) > 255 {
   229  			err = errors.New("FQDN too long")
   230  			return
   231  		}
   232  		writeCon = append(writeCon, 3)
   233  		writeCon = append(writeCon, byte(len(host)))
   234  		writeCon = append(writeCon, host...)
   235  	}
   236  	writeCon = append(writeCon, byte(port>>8), byte(port))
   237  	if _, err = conn.Write(writeCon); err != nil {
   238  		return
   239  	}
   240  	if _, err = io.ReadFull(conn, readCon); err != nil {
   241  		return
   242  	}
   243  	if readCon[0] != 5 {
   244  		err = errors.New("socks version error")
   245  		return
   246  	}
   247  	if readCon[1] != 0 {
   248  		err = errors.New("socks conn error")
   249  		return
   250  	}
   251  	if readCon[3] != 1 {
   252  		err = errors.New("socks conn type error")
   253  		return
   254  	}
   255  
   256  	switch readCon[3] {
   257  	case 1: //ipv4
   258  		if _, err = io.ReadFull(conn, readCon); err != nil {
   259  			return
   260  		}
   261  	case 3: //domain
   262  		if _, err = io.ReadFull(conn, readCon[:1]); err != nil {
   263  			return
   264  		}
   265  		if _, err = io.ReadFull(conn, make([]byte, readCon[0])); err != nil {
   266  			return
   267  		}
   268  	case 4: //IPv6
   269  		if _, err = io.ReadFull(conn, make([]byte, 16)); err != nil {
   270  			return
   271  		}
   272  	default:
   273  		err = errors.New("invalid atyp")
   274  		return
   275  	}
   276  	_, err = io.ReadFull(conn, readCon[:2])
   277  	return
   278  }
   279  func (obj *DialClient) lookupIPAddr(ips []net.IPAddr, addrType gtls.AddrType) (net.IP, error) {
   280  	for _, ipAddr := range ips {
   281  		ip := ipAddr.IP
   282  		if ipType := gtls.ParseIp(ip); ipType == 4 || ipType == 6 {
   283  			if addrType == 0 || addrType == ipType {
   284  				return ip, nil
   285  			}
   286  		}
   287  	}
   288  	for _, ipAddr := range ips {
   289  		ip := ipAddr.IP
   290  		if ipType := gtls.ParseIp(ip); ipType == 4 || ipType == 6 {
   291  			return ip, nil
   292  		}
   293  	}
   294  	return nil, errors.New("dns parse host error")
   295  }
   296  func (obj *DialClient) getDialer(ctxData *reqCtxData, parseDns bool) *net.Dialer {
   297  	var dialOption DialOption
   298  	var isNew bool
   299  	if ctxData.dialTimeout == 0 {
   300  		dialOption.DialTimeout = obj.dialer.Timeout
   301  	} else {
   302  		dialOption.DialTimeout = ctxData.dialTimeout
   303  		if ctxData.dialTimeout != obj.dialer.Timeout {
   304  			isNew = true
   305  		}
   306  	}
   307  
   308  	if ctxData.keepAlive == 0 {
   309  		dialOption.KeepAlive = obj.dialer.KeepAlive
   310  	} else {
   311  		dialOption.KeepAlive = ctxData.keepAlive
   312  		if ctxData.keepAlive != obj.dialer.KeepAlive {
   313  			isNew = true
   314  		}
   315  	}
   316  
   317  	if ctxData.localAddr == nil {
   318  		if obj.dialer.LocalAddr != nil {
   319  			dialOption.LocalAddr = obj.dialer.LocalAddr.(*net.TCPAddr)
   320  		}
   321  	} else {
   322  		dialOption.LocalAddr = ctxData.localAddr
   323  		if ctxData.localAddr.String() != obj.dialer.LocalAddr.String() {
   324  			isNew = true
   325  		}
   326  	}
   327  	if ctxData.dns == nil {
   328  		dialOption.Dns = obj.dns
   329  	} else {
   330  		dialOption.Dns = ctxData.dns
   331  		if parseDns && ctxData.dns.String() != obj.dns.String() {
   332  			isNew = true
   333  		}
   334  	}
   335  	if isNew {
   336  		return NewDialer(dialOption)
   337  	} else {
   338  		return obj.dialer
   339  	}
   340  }
   341  func (obj *DialClient) addTls(ctx context.Context, conn net.Conn, host string, disHttp2 bool, tlsConfig *tls.Config) (*tls.Conn, error) {
   342  	var tlsConn *tls.Conn
   343  	tlsConfig.ServerName = gtls.GetServerName(host)
   344  	if disHttp2 {
   345  		tlsConfig.NextProtos = []string{"http/1.1"}
   346  	} else {
   347  		tlsConfig.NextProtos = []string{"h2", "http/1.1"}
   348  	}
   349  	tlsConn = tls.Client(conn, tlsConfig)
   350  	return tlsConn, tlsConn.HandshakeContext(ctx)
   351  }
   352  func (obj *DialClient) addJa3Tls(ctx context.Context, conn net.Conn, host string, disHttp2 bool, ja3Spec ja3.Ja3Spec, tlsConfig *utls.Config) (*utls.UConn, error) {
   353  	tlsConfig.ServerName = gtls.GetServerName(host)
   354  	if disHttp2 {
   355  		tlsConfig.NextProtos = []string{"http/1.1"}
   356  	} else {
   357  		tlsConfig.NextProtos = []string{"h2", "http/1.1"}
   358  	}
   359  	return ja3.NewClient(ctx, conn, ja3Spec, disHttp2, tlsConfig)
   360  }
   361  func (obj *DialClient) Socks5Proxy(ctx context.Context, ctxData *reqCtxData, network string, addr string, proxyUrl *url.URL) (conn net.Conn, err error) {
   362  	if conn, err = obj.DialContext(ctx, ctxData, network, net.JoinHostPort(proxyUrl.Hostname(), proxyUrl.Port())); err != nil {
   363  		return
   364  	}
   365  	didVerify := make(chan struct{})
   366  	go func() {
   367  		if err = obj.clientVerifySocks5(proxyUrl, addr, conn); err != nil {
   368  			conn.Close()
   369  		}
   370  		close(didVerify)
   371  	}()
   372  	select {
   373  	case <-ctx.Done():
   374  		return conn, ctx.Err()
   375  	case <-didVerify:
   376  		return
   377  	}
   378  }
   379  func (obj *DialClient) clientVerifyHttps(ctx context.Context, scheme string, proxyUrl *url.URL, addr string, host string, conn net.Conn) (err error) {
   380  	hdr := make(http.Header)
   381  	hdr.Set("User-Agent", UserAgent)
   382  	if proxyUrl.User != nil {
   383  		if password, ok := proxyUrl.User.Password(); ok {
   384  			hdr.Set("Proxy-Authorization", "Basic "+tools.Base64Encode(proxyUrl.User.Username()+":"+password))
   385  		}
   386  	}
   387  	cHost := host
   388  	_, hport, _ := net.SplitHostPort(host)
   389  	if hport == "" {
   390  		_, aport, _ := net.SplitHostPort(addr)
   391  		if aport != "" {
   392  			cHost = net.JoinHostPort(cHost, aport)
   393  		} else if scheme == "http" {
   394  			cHost = net.JoinHostPort(cHost, "80")
   395  		} else if scheme == "https" {
   396  			cHost = net.JoinHostPort(cHost, "443")
   397  		} else {
   398  			return errors.New("clientVerifyHttps not found port")
   399  		}
   400  	}
   401  	connectReq, err := NewRequestWithContext(ctx, http.MethodConnect, &url.URL{Opaque: addr}, nil)
   402  	if err != nil {
   403  		return err
   404  	}
   405  	connectReq.Header = hdr
   406  	connectReq.Host = cHost
   407  	if err = connectReq.Write(conn); err != nil {
   408  		return err
   409  	}
   410  	resp, err := readResponse(textproto.NewReader(bufio.NewReader(conn)), connectReq)
   411  	if err != nil {
   412  		return err
   413  	}
   414  	if resp.StatusCode != 200 {
   415  		return errors.New(resp.Status)
   416  	}
   417  	return
   418  }