-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
shikanon
committed
Oct 26, 2018
1 parent
5ad1e32
commit 6928e98
Showing
3 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Socks5 Proxy | ||
------ | ||
|
||
用golang 实现了一个简单的socks5协议来实现代理转发。 | ||
|
||
``` | ||
socksproxy.go `socks5转发协议` | ||
httpproxy.go `http转发协议` | ||
``` | ||
|
||
## TODO | ||
[] 混淆加密 | ||
[] 客户端 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net" | ||
"net/url" | ||
"strings" | ||
) | ||
|
||
func main() { | ||
log.SetFlags(log.LstdFlags|log.Lshortfile) | ||
l, err := net.Listen("tcp", ":18081") | ||
if err != nil { | ||
log.Panic(err) | ||
} | ||
log.Println("start!") | ||
|
||
for { | ||
client, err := l.Accept() | ||
if err != nil { | ||
log.Panic(err) | ||
} | ||
|
||
go handleClientRequest(client) | ||
} | ||
} | ||
|
||
func handleClientRequest(client net.Conn) { | ||
if client == nil { | ||
return | ||
} | ||
defer client.Close() | ||
|
||
var b [1024]byte | ||
n, err := client.Read(b[:]) | ||
if err != nil { | ||
log.Println(err) | ||
return | ||
} | ||
var method, host, address string | ||
fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &host) | ||
hostPortURL, err := url.Parse(host) | ||
if err != nil { | ||
log.Println(err) | ||
return | ||
} | ||
|
||
|
||
if hostPortURL.Opaque == "443" { | ||
address = hostPortURL.Scheme + ":443" | ||
} else { //http访问 | ||
if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80 | ||
address = hostPortURL.Host + ":80" | ||
} else { | ||
address = hostPortURL.Host | ||
} | ||
} | ||
log.Println(address) | ||
|
||
//获得了请求的host和port,就开始拨号吧 | ||
server, err := net.Dial("tcp", address) | ||
if err != nil { | ||
log.Println(err) | ||
return | ||
} | ||
if method == "CONNECT" { | ||
fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n") | ||
} else { | ||
server.Write(b[:n]) | ||
} | ||
//进行转发 | ||
go io.Copy(server, client) | ||
io.Copy(client, server) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
package main | ||
|
||
import ( | ||
"net" | ||
"flag" | ||
"log" | ||
"errors" | ||
"io" | ||
"encoding/binary" | ||
"sync" | ||
) | ||
|
||
/** | ||
The localConn connects to the dstServer, and sends a ver | ||
identifier/method selection message: | ||
+----+----------+----------+ | ||
|VER | NMETHODS | METHODS | | ||
+----+----------+----------+ | ||
| 1 | 1 | 1 to 255 | | ||
+----+----------+----------+ | ||
The VER field is set to X'05' for this ver of the protocol. The | ||
NMETHODS field contains the number of method identifier octets that | ||
appear in the METHODS field. | ||
METHODS常见的几种方式如下: | ||
1>.数字“0”:表示不需要用户名或者密码验证;, | ||
2>.数字“1”:GSSAPI是SSH支持的一种验证方式; | ||
3>.数字“2”:表示需要用户名和密码进行验证; | ||
4>.数字“3”至“7F”:表示用于IANA 分配(IANA ASSIGNED) | ||
5>.数字“80”至“FE”表示私人方法保留(RESERVED FOR PRIVATE METHODS) | ||
4>.数字“FF”:不支持所有的验证方式,无法进行连接 | ||
*/ | ||
type ProtocolVersion struct { | ||
VER uint8 | ||
NMETHODS uint8 | ||
METHODS []uint8 | ||
} | ||
|
||
|
||
func (s *ProtocolVersion) handshake(conn net.Conn) error { | ||
b := make([]byte, 255) | ||
n, err := conn.Read(b) | ||
if err != nil { | ||
log.Println(err) | ||
return err | ||
} | ||
s.VER = b[0] //ReadByte reads and returns a single byte,第一个参数为socks的版本号 | ||
s.NMETHODS = b[1] //nmethods是记录methods的长度的。nmethods的长度是1个字节 | ||
if n != int(2+s.NMETHODS) { | ||
return errors.New("协议错误, sNMETHODS不对") | ||
} | ||
s.METHODS = b[2:2+s.NMETHODS] //读取指定长度信息,读取正好len(buf)长度的字节。如果字节数不是指定长度,则返回错误信息和正确的字节数 | ||
|
||
if s.VER != 5 { | ||
return errors.New("该协议不是socks5协议") | ||
} | ||
|
||
//服务器回应客户端消息: | ||
//第一个参数表示版本号为5,即socks5协议, | ||
// 第二个参数表示服务端选中的认证方法,0即无需密码访问, 2表示需要用户名和密码进行验证。 | ||
resp :=[]byte{5, 0} | ||
conn.Write(resp) | ||
return nil | ||
} | ||
|
||
/* | ||
This begins with the client producing a | ||
Username/Password request: | ||
+----+------+----------+------+----------+ | ||
|VER | ULEN | UNAME | PLEN | PASSWD | | ||
+----+------+----------+------+----------+ | ||
| 1 | 1 | 1 to 255 | 1 | 1 to 255 | | ||
+----+------+----------+------+----------+ | ||
*/ | ||
|
||
type Socks5Auth struct { | ||
VER uint8 | ||
ULEN uint8 | ||
UNAME string | ||
PLEN uint8 | ||
PASSWD string | ||
} | ||
|
||
func (s *Socks5Auth) authenticate(conn net.Conn) error { | ||
b := make([]byte, 128) | ||
n, err := conn.Read(b) | ||
if err != nil{ | ||
log.Println(err) | ||
return err | ||
} | ||
|
||
s.VER = b[0] | ||
if s.VER != 5 { | ||
return errors.New("该协议不是socks5协议") | ||
} | ||
|
||
s.ULEN = b[1] | ||
s.UNAME = string(b[2:2+s.ULEN]) | ||
s.PLEN = b[2+s.ULEN+1] | ||
s.PASSWD = string(b[n-int(s.PLEN):n]) | ||
log.Println(s.UNAME, s.PASSWD) | ||
|
||
/** | ||
回应客户端,响应客户端连接成功 | ||
The server verifies the supplied UNAME and PASSWD, and sends the | ||
following response: | ||
+----+--------+ | ||
|VER | STATUS | | ||
+----+--------+ | ||
| 1 | 1 | | ||
+----+--------+ | ||
A STATUS field of X'00' indicates success. If the server returns a | ||
`failure' (STATUS value other than X'00') status, it MUST close the | ||
connection. | ||
*/ | ||
resp := []byte{0x05, 0x00} | ||
conn.Write(resp) | ||
|
||
return nil | ||
} | ||
|
||
|
||
/** | ||
+----+-----+-------+------+----------+----------+ | ||
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | | ||
+----+-----+-------+------+----------+----------+ | ||
| 1 | 1 | X'00' | 1 | Variable | 2 | | ||
+----+-----+-------+------+----------+----------+ | ||
cmd代表客户端请求的类型,值长度也是1个字节,有三种类型: | ||
1>.数字“1”:表示客户端需要你帮忙代理连接,即CONNECT ; | ||
2>.数字“2”:表示让你代理服务器,帮他建立端口,即BIND ; | ||
3>.数字“3”:表示UDP连接请求用来建立一个在UDP延迟过程中操作UDP数据报的连接,即UDP ASSOCIATE; | ||
ATYP代表请求的远程服务器地址类型,它是一个可变参数,但是它值的长度1个字节, | ||
有三种类型: | ||
1>.数字“1”:表示是一个IPV4地址(IP V4 address); | ||
2>.数字“3”:表示是一个域名(DOMAINNAME); | ||
3>.数字“4”:表示是一个IPV6地址(IP V6 address); | ||
*/ | ||
type Socks5Resolution struct { | ||
VER uint8 | ||
CMD uint8 | ||
RSV uint8 | ||
ATYP uint8 | ||
DSTADDR []byte | ||
DSTPORT uint16 | ||
DSTDOMAIN string | ||
RAWADDR *net.TCPAddr | ||
} | ||
|
||
func (s *Socks5Resolution) lstRequest(conn net.Conn) error { | ||
b := make([]byte, 128) | ||
n, err := conn.Read(b) | ||
if err != nil || n < 7 { | ||
log.Println(err) | ||
return errors.New("请求协议错误") | ||
} | ||
s.VER = b[0] | ||
if s.VER != 5 { | ||
return errors.New("该协议不是socks5协议") | ||
} | ||
|
||
s.CMD = b[1] | ||
if s.CMD != 1 { | ||
return errors.New("客户端请求类型不为代理连接, 其他功能暂时不支持.") | ||
} | ||
s.RSV = b[2] //RSV保留字端,值长度为1个字节 | ||
|
||
s.ATYP = b[3] | ||
|
||
switch s.ATYP { | ||
case 1: | ||
// IP V4 address: X'01' | ||
s.DSTADDR = b[4 : 4+net.IPv4len] | ||
case 3: | ||
// DOMAINNAME: X'03' | ||
s.DSTDOMAIN = string(b[5:n-2]) | ||
ipAddr, err := net.ResolveIPAddr("ip", s.DSTDOMAIN) | ||
if err != nil { | ||
return err | ||
} | ||
s.DSTADDR = ipAddr.IP | ||
case 4: | ||
// IP V6 address: X'04' | ||
s.DSTADDR = b[4 : 4+net.IPv6len] | ||
default: | ||
return errors.New("IP地址错误") | ||
} | ||
|
||
s.DSTPORT = binary.BigEndian.Uint16(b[n-2:n]) | ||
// DSTADDR全部换成IP地址,可以防止DNS污染和封杀 | ||
s.RAWADDR = &net.TCPAddr{ | ||
IP: s.DSTADDR, | ||
Port: int(s.DSTPORT), | ||
} | ||
|
||
/** | ||
回应客户端,响应客户端连接成功 | ||
+----+-----+-------+------+----------+----------+ | ||
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | | ||
+----+-----+-------+------+----------+----------+ | ||
| 1 | 1 | X'00' | 1 | Variable | 2 | | ||
+----+-----+-------+------+----------+----------+ | ||
*/ | ||
resp := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} | ||
conn.Write(resp) | ||
|
||
return nil | ||
|
||
|
||
} | ||
|
||
|
||
func handleClientRequest(client net.Conn){ | ||
if client == nil { | ||
return | ||
} | ||
defer client.Close() | ||
|
||
// r := bufio.NewReader(client) | ||
// 认证协商 | ||
var proto ProtocolVersion | ||
err := proto.handshake(client) | ||
if err != nil { | ||
log.Print(err) | ||
return | ||
} | ||
|
||
//获取客户端代理的请求 | ||
var request Socks5Resolution | ||
err = request.lstRequest(client) | ||
if err != nil { | ||
log.Print(err) | ||
return | ||
} | ||
|
||
log.Println(client.RemoteAddr(), request.DSTDOMAIN, request.DSTADDR, request.DSTPORT) | ||
|
||
// 连接真正的远程服务 | ||
dstServer, err := net.DialTCP("tcp", nil, request.RAWADDR) | ||
if err != nil { | ||
log.Print(err) | ||
return | ||
} | ||
defer dstServer.Close() | ||
|
||
wg := new(sync.WaitGroup) | ||
wg.Add(2) | ||
|
||
go func() { | ||
defer wg.Done() | ||
defer dstServer.Close() | ||
io.Copy(dstServer, client) | ||
}() | ||
|
||
go func() { | ||
defer wg.Done() | ||
defer client.Close() | ||
io.Copy(client, dstServer) | ||
}() | ||
wg.Wait() | ||
|
||
} | ||
|
||
func main() { | ||
flag.Parse() | ||
listener,err := net.Listen("tcp",":18888") | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
for { | ||
conn,err := listener.Accept() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
go handleClientRequest(conn) | ||
} | ||
} |