go-socks5/ccsocks5/client.go

203 lines
4.7 KiB
Go
Raw Normal View History

2020-08-06 07:06:33 +00:00
package ccsocks5
import (
"errors"
"net"
2020-08-06 09:52:55 +00:00
"time"
"golang.org/x/net/proxy"
2022-10-17 01:36:23 +00:00
"git.tcp.direct/kayos/go-socks5/bufferpool"
"git.tcp.direct/kayos/go-socks5/statute"
)
2020-08-06 07:06:33 +00:00
// Client is socks5 client.
type Client struct {
proxyAddr string
auth *proxy.Auth
// On command UDP, let server control the tcp and udp connection relationship
2020-08-06 09:48:23 +00:00
proxyConn net.Conn
// real server connection udp/tcp
2020-08-06 07:06:33 +00:00
net.Conn
2020-08-06 09:48:23 +00:00
bufferPool bufferpool.BufPool
}
2020-08-06 07:59:48 +00:00
// NewClient This is just create a client.
// you need to use Dial to create conn.
2020-08-06 08:29:38 +00:00
func NewClient(proxyAddr string, opts ...Option) *Client {
c := &Client{
2020-08-06 09:48:23 +00:00
proxyAddr: proxyAddr,
bufferPool: bufferpool.NewPool(32 * 1024),
}
for _, opt := range opts {
opt(c)
}
2020-08-06 08:29:38 +00:00
return c
}
2020-08-06 07:06:33 +00:00
// Close closes the connection.
func (sf *Client) Close() (err error) {
2020-08-06 09:48:23 +00:00
if sf.proxyConn != nil {
err = sf.proxyConn.Close()
2020-08-06 07:59:48 +00:00
}
2020-08-06 07:06:33 +00:00
if sf.Conn != nil {
err = sf.Conn.Close()
}
return
}
2020-08-06 07:06:33 +00:00
// Dial connects to the address on the named network through proxy , with socks5 handshake.
func (sf *Client) Dial(network, addr string) (net.Conn, error) {
if network == "tcp" {
return sf.DialTCP(network, addr)
}
if network == "udp" {
return sf.DialUDP(network, nil, addr)
}
return nil, errors.New("not support network")
}
2020-08-06 07:06:33 +00:00
// DialTCP connects to the address on the named network through proxy , with socks5 handshake.
2020-08-06 06:35:01 +00:00
func (sf *Client) DialTCP(network, addr string) (net.Conn, error) {
conn := *sf // clone a client
2020-08-06 07:06:33 +00:00
remoteAddress, err := net.ResolveTCPAddr(network, addr)
if err != nil {
return nil, err
}
2020-08-06 10:00:33 +00:00
conn.proxyConn, err = net.Dial(network, sf.proxyAddr)
if err != nil {
return nil, err
}
if _, err := conn.handshake(statute.CommandConnect, addr); err != nil {
conn.Close()
return nil, err
}
2020-08-06 07:06:33 +00:00
conn.Conn = &underConnect{
2020-08-06 09:48:23 +00:00
conn.proxyConn.(*net.TCPConn),
2020-08-06 07:06:33 +00:00
remoteAddress,
}
return &Connect{&conn}, nil
}
2020-08-06 07:06:33 +00:00
// DialUDP connects to the address on the named network through proxy , with socks5 handshake.
2020-08-06 06:35:01 +00:00
func (sf *Client) DialUDP(network string, laddr *net.UDPAddr, raddr string) (net.Conn, error) {
conn := *sf // clone a client
remoteAddress, err := net.ResolveUDPAddr(network, raddr)
if err != nil {
return nil, err
}
2020-08-06 10:00:33 +00:00
conn.proxyConn, err = net.Dial("tcp", sf.proxyAddr)
if err != nil {
return nil, err
}
bndAddress, err := conn.handshake(statute.CommandAssociate, raddr)
if err != nil {
return nil, err
}
ra, err := net.ResolveUDPAddr(network, bndAddress)
if err != nil {
conn.Close()
return nil, err
}
if laddr == nil {
2020-08-06 12:21:26 +00:00
ad := conn.proxyConn.LocalAddr().(*net.TCPAddr)
laddr = &net.UDPAddr{
2020-08-06 12:21:26 +00:00
IP: ad.IP,
Port: ad.Port,
Zone: ad.Zone,
}
}
udpConn, err := net.DialUDP(network, laddr, ra)
if err != nil {
conn.Close()
return nil, err
}
2020-08-06 07:06:33 +00:00
conn.Conn = &underAssociate{
udpConn,
conn.bufferPool,
remoteAddress,
}
return &Associate{&conn}, nil
}
func (sf *Client) handshake(command byte, addr string) (string, error) {
methods := statute.MethodNoAuth
if sf.auth != nil {
methods = statute.MethodUserPassAuth
}
2020-08-06 09:48:23 +00:00
_, err := sf.proxyConn.Write(statute.NewMethodRequest(statute.VersionSocks5, []byte{methods}).Bytes())
if err != nil {
return "", err
}
2020-08-06 09:48:23 +00:00
reply, err := statute.ParseMethodReply(sf.proxyConn)
if err != nil {
return "", err
}
if reply.Ver != statute.VersionSocks5 {
return "", statute.ErrNotSupportVersion
}
if reply.Method != methods {
return "", statute.ErrNotSupportMethod
}
if methods == statute.MethodUserPassAuth {
2020-08-30 03:15:05 +00:00
_, err = sf.proxyConn.Write(statute.NewUserPassRequest(statute.UserPassAuthVersion,
[]byte(sf.auth.User), []byte(sf.auth.Password)).Bytes())
if err != nil {
return "", err
}
2020-08-06 09:48:23 +00:00
rsp, err := statute.ParseUserPassReply(sf.proxyConn)
if err != nil {
return "", err
}
if rsp.Ver != statute.UserPassAuthVersion {
return "", statute.ErrNotSupportMethod
}
if rsp.Status != statute.RepSuccess {
return "", statute.ErrUserAuthFailed
}
}
a, err := statute.ParseAddrSpec(addr)
if err != nil {
return "", err
}
reqHead := statute.Request{
Version: statute.VersionSocks5,
Command: command,
DstAddr: a,
}
2020-08-06 09:48:23 +00:00
if _, err := sf.proxyConn.Write(reqHead.Bytes()); err != nil {
return "", err
}
2020-08-06 09:48:23 +00:00
rspHead, err := statute.ParseReply(sf.proxyConn)
if err != nil {
return "", err
}
if rspHead.Response != statute.RepSuccess {
return "", errors.New("host unreachable")
}
return rspHead.BndAddr.String(), nil
}
2020-08-06 09:52:55 +00:00
// SetKeepAlive sets whether the operating system should send
// keep-alive messages on the connection.
func (sf *Client) SetKeepAlive(keepalive bool) error {
2020-08-06 10:00:33 +00:00
return sf.proxyConn.(*net.TCPConn).SetKeepAlive(keepalive)
2020-08-06 09:52:55 +00:00
}
// SetKeepAlivePeriod sets period between keep-alives.
func (sf *Client) SetKeepAlivePeriod(d time.Duration) error {
2020-08-06 10:00:33 +00:00
return sf.proxyConn.(*net.TCPConn).SetKeepAlivePeriod(d)
2020-08-06 09:52:55 +00:00
}