254 lines
4.9 KiB
Go
254 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
//"bytes"
|
|
"encoding/base64"
|
|
"errors"
|
|
"github.com/ginuerzh/gosocks5"
|
|
"io"
|
|
//"log"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
MethodTLS uint8 = 0x80 + iota
|
|
MethodAES128
|
|
MethodAES192
|
|
MethodAES256
|
|
MethodDES
|
|
MethodBF
|
|
MethodCAST5
|
|
MethodRC4MD5
|
|
MethodRC4
|
|
MethodTable
|
|
)
|
|
|
|
var Methods = map[uint8]string{
|
|
gosocks5.MethodNoAuth: "", // 0x00
|
|
MethodTLS: "tls", // 0x80
|
|
MethodAES128: "aes-128-cfb", // 0x81
|
|
MethodAES192: "aes-192-cfb", // 0x82
|
|
MethodAES256: "aes-256-cfb", // 0x83
|
|
MethodDES: "des-cfb", // 0x84
|
|
MethodBF: "bf-cfb", // 0x85
|
|
MethodCAST5: "cast5-cfb", // 0x86
|
|
MethodRC4MD5: "rc4-md5", // 8x87
|
|
MethodRC4: "rc4", // 0x88
|
|
MethodTable: "table", // 0x89
|
|
}
|
|
|
|
func ToSocksAddr(addr net.Addr) *gosocks5.Addr {
|
|
host, port, _ := net.SplitHostPort(addr.String())
|
|
p, _ := strconv.Atoi(port)
|
|
|
|
return &gosocks5.Addr{
|
|
Type: gosocks5.AddrIPv4,
|
|
Host: host,
|
|
Port: uint16(p),
|
|
}
|
|
}
|
|
|
|
func dial(addr string) (net.Conn, error) {
|
|
taddr, err := net.ResolveTCPAddr("tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return net.DialTCP("tcp", nil, taddr)
|
|
}
|
|
|
|
func connect(addr string) (net.Conn, error) {
|
|
if !strings.Contains(addr, ":") {
|
|
addr += ":80"
|
|
}
|
|
if proxyURL == nil {
|
|
return dial(addr)
|
|
}
|
|
|
|
switch proxyURL.Scheme {
|
|
case "socks": // socks5 proxy
|
|
return connectSocks5Proxy(addr)
|
|
case "http": // http proxy
|
|
fallthrough
|
|
default:
|
|
return connectHTTPProxy(addr)
|
|
}
|
|
|
|
}
|
|
|
|
func connectHTTPProxy(addr string) (conn net.Conn, err error) {
|
|
conn, err = dial(proxyURL.Host)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
req := &http.Request{
|
|
Method: "CONNECT",
|
|
URL: &url.URL{Host: addr},
|
|
Host: addr,
|
|
Header: make(http.Header),
|
|
}
|
|
req.Header.Set("Proxy-Connection", "keep-alive")
|
|
setBasicAuth(req)
|
|
|
|
if err = req.Write(conn); err != nil {
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
|
if err != nil {
|
|
conn.Close()
|
|
return
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
conn.Close()
|
|
//log.Println(resp.Status)
|
|
return nil, errors.New(resp.Status)
|
|
}
|
|
return
|
|
}
|
|
|
|
func connectSocks5Proxy(addr string) (conn net.Conn, err error) {
|
|
conn, err = dial(proxyURL.Host)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
conf := &gosocks5.Config{
|
|
Methods: []uint8{gosocks5.MethodNoAuth, gosocks5.MethodUserPass},
|
|
MethodSelected: proxyMethodSelected,
|
|
}
|
|
|
|
c := gosocks5.ClientConn(conn, conf)
|
|
if err := c.Handleshake(); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
conn = c
|
|
|
|
s := strings.Split(addr, ":")
|
|
host := s[0]
|
|
port := 80
|
|
if len(s) == 2 {
|
|
n, _ := strconv.ParseUint(s[1], 10, 16)
|
|
port = int(n)
|
|
}
|
|
a := &gosocks5.Addr{
|
|
Type: gosocks5.AddrDomain,
|
|
Host: host,
|
|
Port: uint16(port),
|
|
}
|
|
if err := gosocks5.NewRequest(gosocks5.CmdConnect, a).Write(conn); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
rep, err := gosocks5.ReadReply(conn)
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
if rep.Rep != gosocks5.Succeeded {
|
|
conn.Close()
|
|
return nil, errors.New("Socks Failture")
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func proxyMethodSelected(method uint8, conn net.Conn) (net.Conn, error) {
|
|
switch method {
|
|
case gosocks5.MethodUserPass:
|
|
if proxyURL == nil || proxyURL.User == nil {
|
|
return nil, gosocks5.ErrAuthFailure
|
|
}
|
|
pwd, _ := proxyURL.User.Password()
|
|
if err := gosocks5.NewUserPassRequest(gosocks5.UserPassVer,
|
|
proxyURL.User.Username(), pwd).Write(conn); err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := gosocks5.ReadUserPassResponse(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.Status != gosocks5.Succeeded {
|
|
return nil, gosocks5.ErrAuthFailure
|
|
}
|
|
case gosocks5.MethodNoAcceptable:
|
|
return nil, gosocks5.ErrBadMethod
|
|
|
|
//case gosocks5.MethodNoAuth:
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func setBasicAuth(r *http.Request) {
|
|
if proxyURL != nil && proxyURL.User != nil {
|
|
r.Header.Set("Proxy-Authorization",
|
|
"Basic "+base64.StdEncoding.EncodeToString([]byte(proxyURL.User.String())))
|
|
}
|
|
}
|
|
|
|
// based on io.Copy
|
|
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
|
buf := lpool.Take()
|
|
defer lpool.put(buf)
|
|
|
|
for {
|
|
nr, er := src.Read(buf)
|
|
//log.Println("cp r", nr, er)
|
|
if nr > 0 {
|
|
nw, ew := dst.Write(buf[:nr])
|
|
//log.Println("cp w", nw, ew)
|
|
if nw > 0 {
|
|
written += int64(nw)
|
|
}
|
|
if ew != nil {
|
|
err = ew
|
|
break
|
|
}
|
|
/*
|
|
if nr != nw {
|
|
err = io.ErrShortWrite
|
|
break
|
|
}
|
|
*/
|
|
}
|
|
if er == io.EOF {
|
|
break
|
|
}
|
|
if er != nil {
|
|
err = er
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func Pipe(src io.Reader, dst io.Writer, c chan<- error) {
|
|
_, err := Copy(dst, src)
|
|
c <- err
|
|
}
|
|
|
|
func Transport(conn, conn2 net.Conn) (err error) {
|
|
rChan := make(chan error, 1)
|
|
wChan := make(chan error, 1)
|
|
|
|
go Pipe(conn, conn2, wChan)
|
|
go Pipe(conn2, conn, rChan)
|
|
|
|
select {
|
|
case err = <-wChan:
|
|
//log.Println("w exit", err)
|
|
case err = <-rChan:
|
|
//log.Println("r exit", err)
|
|
}
|
|
|
|
return
|
|
}
|