You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
3.5 KiB
Go

package socks
import (
"encoding/binary"
"fmt"
"io"
"net"
"strconv"
)
// Client is a net.Conn with socks5 support
type Client struct {
net.Conn
// socks5 username
Username string
// socks5 password
Password string
handshakeDone bool
connected bool
closed bool
}
func (sc *Client) handShake() error {
if sc.handshakeDone {
return nil
}
// password auth or none
if _, err := sc.Conn.Write([]byte{socks5Version, 0x02, 0x00, 0x02}); err != nil {
return err
}
buf := make([]byte, 512)
if _, err := io.ReadFull(sc.Conn, buf[:2]); err != nil {
return err
}
if buf[0] != socks5Version {
return fmt.Errorf("error socks version %d", buf[0])
}
if buf[1] != 0x00 && buf[1] != 0x02 {
return fmt.Errorf("server return with code %d", buf[1])
}
if buf[1] == 0x00 {
sc.handshakeDone = true
return nil
}
// password auth
l := 3 + len(sc.Username) + len(sc.Password)
buf[0] = 0x01 // auth protocol version
buf[1] = byte(len(sc.Username)) // username length
copy(buf[2:], []byte(sc.Username)) // username
buf[2+len(sc.Username)] = byte(len(sc.Password)) // password length
copy(buf[3+len(sc.Username):], []byte(sc.Password)) //password
if _, err := sc.Conn.Write(buf[:l]); err != nil {
return err
}
if _, err := sc.Conn.Read(buf[:2]); err != nil {
return err
}
if buf[0] != 0x01 {
return fmt.Errorf("unexpected auth protocol version %v", buf[0])
}
// password auth success
if buf[1] == 0x00 {
sc.handshakeDone = true
return nil
}
return fmt.Errorf("password rejected")
}
// Dial dial to the addr from socks server,
// this is net.Dial style,
// can call sc.Connect instead
func (sc *Client) Dial(network, addr string) (net.Conn, error) {
switch network {
case "tcp":
default:
return nil, fmt.Errorf("unsupported network type: %s", network)
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
if err = sc.Connect(host, uint16(p)); err != nil {
return nil, err
}
return sc, nil
}
// Connect handshakes with the socks server and request the
// server to connect to the target host and port
func (sc *Client) Connect(host string, port uint16) error {
if !sc.handshakeDone {
if err := sc.handShake(); err != nil {
return err
}
}
if sc.connected {
return fmt.Errorf("only one connection allowed")
}
buf := make([]byte, 512)
l := 4 + len(host) + 1 + 2
buf[0] = socks5Version
buf[1] = cmdConnect
buf[2] = 0x00
buf[3] = addrTypeDomain
buf[4] = byte(len(host))
copy(buf[5:5+len(host)], []byte(host))
binary.BigEndian.PutUint16(buf[l-2:l], port)
if _, err := sc.Conn.Write(buf[:l]); err != nil {
return err
}
if _, err := io.ReadAtLeast(sc.Conn, buf, 10); err != nil {
return err
}
if buf[0] != socks5Version {
return fmt.Errorf("error socks version %d", buf[0])
}
if buf[1] != 0x00 {
return fmt.Errorf("server error code %d", buf[1])
}
sc.connected = true
return nil
}
// Read read from the underlying connection
func (sc *Client) Read(b []byte) (int, error) {
if !sc.connected {
return 0, fmt.Errorf("call connect first")
}
return sc.Conn.Read(b)
}
// Write write data to underlying connection
func (sc *Client) Write(b []byte) (int, error) {
if !sc.connected {
return 0, fmt.Errorf("call connect first")
}
return sc.Conn.Write(b)
}
// Close close the underlying connection
func (sc *Client) Close() error {
if !sc.closed {
sc.closed = true
return sc.Conn.Close()
}
return nil
}