This is a creation in Article, where the information may have evolved or changed.
The first two days, using Golang implementation of a simple HTTP proxy, the implementation see http://www.flysnow.org/2016/12/24/golang-http-proxy.html, this time using Golang implementation of a SOCKET5 simple proxy. Socket5 and HTTP are not very different, they can completely give the TCP protocol, only the information structure of the request is different, so this time we can not like the last HTTP proxy, parsing requests and responses, to follow the protocol of the socket to resolve.
Socket Protocol version
The socket protocol is divided into two versions of SOCKET4 and SOCKET5, the most obvious difference being that SOCKET5 supports both TCP and UDP two protocols, while SOCKET4 only supports TCP. Most of the current use is SOCKET5, we simply introduce the SOCKET5 protocol here.
SOCKET5 authentication of the license agreement
The implementation details and specifications of the SOCKET5 protocol must be understood in order to implement the connection session between Socket5. It's like we all talk in Mandarin, we both understand each other, and we can respond to each other's understanding. The same is true of SOCKET5 's client and server communication, whose language is the SOCKET5 protocol. Because SOCKET5 supports TCP and UDP two, this one is only described in TCP, UDP is similar.
First, the client sends a validation message to the server, which is the precondition for establishing a connection. Like the client: Hi, buddy, take a fire. Service side to know it said: "Good, give; if you do not know, say:" You which onion AH!! The password for the client request is simple:
VER |
Nmethods |
METHODS |
1 |
1 |
1 to 255 |
- The first field, Ver, represents the version of the socket, and Soket5 defaults to 0x05, with a fixed length of 1 bytes
- The second field, Nmethods, represents the length of the third field methods, and its length is also 1 bytes
- The third methods means that the client supports a number of authentication methods, and his attempt is 1-255 bytes.
The currently supported authentication methods are:
- X ' xx ' No authentication REQUIRED (no verification required)
- X ' GSSAPI '
- X ' Username/password ' (username and password)
- X ' ASSIGNED ' to X ' 7F ' IANA
- X ' z ' to X ' FE ' RESERVED for PRIVATE METHODS
- X ' FF ' no acceptable METHODS (none supported, unable to connect)
All of the above are hexadecimal constants, for example, X ' 00 ' indicates hexadecimal 0x00.
After the server receives the client's authentication information, it responds to the client, which requires the client to provide information about which authentication method is available. The response from the service side is also very concise.
- The first field, Ver, represents the version of the socket, and Soket5 defaults to 0x05, which has a value of 1 bytes.
- The second field method represents the need for a client to provide authentication information in this way, with a value of 1 bytes and a choice of six authentication methods above.
For example, if the server does not need authentication, you can respond to the client as follows:
This is on behalf of the server said: "Dude, I don't have a request, you come, we use the Go Implementation code is as follows:
123456789101112 |
var b [1024x768]byten, Err: = client. Read (b[:])ifnil {log. PRINTLN (Err)return}if b[00x05//Processing SOCKET5 protocol only // Client response: The socket server does not require the client to authenticate . Write ([]byte{0x050x00}) N, err = client. Read (b[:])} |
Here is an example of the simplest, non-validating way of doing this, which allows you to start a connection once you have answered the above question. For other authentication methods, you also need to ask one more time, mainly the client provides authentication information, the service side response verification is correct, these details can refer to Http://www.ietf.org/rfc/rfc1928.txt and HTTP/ Www.ietf.org/rfc/rfc1929.txt the SOCKET5 protocol definition.
The SOCKET5 protocol establishes a connection.
After the SOCKET5 client and the server have passed the authorization verification, the connection is established. The connection is initiated by the client, telling Sokcet which remote server the client needs to access, which contains the address and port of the remote server, which can be either IP4,IP6 or the domain name.
VER |
CMD |
RSV |
Atyp |
DST. ADDR |
DST. PORT |
1 |
1 |
X ' 00 ' |
1 |
Variable |
2 |
- Ver represents the version of the socket protocol, SOKET5 defaults to 0x05, with a value length of 1 bytes
- CMD represents the type of client request, the value length is also 1 bytes, there are three types
- CONNECT X ' 01 '
- BIND X ' 02 '
- UDP ASSOCIATE X ' 03 '
- RSV reserved word with a value length of 1 bytes
- Atyp represents the requested remote server address type with a value of 1 bytes and three types
- IP V4 address:x ' 01 '
- Domainname:x ' 03 '
- IP V6 address:x ' 04 '
- Dst. Addr represents the address of the remote server, parsing according to Atyp, the value of variable length.
- Dst. Port on behalf of the remote server, to access which port meaning, value length 2 bytes
To parse the remote server information we need from the protocol, the go Code is implemented as follows:
12345678910 |
varHost,portstringSwitchb[3] { Case 0x01://IP V4Host = net. IPV4 (b[4],b[5],b[6],b[7]). String () Case 0x03://Domain nameHost =string(b[5: N-2])//b[4] Indicates the length of the domain name Case 0x04://IP V6Host = net. ip{b[4], b[5], b[6], b[7], b[8], b[9], b[Ten], b[ One], b[ A], b[ -], b[ -], b[ the], b[ -], b[ -], b[ -], b[ +]}. String ()}port = StrConv. Itoa (int(B[n-2]) <<8|int(B[n-1])) |
Now the client to the request of the remote server to tell the SOCKET5 proxy server, then the SOCKET5 Proxy server can be connected with the remote server, regardless of whether the connection is successful, etc., to the client response, the response format is:
VER |
REP |
RSV |
atyp |
BND. ADDR |
BND. PORT |
1 |
1 |
X ' xx ' |
1 |
variable |
2 |
- Ver represents the version of the socket protocol, SOKET5 defaults to 0x05, with a value length of 1 bytes
- The rep represents the response status code, and the value length is 1 bytes, and there are several types
- X ' xx ' succeeded
- X ' SOCKS server failure
- X ' connection ' allowed by ruleset
- X ' unreachable ' Network
- X ' unreachable ' Host
- X ' Connection refused
- X ' ' TTL expired
- X ' "Command not supported
- X ' Address type not supported
- X ' Unassigned ' to X ' FF '
- RSV reserved word with a value length of 1 bytes
- Atyp represents the requested remote server address type with a value of 1 bytes and three types
- IP V4 address:x ' 01 '
- Domainname:x ' 03 '
- IP V6 address:x ' 04 '
- BND. Addr represents the binding address, and the value is variable in length.
- BND. Port represents the bound ports, with a value of 2 bytes in length
The go implementation connects and answers as follows:
1234567 |
Server, Err: = Net. Dial ("TCP", net. Joinhostport (host, port))ifnil {log. PRINTLN (Err)return}defer server. Close () client. Write ([]byte{0x050x000x000x010x000x00 0x00 0x00 0x00 0x00 //Response Client connection succeeded |
The address and port of the binding, the two responses differ according to the requested CMD, and the reference http://www.ietf.org/rfc/rfc1928.txt is described in detail.
Data forwarding
After the connection is established, the data transfer is forwarded, and the TCP protocol can be forwarded directly. UDP words need special treatment, the specific reference protocol definition, in fact, is a special format of the response, and the above one to answer the similar, more details of the protocol http://www.ietf.org/rfc/rfc1928.txt.
TCP Direct forwarding is simple:
123 |
//To forward go io. Copy (server, client) IO. Copy (client, server) |
Complete code Implementation
The following is the completed code implementation.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 6667 |
PackageMainImport("IO""Log""NET""StrConv") func main() {log. SetFlags (log. Lstdflags | Log. Lshortfile) L, err: = Net. Listen ("TCP",": 8081")ifErr! =Nil{log. Panic (ERR)} for{client, err: = L.accept ()ifErr! =Nil{log. Panic (ERR)}GoHandleclientrequest (client)}} func handleclientrequest(client net. Conn) {ifClient = =Nil{return}deferClient. Close ()varb [1024x768]byteN, Err: = client. Read (b[:])ifErr! =Nil{log. PRINTLN (ERR)return}ifb[0] ==0x05{//Deal only with SOCKET5 protocol//Client response: The socket server does not require authenticationClient. Write ([]byte{0x05,0x00}) N, err = client. Read (b[:])varHost, PortstringSwitchb[3] { Case 0x01://IP V4Host = net. IPV4 (b[4], b[5], b[6], b[7]). String () Case 0x03://Domain nameHost =string(b[5: N-2])//b[4] Indicates the length of the domain name Case 0x04://IP V6Host = net. ip{b[4], b[5], b[6], b[7], b[8], b[9], b[Ten], b[ One], b[ A], b[ -], b[ -], b[ the], b[ -], b[ -], b[ -], b[ +]}. String ()}port = StrConv. Itoa (int(B[n-2]) <<8|int(B[n-1]) server, err: = Net. Dial ("TCP", net. Joinhostport (host, Port))ifErr! =Nil{log. PRINTLN (ERR)return}deferServer. Close () client. Write ([]byte{0x05,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00})//Response Client connection succeeded//To forwardGoIo. Copy (server, client) IO. Copy (client, server)}} |
Here is a simple version of the SOCKET5 agent, there are many do not realize, to achieve the completion of the SOCKET5, you can try according to his definition of the protocol.