package main
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"strconv"
"time"
)
func main() {
tr := &http.Transport{Dial: dial("socks5://192.168.16.22:1080?timeout=5s")}
httpClient := &http.Client{Transport: tr}
resp, err := httpClient.Get("https://www.google.com")
if err != nil {
log.Fatal(err)
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
panic(err)
}
}(resp.Body)
if resp.StatusCode != http.StatusOK {
log.Fatal(resp.StatusCode)
}
buf, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf))
}
type (
Config struct {
Proto int
Host string
Auth *Auth
Timeout time.Duration
}
Auth struct {
Username string
Password string
}
)
func parse(proxyURI string) (*Config, error) {
uri, err := url.Parse(proxyURI)
if err != nil {
return nil, err
}
cfg := &Config{}
switch uri.Scheme {
case "socks4":
cfg.Proto = SOCKS4
case "socks4a":
cfg.Proto = SOCKS4A
case "socks5":
cfg.Proto = SOCKS5
default:
return nil, fmt.Errorf("unknown SOCKS protocol %s", uri.Scheme)
}
cfg.Host = uri.Host
user := uri.User.Username()
password, _ := uri.User.Password()
if user != "" || password != "" {
if user == "" || password == "" || len(user) > 255 || len(password) > 255 {
return nil, errors.New("invalid user name or password")
}
cfg.Auth = &Auth{
Username: user,
Password: password,
}
}
query := uri.Query()
timeout := query.Get("timeout")
if timeout != "" {
var err error
cfg.Timeout, err = time.ParseDuration(timeout)
if err != nil {
return nil, err
}
}
return cfg, nil
}
type requestBuilder struct {
bytes.Buffer
}
func (b *requestBuilder) add(data ...byte) {
_, _ = b.Write(data)
}
func (config *Config) sendReceive(conn net.Conn, req []byte) (resp []byte, err error) {
if config.Timeout > 0 {
if err := conn.SetWriteDeadline(time.Now().Add(config.Timeout)); err != nil {
return nil, err
}
}
_, err = conn.Write(req)
if err != nil {
return
}
resp, err = config.readAll(conn)
return
}
func (config *Config) readAll(conn net.Conn) (resp []byte, err error) {
resp = make([]byte, 1024)
if config.Timeout > 0 {
if err := conn.SetReadDeadline(time.Now().Add(config.Timeout)); err != nil {
return nil, err
}
}
n, err := conn.Read(resp)
resp = resp[:n]
return
}
func lookupIPv4(host string) (net.IP, error) {
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
for _, ip := range ips {
ipv4 := ip.To4()
if ipv4 == nil {
continue
}
return ipv4, nil
}
return nil, fmt.Errorf("no IPv4 address found for host: %s", host)
}
func splitHostPort(addr string) (host string, port uint16, err error) {
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return "", 0, err
}
portInt, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return "", 0, err
}
port = uint16(portInt)
return
}
const (
SOCKS4 = iota
SOCKS4A
SOCKS5
)
func dial(proxyURI string) func(string, string) (net.Conn, error) {
cfg, err := parse(proxyURI)
if err != nil {
return dialError(err)
}
return cfg.dialFunc()
}
func (config *Config) dialFunc() func(string, string) (net.Conn, error) {
switch config.Proto {
case SOCKS5:
return func(_, targetAddr string) (conn net.Conn, err error) {
return config.dialSocks5(targetAddr)
}
case SOCKS4, SOCKS4A:
return func(_, targetAddr string) (conn net.Conn, err error) {
return config.dialSocks4(targetAddr)
}
}
return dialError(fmt.Errorf("unknown SOCKS protocol %v", config.Proto))
}
func dialError(err error) func(string, string) (net.Conn, error) {
return func(_, _ string) (net.Conn, error) {
return nil, err
}
}
func (config *Config) dialSocks4(targetAddr string) (_ net.Conn, err error) {
socksType := config.Proto
proxy := config.Host
conn, err := net.DialTimeout("tcp", proxy, config.Timeout)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
err := conn.Close()
if err != nil {
panic(err)
}
}
}()
host, port, err := splitHostPort(targetAddr)
if err != nil {
return nil, err
}
ip := net.IPv4(0, 0, 0, 1).To4()
if socksType == SOCKS4 {
ip, err = lookupIPv4(host)
if err != nil {
return nil, err
}
}
req := []byte{
4,
1,
byte(port >> 8),
byte(port),
ip[0], ip[1], ip[2], ip[3],
0,
}
if socksType == SOCKS4A {
req = append(req, []byte(host+"\x00")...)
}
resp, err := config.sendReceive(conn, req)
if err != nil {
return nil, err
} else if len(resp) != 8 {
return nil, errors.New("server does not respond properly")
}
switch resp[1] {
case 90:
case 91:
return nil, errors.New("socks connection request rejected or failed")
case 92:
return nil, errors.New("socks connection request rejected because SOCKS server cannot connect to identd on the client")
case 93:
return nil, errors.New("socks connection request rejected because the client program and identd report different user-ids")
default:
return nil, errors.New("socks connection request failed, unknown error")
}
if err := conn.SetDeadline(time.Time{}); err != nil {
return nil, err
}
return conn, nil
}
func (config *Config) dialSocks5(targetAddr string) (_ net.Conn, err error) {
proxy := config.Host
conn, err := net.DialTimeout("tcp", proxy, config.Timeout)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
err := conn.Close()
if err != nil {
panic(err)
}
}
}()
var req requestBuilder
version := byte(5)
method := byte(0)
if config.Auth != nil {
method = 2
}
req.add(
version,
1,
method,
)
resp, err := config.sendReceive(conn, req.Bytes())
if err != nil {
return nil, err
} else if len(resp) != 2 {
return nil, errors.New("server does not respond properly")
} else if resp[0] != 5 {
return nil, errors.New("server does not support Socks 5")
} else if resp[1] != method {
return nil, errors.New("socks method negotiation failed")
}
if config.Auth != nil {
version := byte(1)
req.Reset()
req.add(
version,
byte(len(config.Auth.Username)),
)
req.add([]byte(config.Auth.Username)...)
req.add(byte(len(config.Auth.Password)))
req.add([]byte(config.Auth.Password)...)
resp, err := config.sendReceive(conn, req.Bytes())
if err != nil {
return nil, err
} else if len(resp) != 2 {
return nil, errors.New("server does not respond properly")
} else if resp[0] != version {
return nil, errors.New("server does not support user/password version 1")
} else if resp[1] != 0 {
return nil, errors.New("user/password login failed")
}
}
host, port, err := splitHostPort(targetAddr)
if err != nil {
return nil, err
}
req.Reset()
req.add(
5,
1,
0,
3,
byte(len(host)),
)
req.add([]byte(host)...)
req.add(
byte(port>>8),
byte(port),
)
resp, err = config.sendReceive(conn, req.Bytes())
if err != nil {
return
} else if len(resp) != 10 {
return nil, errors.New("server does not respond properly")
} else if resp[1] != 0 {
return nil, errors.New("can't complete SOCKS5 connection")
}
return conn, nil
}