diff --git a/dial.go b/dial.go index 981249f..2f8eec9 100644 --- a/dial.go +++ b/dial.go @@ -8,7 +8,6 @@ import ( "io" "net" "net/url" - "strconv" "sync" "time" @@ -22,7 +21,7 @@ import ( type msgClient struct { time time.Time - host string + ip net.IP } type DialOption struct { @@ -32,7 +31,6 @@ type DialOption struct { AddrType gtls.AddrType //first ip type Dns *net.UDPAddr GetAddrType func(host string) gtls.AddrType - // EnableNetPoll bool } type dialer interface { @@ -55,6 +53,7 @@ func (d *myDialer) DialContext(ctx context.Context, network string, address stri func (d *myDialer) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) { return d.dialer.Resolver.LookupIPAddr(ctx, host) } + func newDialer(option DialOption) dialer { if option.KeepAlive == 0 { option.KeepAlive = time.Second * 5 @@ -92,52 +91,35 @@ func newDialer(option DialOption) dialer { dialer.dialer.SetMultipathTCP(true) return &dialer } -func (obj *Dialer) dialContext(ctx context.Context, option *RequestOption, network string, addr string, isProxy bool) (net.Conn, error) { +func (obj *Dialer) dialContext(ctx context.Context, option *RequestOption, network string, addr Address, isProxy bool) (net.Conn, error) { if option == nil { option = &RequestOption{} } - host, port, err := net.SplitHostPort(addr) - if err != nil { - return nil, tools.WrapError(err, "addrToIp error,SplitHostPort") + var err error + if addr.IP == nil { + addr.IP, err = obj.loadHost(ctx, addr.Name, option) } - dialer := newDialer(option.DialOption) - if _, ipInt := gtls.ParseHost(host); ipInt == 0 { //domain - host, ok := obj.loadHost(host) - if !ok { //dns parse - var addrType gtls.AddrType - if option.DialOption.AddrType != 0 { - addrType = option.DialOption.AddrType - } else if option.DialOption.GetAddrType != nil { - addrType = option.DialOption.GetAddrType(host) - } - ips, err := dialer.LookupIPAddr(ctx, host) - if err != nil { - return nil, err - } - if host, err = obj.addrToIp(host, ips, addrType); err != nil { - return nil, err - } - if option.Logger != nil { - if isProxy { - option.Logger(Log{ - Id: option.requestId, - Time: time.Now(), - Type: LogType_ProxyDNSLookup, - Msg: host, - }) - } else { - option.Logger(Log{ - Id: option.requestId, - Time: time.Now(), - Type: LogType_DNSLookup, - Msg: host, - }) - } - } - addr = net.JoinHostPort(host, port) + if option.Logger != nil { + if isProxy { + option.Logger(Log{ + Id: option.requestId, + Time: time.Now(), + Type: LogType_ProxyDNSLookup, + Msg: addr.Name, + }) + } else { + option.Logger(Log{ + Id: option.requestId, + Time: time.Now(), + Type: LogType_DNSLookup, + Msg: addr.Name, + }) } } - con, err := dialer.DialContext(ctx, network, addr) + if err != nil { + return nil, err + } + con, err := newDialer(option.DialOption).DialContext(ctx, network, addr.String()) if option.Logger != nil { if isProxy { option.Logger(Log{ @@ -157,10 +139,10 @@ func (obj *Dialer) dialContext(ctx context.Context, option *RequestOption, netwo } return con, err } -func (obj *Dialer) DialContext(ctx context.Context, ctxData *RequestOption, network string, addr string) (net.Conn, error) { +func (obj *Dialer) DialContext(ctx context.Context, ctxData *RequestOption, network string, addr Address) (net.Conn, error) { return obj.dialContext(ctx, ctxData, network, addr, false) } -func (obj *Dialer) ProxyDialContext(ctx context.Context, ctxData *RequestOption, network string, addr string) (net.Conn, error) { +func (obj *Dialer) ProxyDialContext(ctx context.Context, ctxData *RequestOption, network string, addr Address) (net.Conn, error) { return obj.dialContext(ctx, ctxData, network, addr, true) } @@ -208,7 +190,7 @@ func (obj *Dialer) dialProxyContext(ctx context.Context, ctxData *RequestOption, if ctxData == nil { ctxData = &RequestOption{} } - addr, err := getProxyAddr(proxyUrl) + addr, err := GetAddressWithUrl(proxyUrl) if err != nil { return nil, err } @@ -244,7 +226,15 @@ func (obj *Dialer) verifyProxyToRemote(ctx context.Context, option *RequestOptio }) } case "socks5": - err = obj.clientVerifySocks5(conn, proxyUrl, remoteUrl) + var proxyAddress Address + var remoteAddress Address + if proxyAddress, err = GetAddressWithUrl(proxyUrl); err != nil { + return + } + if remoteAddress, err = GetAddressWithUrl(remoteUrl); err != nil { + return + } + err = obj.verifyTCPSocks5(conn, proxyAddress, remoteAddress) if option.Logger != nil { option.Logger(Log{ Id: option.requestId, @@ -263,118 +253,56 @@ func (obj *Dialer) verifyProxyToRemote(ctx context.Context, option *RequestOptio return conn, err } } -func (obj *Dialer) loadHost(host string) (string, bool) { +func (obj *Dialer) loadHost(ctx context.Context, host string, option *RequestOption) (net.IP, error) { msgDataAny, ok := obj.dnsIpData.Load(host) if ok { msgdata := msgDataAny.(msgClient) if time.Since(msgdata.time) < time.Second*60*5 { - return msgdata.host, true + return msgdata.ip, nil } } - return host, false + ip, ipInt := gtls.ParseHost(host) + if ipInt != 0 { + return ip, nil + } + var addrType gtls.AddrType + if option.DialOption.AddrType != 0 { + addrType = option.DialOption.AddrType + } else if option.DialOption.GetAddrType != nil { + addrType = option.DialOption.GetAddrType(host) + } + ips, err := newDialer(option.DialOption).LookupIPAddr(ctx, host) + if err != nil { + return net.IP{}, err + } + if ip, err = obj.addrToIp(host, ips, addrType); err != nil { + return nil, err + } + return ip, nil } -func (obj *Dialer) addrToIp(host string, ips []net.IPAddr, addrType gtls.AddrType) (string, error) { +func (obj *Dialer) addrToIp(host string, ips []net.IPAddr, addrType gtls.AddrType) (net.IP, error) { ip, err := obj.lookupIPAddr(ips, addrType) if err != nil { - return host, tools.WrapError(err, "addrToIp error,lookupIPAddr") + return ip, tools.WrapError(err, "addrToIp error,lookupIPAddr") } - obj.dnsIpData.Store(host, msgClient{time: time.Now(), host: ip.String()}) - return ip.String(), nil + obj.dnsIpData.Store(host, msgClient{time: time.Now(), ip: ip}) + return ip, nil } -func (obj *Dialer) clientVerifySocks5(conn net.Conn, proxyUrl *url.URL, remoteUrl *url.URL) (err error) { - if _, err = conn.Write([]byte{5, 2, 0, 2}); err != nil { +func (obj *Dialer) verifySocks5(conn net.Conn, network string, proxyAddr Address, remoteAddr Address) (proxyAddress Address, err error) { + err = obj.verifySocks5Auth(conn, proxyAddr) + if err != nil { return } - readCon := make([]byte, 4) - if _, err = io.ReadFull(conn, readCon[:2]); err != nil { + err = obj.writeCmd(conn, network) + if err != nil { return } - switch readCon[1] { - case 2: - if proxyUrl.User == nil { - err = errors.New("socks5 need auth") - return - } - pwd, pwdOk := proxyUrl.User.Password() - if !pwdOk { - err = errors.New("socks5 auth error") - return - } - usr := proxyUrl.User.Username() - - if usr == "" { - err = errors.New("socks5 auth user format error") - return - } - if _, err = conn.Write(append( - append( - []byte{1, byte(len(usr))}, - tools.StringToBytes(usr)..., - ), - append( - []byte{byte(len(pwd))}, - tools.StringToBytes(pwd)..., - )..., - )); err != nil { - return - } - if _, err = io.ReadFull(conn, readCon[:2]); err != nil { - return - } - switch readCon[1] { - case 0: - default: - err = errors.New("socks5 auth error") - return - } - case 0: - default: - err = errors.New("not support auth format") - return - } - - host := remoteUrl.Hostname() - var port int - if cport := remoteUrl.Port(); cport == "" { - switch remoteUrl.Scheme { - case "http": - port = 80 - case "https": - port = 443 - case "socks5": - port = 1080 - default: - err = errors.New("not support scheme") - return - } - } else { - port, err = strconv.Atoi(cport) - if err != nil { - return - } - } - writeCon := []byte{5, 1, 0} - ip, ipInt := gtls.ParseHost(host) - switch ipInt { - case 4: - writeCon = append(writeCon, 1) - writeCon = append(writeCon, ip...) - case 6: - writeCon = append(writeCon, 4) - writeCon = append(writeCon, ip...) - case 0: - if len(host) > 255 { - err = errors.New("FQDN too long") - return - } - writeCon = append(writeCon, 3) - writeCon = append(writeCon, byte(len(host))) - writeCon = append(writeCon, host...) - } - writeCon = append(writeCon, byte(port>>8), byte(port)) - if _, err = conn.Write(writeCon); err != nil { + remoteAddr.NetWork = network + err = WriteUdpAddr(conn, remoteAddr) + if err != nil { return } + readCon := make([]byte, 3) if _, err = io.ReadFull(conn, readCon); err != nil { return } @@ -386,32 +314,92 @@ func (obj *Dialer) clientVerifySocks5(conn net.Conn, proxyUrl *url.URL, remoteUr err = errors.New("socks conn error") return } - if readCon[3] != 1 { - err = errors.New("socks conn type error") + proxyAddress, err = ReadUdpAddr(conn) + return +} +func (obj *Dialer) verifyTCPSocks5(conn net.Conn, proxyAddr Address, remoteAddr Address) (err error) { + _, err = obj.verifySocks5(conn, "tcp", proxyAddr, remoteAddr) + return +} + +func (obj *Dialer) verifyUDPSocks5(ctx context.Context, conn net.Conn, proxyAddr Address, remoteAddr Address) (wrapConn net.PacketConn, err error) { + remoteAddr.NetWork = "udp" + proxyAddress, err := obj.verifySocks5(conn, "udp", proxyAddr, remoteAddr) + if err != nil { + return nil, err + } + var listener net.ListenConfig + wrapConn, err = listener.ListenPacket(ctx, "udp", ":0") + if err != nil { + return nil, err + } + wrapConn, err = NewUDPConn(wrapConn, &net.UDPAddr{IP: proxyAddress.IP, Port: proxyAddress.Port}) + if err != nil { + return wrapConn, err + } + go func() { + var buf [1]byte + for { + _, err := conn.Read(buf[:]) + if err != nil { + wrapConn.Close() + break + } + } + }() + return wrapConn, nil +} +func (obj *Dialer) writeCmd(conn net.Conn, network string) (err error) { + var cmd byte + switch network { + case "tcp": + cmd = 1 + case "udp": + cmd = 3 + default: + return errors.New("not support network") + } + _, err = conn.Write([]byte{5, cmd, 0}) + return +} +func (obj *Dialer) verifySocks5Auth(conn net.Conn, proxyAddr Address) (err error) { + if _, err = conn.Write([]byte{5, 2, 0, 2}); err != nil { return } - - switch readCon[3] { - case 1: //ipv4 + readCon := make([]byte, 2) + if _, err = io.ReadFull(conn, readCon); err != nil { + return + } + switch readCon[1] { + case 2: + if proxyAddr.User == "" || proxyAddr.Password == "" { + err = errors.New("socks5 need auth") + return + } + if _, err = conn.Write(append( + append( + []byte{1, byte(len(proxyAddr.User))}, + tools.StringToBytes(proxyAddr.User)..., + ), + append( + []byte{byte(len(proxyAddr.Password))}, + tools.StringToBytes(proxyAddr.Password)..., + )..., + )); err != nil { + return + } if _, err = io.ReadFull(conn, readCon); err != nil { return } - case 3: //domain - if _, err = io.ReadFull(conn, readCon[:1]); err != nil { - return - } - if _, err = io.ReadFull(conn, make([]byte, readCon[0])); err != nil { - return - } - case 4: //IPv6 - if _, err = io.ReadFull(conn, make([]byte, 16)); err != nil { - return + switch readCon[1] { + case 0: + default: + err = errors.New("socks5 auth error") } + case 0: default: - err = errors.New("invalid atyp") - return + err = errors.New("not support auth format") } - _, err = io.ReadFull(conn, readCon[:2]) return } func (obj *Dialer) lookupIPAddr(ips []net.IPAddr, addrType gtls.AddrType) (net.IP, error) { @@ -431,10 +419,6 @@ func (obj *Dialer) lookupIPAddr(ips []net.IPAddr, addrType gtls.AddrType) (net.I } return nil, errors.New("dns parse host error") } - -// func (obj *Dialer) getDialer(option *RequestOption) *net.Dialer { -// return newDialer(option.DialOption) -// } func (obj *Dialer) addTls(ctx context.Context, conn net.Conn, host string, forceHttp1 bool, tlsConfig *tls.Config) (*tls.Conn, error) { var tlsConn *tls.Conn tlsConfig.ServerName = gtls.GetServerName(host) @@ -455,8 +439,8 @@ func (obj *Dialer) addJa3Tls(ctx context.Context, conn net.Conn, host string, fo } return ja3.NewClient(ctx, conn, ja3Spec, forceHttp1, tlsConfig) } -func (obj *Dialer) Socks5Proxy(ctx context.Context, ctxData *RequestOption, network string, proxyUrl *url.URL, remoteUrl *url.URL) (conn net.Conn, err error) { - if conn, err = obj.DialContext(ctx, ctxData, network, net.JoinHostPort(proxyUrl.Hostname(), proxyUrl.Port())); err != nil { +func (obj *Dialer) Socks5TcpProxy(ctx context.Context, ctxData *RequestOption, proxyAddr Address, remoteAddr Address) (conn net.Conn, err error) { + if conn, err = obj.DialContext(ctx, ctxData, "tcp", proxyAddr); err != nil { return } defer func() { @@ -466,8 +450,8 @@ func (obj *Dialer) Socks5Proxy(ctx context.Context, ctxData *RequestOption, netw }() didVerify := make(chan struct{}) go func() { - err = obj.clientVerifySocks5(conn, proxyUrl, remoteUrl) - close(didVerify) + defer close(didVerify) + err = obj.verifyTCPSocks5(conn, proxyAddr, remoteAddr) }() select { case <-ctx.Done(): @@ -476,6 +460,41 @@ func (obj *Dialer) Socks5Proxy(ctx context.Context, ctxData *RequestOption, netw return } } +func (obj *Dialer) Socks5UdpProxy(ctx context.Context, ctxData *RequestOption, proxyAddress Address, remoteAddress Address) (udpConn net.PacketConn, err error) { + conn, err := obj.ProxyDialContext(ctx, ctxData, "tcp", proxyAddress) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + if conn != nil { + conn.Close() + } + if udpConn != nil { + udpConn.Close() + } + } + }() + didVerify := make(chan struct{}) + go func() { + defer close(didVerify) + udpConn, err = obj.verifyUDPSocks5(ctx, conn, proxyAddress, remoteAddress) + if ctxData.Logger != nil { + ctxData.Logger(Log{ + Id: ctxData.requestId, + Time: time.Now(), + Type: LogType_ProxyConnectRemote, + Msg: remoteAddress.String(), + }) + } + }() + select { + case <-ctx.Done(): + return udpConn, context.Cause(ctx) + case <-didVerify: + return + } +} func (obj *Dialer) clientVerifyHttps(ctx context.Context, conn net.Conn, proxyUrl *url.URL, remoteUrl *url.URL) (err error) { hdr := make(http.Header) hdr.Set("User-Agent", tools.UserAgent) @@ -492,6 +511,7 @@ func (obj *Dialer) clientVerifyHttps(ctx context.Context, conn net.Conn, proxyUr if err != nil { return err } + connectReq.Header = hdr connectReq.Host = remoteUrl.Host if err = connectReq.Write(conn); err != nil { diff --git a/go.mod b/go.mod index 4e4e103..9e48a1a 100644 --- a/go.mod +++ b/go.mod @@ -10,23 +10,26 @@ require ( github.com/gospider007/http2 v0.0.0-20241222151842-034aa1d46e9d github.com/gospider007/http3 v0.0.0-20241215120136-980caa047c47 github.com/gospider007/ja3 v0.0.0-20241216123149-83352be79439 + github.com/gospider007/proxy v0.0.0-20241224001512-8d6d724b8ca3 github.com/gospider007/re v0.0.0-20241216142712-efbef8d55ea2 github.com/gospider007/tools v0.0.0-20241216141313-4a832f55a843 github.com/gospider007/websocket v0.0.0-20241216130619-89829336d9a6 + github.com/quic-go/quic-go v0.48.2 + github.com/refraction-networking/uquic v0.0.6 github.com/refraction-networking/utls v1.6.7 golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 golang.org/x/net v0.33.0 ) require ( - github.com/PuerkitoBio/goquery v1.10.0 // indirect + github.com/PuerkitoBio/goquery v1.10.1 // indirect github.com/STARRY-S/zip v0.2.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.0 // indirect github.com/bodgit/windows v1.0.1 // indirect - github.com/caddyserver/certmagic v0.21.4 // indirect + github.com/caddyserver/certmagic v0.21.5 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cloudflare/circl v1.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect @@ -37,6 +40,7 @@ require ( github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/gospider007/blog v0.0.0-20241205091827-6bcaf48620d4 // indirect github.com/gospider007/kinds v0.0.0-20240929092451-8f867acde255 // indirect + github.com/gospider007/net v0.0.0-20241216130419-175071962ced // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect @@ -45,17 +49,15 @@ require ( github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/libdns/libdns v0.2.2 // indirect - github.com/mholt/acmez/v2 v2.0.3 // indirect - github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d // indirect + github.com/mholt/acmez/v3 v3.0.0 // indirect + github.com/mholt/archives v0.0.0-20241226194006-fc8400ac3529 // indirect github.com/miekg/dns v1.1.62 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nwaples/rardecode/v2 v2.0.1 // indirect - github.com/onsi/ginkgo/v2 v2.22.1 // indirect + github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.48.2 // indirect - github.com/refraction-networking/uquic v0.0.6 // indirect github.com/sorairolake/lzip-go v0.3.5 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect @@ -68,6 +70,7 @@ require ( go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap/exp v0.3.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/image v0.23.0 // indirect diff --git a/go.sum b/go.sum index 5e9cb16..73b6081 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= -github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= +github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU= +github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY= github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg= github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= @@ -31,8 +31,8 @@ github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= -github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0= -github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE= +github.com/caddyserver/certmagic v0.21.5 h1:iIga4nZRgd27EIEbX7RZmoRMul+EVBn/h7bAGL83dnY= +github.com/caddyserver/certmagic v0.21.5/go.mod h1:n1sCo7zV1Ez2j+89wrzDxo4N/T1Ws/Vx8u5NvuBFabw= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -113,6 +113,10 @@ github.com/gospider007/ja3 v0.0.0-20241216123149-83352be79439 h1:zDuskJWFKk7JMs7 github.com/gospider007/ja3 v0.0.0-20241216123149-83352be79439/go.mod h1:AZIKhClKQolSI8o6ixFKIVGOC4lWOdsrF4fshR5axO4= github.com/gospider007/kinds v0.0.0-20240929092451-8f867acde255 h1:X+AM/mgmh/EfyQUjKZp1VFc9TSlrhkwS0eSYeo5fMs4= github.com/gospider007/kinds v0.0.0-20240929092451-8f867acde255/go.mod h1:yZx7Zfp1I4P6CO3TcDyDY5SuXQYr0bZjzT9zG0XrJAI= +github.com/gospider007/net v0.0.0-20241216130419-175071962ced h1:vQfZkebsHLjxDk9Wg3neaIsgtyARNmkgrHsh+WroAVY= +github.com/gospider007/net v0.0.0-20241216130419-175071962ced/go.mod h1:GrNK3zEmo7N9nuAcOOu4GFw+kvfkYaOMiRSOm38CH7o= +github.com/gospider007/proxy v0.0.0-20241224001512-8d6d724b8ca3 h1:lc25EnLnhcXk0hmBvmHYh+pOAJoWvdTFVAVG/JWDSiw= +github.com/gospider007/proxy v0.0.0-20241224001512-8d6d724b8ca3/go.mod h1:Zwwc4Sxsy2MIh9V3Bvpj9JXnqJKkLz6TRyJRKIUgqDg= github.com/gospider007/re v0.0.0-20241216142712-efbef8d55ea2 h1:ixXFS1DqP0NnHna+b0JKaPqMRYRmahzUADZn7PawQq0= github.com/gospider007/re v0.0.0-20241216142712-efbef8d55ea2/go.mod h1:kr9bUaC42FS019Ak23fSctbTRB2JpfPPg/pSVjQmsws= github.com/gospider007/tools v0.0.0-20241216141313-4a832f55a843 h1:Q5judZjpY0p9Qui/ovk2vpvunc1wGkk0wgc8ApbTi3U= @@ -147,10 +151,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw= -github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw= -github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d h1:Vw3f39TqFSQLA+OyW+8SouppHTYzX8/fDv6Ao8uj3Ho= -github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I= +github.com/mholt/acmez/v3 v3.0.0 h1:r1NcjuWR0VaKP2BTjDK9LRFBw/WvURx3jlaEUl9Ht8E= +github.com/mholt/acmez/v3 v3.0.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/mholt/archives v0.0.0-20241226194006-fc8400ac3529 h1:XsFbmbdHgEXRCASoX0wlUi1Es+yTDhsmiUo2UVukmLs= +github.com/mholt/archives v0.0.0-20241226194006-fc8400ac3529/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -160,10 +164,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nwaples/rardecode/v2 v2.0.1 h1:3MN6/R+Y4c7e+21U3yhWuUcf72sYmcmr6jtiuAVSH1A= github.com/nwaples/rardecode/v2 v2.0.1/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= -github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= -github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= -github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= -github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -229,6 +233,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -434,8 +440,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/option.go b/option.go index 6578a42..2ae578b 100644 --- a/option.go +++ b/option.go @@ -44,6 +44,7 @@ type Log struct { // Connection Management Options type ClientOption struct { + RequestOption Logger func(Log) //debuggable H3 bool //开启http3 OrderHeaders []string //order headers diff --git a/requests.go b/requests.go index 2273f90..873ea45 100644 --- a/requests.go +++ b/requests.go @@ -35,7 +35,7 @@ func GetRequestOption(ctx context.Context) *RequestOption { if ok { return option } - return nil + return new(RequestOption) } // sends a GET request and returns the response. diff --git a/roundTripper.go b/roundTripper.go index 7ebd18c..f7e3a10 100644 --- a/roundTripper.go +++ b/roundTripper.go @@ -15,6 +15,8 @@ import ( "github.com/gospider007/http2" "github.com/gospider007/http3" "github.com/gospider007/tools" + "github.com/quic-go/quic-go" + uquic "github.com/refraction-networking/uquic" ) type reqTask struct { @@ -89,14 +91,38 @@ func (obj *roundTripper) newConnecotr() *connecotr { return conne } -func (obj *roundTripper) http3Dial(option *RequestOption, req *http.Request) (conn *connecotr, err error) { +func (obj *roundTripper) http3Dial(ctx context.Context, option *RequestOption, proxyUrl *url.URL, remtoeAddress Address) (udpConn net.PacketConn, err error) { + if proxyUrl != nil { + if proxyUrl.Scheme != "socks5" { + err = errors.New("http3 only socks5 proxy supported") + return + } + var proxyAddress Address + proxyAddress, err = GetAddressWithUrl(proxyUrl) + if err != nil { + return nil, err + } + udpConn, err = obj.dialer.Socks5UdpProxy(ctx, option, proxyAddress, remtoeAddress) + } else { + udpConn, err = net.ListenUDP("udp", nil) + } + return +} +func (obj *roundTripper) ghttp3Dial(ctx context.Context, option *RequestOption, proxyUrl *url.URL, remoteAddress Address) (conn *connecotr, err error) { + udpConn, err := obj.http3Dial(ctx, option, proxyUrl, remoteAddress) + if err != nil { + return nil, err + } tlsConfig := option.TlsConfig.Clone() tlsConfig.NextProtos = []string{http3.NextProtoH3} - tlsConfig.ServerName = req.Host - netConn, err := http3.Dial(req.Context(), getAddr(req.URL), tlsConfig, nil) - if err != nil { - return + tlsConfig.ServerName = remoteAddress.Host + if remoteAddress.IP == nil { + remoteAddress.IP, err = obj.dialer.loadHost(ctx, remoteAddress.Name, option) + if err != nil { + return nil, err + } } + netConn, err := quic.DialEarly(ctx, udpConn, &net.UDPAddr{IP: remoteAddress.IP, Port: remoteAddress.Port}, tlsConfig, nil) conn = obj.newConnecotr() conn.Conn = http3.NewClient(netConn, func() { conn.forceCnl(errors.New("http3 client close")) @@ -104,14 +130,21 @@ func (obj *roundTripper) http3Dial(option *RequestOption, req *http.Request) (co return } -func (obj *roundTripper) ghttp3Dial(option *RequestOption, req *http.Request) (conn *connecotr, err error) { +func (obj *roundTripper) uhttp3Dial(ctx context.Context, option *RequestOption, proxyUrl *url.URL, remoteAddress Address) (conn *connecotr, err error) { + udpConn, err := obj.http3Dial(ctx, option, proxyUrl, remoteAddress) + if err != nil { + return nil, err + } tlsConfig := option.UtlsConfig.Clone() tlsConfig.NextProtos = []string{http3.NextProtoH3} - tlsConfig.ServerName = req.Host - netConn, err := http3.UDial(req.Context(), getAddr(req.URL), tlsConfig, nil) - if err != nil { - return + tlsConfig.ServerName = remoteAddress.Host + if remoteAddress.IP == nil { + remoteAddress.IP, err = obj.dialer.loadHost(ctx, remoteAddress.Name, option) + if err != nil { + return nil, err + } } + netConn, err := uquic.DialEarly(ctx, udpConn, remoteAddress, tlsConfig, nil) conn = obj.newConnecotr() conn.Conn = http3.NewUClient(netConn, func() { conn.forceCnl(errors.New("http3 client close")) @@ -120,22 +153,35 @@ func (obj *roundTripper) ghttp3Dial(option *RequestOption, req *http.Request) (c } func (obj *roundTripper) dial(option *RequestOption, req *http.Request) (conn *connecotr, err error) { - if option.H3 { - if option.Ja3Spec.IsSet() { - return obj.ghttp3Dial(option, req) - } else { - return obj.http3Dial(option, req) - } - } proxys, err := obj.initProxys(option, req) if err != nil { return nil, err } + if option.H3 { + var proxyUrl *url.URL + if len(proxys) > 0 { + proxyUrl = proxys[0] + } + remoteAddress, err := GetAddressWithUrl(req.URL) + if err != nil { + return nil, err + } + if option.Ja3Spec.IsSet() { + return obj.uhttp3Dial(req.Context(), option, proxyUrl, remoteAddress) + } else { + return obj.ghttp3Dial(req.Context(), option, proxyUrl, remoteAddress) + } + } var netConn net.Conn if len(proxys) > 0 { netConn, err = obj.dialer.DialProxyContext(req.Context(), option, "tcp", option.TlsConfig.Clone(), append(proxys, cloneUrl(req.URL))...) } else { - netConn, err = obj.dialer.DialContext(req.Context(), option, "tcp", getAddr(req.URL)) + var remoteAddress Address + remoteAddress, err = GetAddressWithUrl(req.URL) + if err != nil { + return nil, err + } + netConn, err = obj.dialer.DialContext(req.Context(), option, "tcp", remoteAddress) } defer func() { if err != nil && netConn != nil { diff --git a/socks5.go b/socks5.go new file mode 100644 index 0000000..3c15058 --- /dev/null +++ b/socks5.go @@ -0,0 +1,307 @@ +package requests + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "math" + "net" + "strconv" + "time" +) + +var ( + errBadHeader = errors.New("bad header") + errUnsupportedMethod = errors.New("unsupported method") +) + +const MaxUdpPacket int = math.MaxUint16 - 28 +const ( + ipv4Address = 0x01 + fqdnAddress = 0x03 + ipv6Address = 0x04 +) + +func WriteUdpAddr(w io.Writer, addr Address) error { + if addr.IP != nil { + if ip4 := addr.IP.To4(); ip4 != nil { + _, err := w.Write([]byte{ipv4Address}) + if err != nil { + return err + } + _, err = w.Write(ip4) + if err != nil { + return err + } + } else if ip6 := addr.IP.To16(); ip6 != nil { + _, err := w.Write([]byte{ipv6Address}) + if err != nil { + return err + } + _, err = w.Write(ip6) + if err != nil { + return err + } + } else { + _, err := w.Write([]byte{ipv4Address, 0, 0, 0, 0}) + if err != nil { + return err + } + } + } else if addr.Name != "" { + if len(addr.Name) > 255 { + return errors.New("errStringTooLong") + } + _, err := w.Write([]byte{fqdnAddress, byte(len(addr.Name))}) + if err != nil { + return err + } + _, err = w.Write([]byte(addr.Name)) + if err != nil { + return err + } + } else { + _, err := w.Write([]byte{ipv4Address, 0, 0, 0, 0}) + if err != nil { + return err + } + } + var p [2]byte + binary.BigEndian.PutUint16(p[:], uint16(addr.Port)) + _, err := w.Write(p[:]) + return err +} + +type Address struct { + User string + Password string + Name string + Host string + IP net.IP + Port int + NetWork string +} + +func (a Address) String() string { + port := strconv.Itoa(a.Port) + if len(a.IP) != 0 { + return net.JoinHostPort(a.IP.String(), port) + } + return net.JoinHostPort(a.Name, port) +} +func (a Address) Network() string { + return a.NetWork +} + +func ReadUdpAddr(r io.Reader) (Address, error) { + UdpAddress := Address{} + var addrType [1]byte + if _, err := r.Read(addrType[:]); err != nil { + return UdpAddress, err + } + + switch addrType[0] { + case ipv4Address: + addr := make(net.IP, net.IPv4len) + if _, err := io.ReadFull(r, addr); err != nil { + return UdpAddress, err + } + UdpAddress.IP = addr + case ipv6Address: + addr := make(net.IP, net.IPv6len) + if _, err := io.ReadFull(r, addr); err != nil { + return UdpAddress, err + } + UdpAddress.IP = addr + case fqdnAddress: + if _, err := r.Read(addrType[:]); err != nil { + return UdpAddress, err + } + addrLen := int(addrType[0]) + fqdn := make([]byte, addrLen) + if _, err := io.ReadFull(r, fqdn); err != nil { + return UdpAddress, err + } + UdpAddress.Name = string(fqdn) + default: + return UdpAddress, errors.New("invalid atyp") + } + var port [2]byte + if _, err := io.ReadFull(r, port[:]); err != nil { + return UdpAddress, err + } + UdpAddress.Port = int(binary.BigEndian.Uint16(port[:])) + return UdpAddress, nil +} + +type UDPConn struct { + bufRead [MaxUdpPacket]byte + bufWrite [MaxUdpPacket]byte + proxyAddress net.Addr + // defaultTarget net.Addr + prefix []byte + net.PacketConn +} + +func NewUDPConn(raw net.PacketConn, proxyAddress net.Addr) (*UDPConn, error) { + conn := &UDPConn{ + PacketConn: raw, + proxyAddress: proxyAddress, + prefix: []byte{0, 0, 0}, + } + return conn, nil +} + +// func NewUDPConn(raw net.PacketConn, proxyAddress net.Addr, defaultTarget net.Addr) (*UDPConn, error) { +// conn := &UDPConn{ +// PacketConn: raw, +// proxyAddress: proxyAddress, +// defaultTarget: defaultTarget, +// prefix: []byte{0, 0, 0}, +// } +// return conn, nil +// } + +// ReadFrom implements the net.PacketConn ReadFrom method. +func (c *UDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + n, addr, err = c.PacketConn.ReadFrom(c.bufRead[:]) + if err != nil { + return 0, nil, err + } + if n < len(c.prefix) || addr.String() != c.proxyAddress.String() { + return 0, nil, errBadHeader + } + buf := bytes.NewBuffer(c.bufRead[len(c.prefix):n]) + a, err := ReadUdpAddr(buf) + if err != nil { + return 0, nil, err + } + n = copy(p, buf.Bytes()) + return n, a, nil +} + +// WriteTo implements the net.PacketConn WriteTo method. +func (c *UDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + buf := bytes.NewBuffer(c.bufWrite[:0]) + buf.Write(c.prefix) + udpAddr := addr.(*net.UDPAddr) + err = WriteUdpAddr(buf, Address{IP: udpAddr.IP, Port: udpAddr.Port}) + if err != nil { + return 0, err + } + n, err = buf.Write(p) + if err != nil { + return 0, err + } + + data := buf.Bytes() + _, err = c.PacketConn.WriteTo(data, c.proxyAddress) + if err != nil { + return 0, err + } + return n, nil +} + +// // Read implements the net.Conn Read method. +// func (c *UDPConn) Read(b []byte) (int, error) { +// n, addr, err := c.ReadFrom(b) +// if err != nil { +// return 0, err +// } +// if addr.String() != c.defaultTarget.String() { +// return c.Read(b) +// } +// return n, nil +// } + +// // Write implements the net.Conn Write method. +// func (c *UDPConn) Write(b []byte) (int, error) { +// return c.WriteTo(b, c.defaultTarget) +// } + +// // RemoteAddr implements the net.Conn RemoteAddr method. +// func (c *UDPConn) RemoteAddr() net.Addr { +// return c.defaultTarget +// } + +// SetReadBuffer implements the net.UDPConn SetReadBuffer method. +func (c *UDPConn) SetReadBuffer(bytes int) error { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return errUnsupportedMethod + } + return udpConn.SetReadBuffer(bytes) +} + +// SetWriteBuffer implements the net.UDPConn SetWriteBuffer method. +func (c *UDPConn) SetWriteBuffer(bytes int) error { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return errUnsupportedMethod + } + return udpConn.SetWriteBuffer(bytes) +} + +// SetDeadline implements the Conn SetDeadline method. +func (c *UDPConn) SetDeadline(t time.Time) error { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return errUnsupportedMethod + } + return udpConn.SetDeadline(t) +} + +// SetReadDeadline implements the Conn SetReadDeadline method. +func (c *UDPConn) SetReadDeadline(t time.Time) error { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return errUnsupportedMethod + } + return udpConn.SetReadDeadline(t) +} + +// SetWriteDeadline implements the Conn SetWriteDeadline method. +func (c *UDPConn) SetWriteDeadline(t time.Time) error { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return errUnsupportedMethod + } + return udpConn.SetWriteDeadline(t) +} + +// ReadFromUDP implements the net.UDPConn ReadFromUDP method. +func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return 0, nil, errUnsupportedMethod + } + return udpConn.ReadFromUDP(b) +} + +// ReadMsgUDP implements the net.UDPConn ReadMsgUDP method. +func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return 0, 0, 0, nil, errUnsupportedMethod + } + return udpConn.ReadMsgUDP(b, oob) +} + +// WriteToUDP implements the net.UDPConn WriteToUDP method. +func (c *UDPConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return 0, errUnsupportedMethod + } + return udpConn.WriteToUDP(b, addr) +} + +// WriteMsgUDP implements the net.UDPConn WriteMsgUDP method. +func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) { + udpConn, ok := c.PacketConn.(*net.UDPConn) + if !ok { + return 0, 0, errUnsupportedMethod + } + return udpConn.WriteMsgUDP(b, oob, addr) +} diff --git a/test/proxy/http3_proxy_test.go b/test/proxy/http3_proxy_test.go new file mode 100644 index 0000000..da760dd --- /dev/null +++ b/test/proxy/http3_proxy_test.go @@ -0,0 +1,94 @@ +package main + +import ( + "crypto/tls" + "log" + "testing" + + "fmt" + "io" + "net/http" + "time" + + "github.com/gospider007/gtls" + "github.com/gospider007/proxy" + "github.com/gospider007/requests" + "github.com/quic-go/quic-go/http3" +) + +var ( + proxyHost = "127.0.0.1:1080" + remoteHost = "127.0.0.1:8080" +) + +func client() { + for range 5 { + resp, err := requests.Post(nil, "https://"+remoteHost, requests.RequestOption{ + H3: true, + Logger: func(l requests.Log) { + log.Print(l) + }, + Proxy: "socks5://" + proxyHost, + Body: []byte("hello, server!"), + }) + if err != nil { + fmt.Println(err) + continue + } + + fmt.Println(resp.StatusCode()) + fmt.Println(resp.Text()) + time.Sleep(time.Second) + } +} + +func server() { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + fmt.Fprint(w, "hello, world!") + case http.MethodPost: + result, err := io.ReadAll(r.Body) + if err != nil { + fmt.Fprint(w, "error:", err.Error()) + return + } + fmt.Fprintf(w, "echo:'%s'", string(result)) + default: + fmt.Fprint(w, "method is not supported") + } + }) + tlsCert, err := gtls.CreateProxyCertWithName("localhost") + if err != nil { + panic(err) + } + server := http3.Server{ + Addr: "0.0.0.0:8080", + Handler: mux, + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + NextProtos: []string{"http3-echo-example"}, + }, + } + fmt.Println("Server is listening...") + fmt.Println(server.ListenAndServe()) +} +func proxyServer() { + c, err := proxy.NewClient(nil, proxy.ClientOption{ + Addr: ":1080", + Debug: true, + DisVerify: true, + }) + if err != nil { + panic(err) + } + c.Run() +} + +func TestHttp3Proxy(t *testing.T) { + go server() + go proxyServer() + time.Sleep(time.Second * 3) + client() +} diff --git a/tools.go b/tools.go index 3a90bc3..33016c5 100644 --- a/tools.go +++ b/tools.go @@ -3,15 +3,18 @@ package requests import ( "bufio" "context" + "errors" "fmt" "io" "net" "net/http" "net/textproto" "net/url" + "strconv" "strings" _ "unsafe" + "github.com/gospider007/gtls" "github.com/gospider007/ja3" "github.com/gospider007/tools" "golang.org/x/exp/slices" @@ -50,6 +53,60 @@ func getAddr(uurl *url.URL) (addr string) { } return uurl.Host } +func GetAddressWithUrl(uurl *url.URL) (addr Address, err error) { + if uurl == nil { + return Address{}, errors.New("url is nil") + } + var port int + portStr := uurl.Port() + if portStr == "" { + switch uurl.Scheme { + case "http": + port = 80 + case "https": + port = 443 + case "socks5": + port = 1080 + default: + return Address{}, errors.New("unknown scheme") + } + } else { + port, err = strconv.Atoi(portStr) + if err != nil { + return Address{}, err + } + } + ip, _ := gtls.ParseHost(uurl.Hostname()) + addr = Address{ + Name: uurl.Hostname(), + Host: uurl.Host, + IP: ip, + Port: port, + } + if uurl.User != nil { + addr.User = uurl.User.Username() + addr.Password, _ = uurl.User.Password() + } + return +} +func GetAddressWithAddr(addrS string) (addr Address, err error) { + host, port, err := net.SplitHostPort(addrS) + if err != nil { + return Address{}, err + } + ip, _ := gtls.ParseHost(host) + portInt, err := strconv.Atoi(port) + if err != nil { + return Address{}, err + } + addr = Address{ + Name: host, + IP: ip, + Host: addrS, + Port: portInt, + } + return +} func cloneUrl(u *url.URL) *url.URL { if u == nil { return nil