Manually write a code implementation of the forwarding agent service based on go
- 2020-06-19 10:32:51
- OfStack
Since the company often needs to work in another place, the Intranet environment is needed for debugging. Therefore, a proxy forwarding server is manually written and used by the friends: socks5proxy.
In terms of type selection, Go was chosen for language, simple and clear, and socks5 was chosen for forwarding protocol.
SOCKS5 protocol introduction
SOCKS is a kind of network transmission protocol, mainly used for the intermediate transmission between the client and the external network server. SOCKS is the abbreviation of "SOCKetS". SOCKS5 is an upgraded version of SOCKS4, with more authentication, IPv6 and UDP support.
SOCKS5 protocol can be divided into three parts:
(1) Protocol version and authentication method
(2) Execute the corresponding authentication according to the authentication method
(3) Request information
(1) Protocol version and authentication method
After creating the TCP connection with SOCKS5 server, the client needs to send the request to the protocol version and authentication method first.
VER | NMETHODS | METHODS |
---|---|---|
1 | 1 | 1-255 |
Server reply available method for client:
VER | METHOD |
---|---|
1 | 1 |
Code implementation:
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 In the first 1 The parameters for socks The version number of the
s.NMETHODS = b[1] //nmethods Is a record methods Of the length of. nmethods Is the length of the 1 bytes
if n != int(2+s.NMETHODS) {
return errors.New(" Protocol error , sNMETHODS wrong ")
}
s.METHODS = b[2:2+s.NMETHODS] // Reads the specified length, reads exactly len(buf) A byte of length. If the number of bytes is not the specified length, an error message is returned along with the correct number of bytes
if s.VER != 5 {
return errors.New(" The agreement is not socks5 agreement ")
}
// The server responds to the client message :
// The first 1 The version number is 5 , i.e., socks5 Agreement,
// The first 2 The parameters represent the authentication method selected by the server, 0 That is, access without a password , 2 Indicates that a user name and password are required for authentication.
resp :=[]byte{5, 0}
conn.Write(resp)
return nil
}
(2) Execute the corresponding authentication according to the authentication method
SOCKS5 provides five authentication methods:
0x00 Does not require certification 0x01 GSSAPI 0x02 User name and password authentication Allocated (reserved) by IANA 0x80-0xFE reserved for private methodsHere mainly introduces the user name, password authentication. After the client and the server negotiate and use the username and password for authentication, the client issues the username and password:
鉴定协议版本 | 用户名长度 | 用户名 | 密码长度 | 密码 |
---|---|---|---|---|
1 | 1 | 动态 | 1 | 动态 |
The server issues the following response after identification:
鉴定协议版本 | 鉴定状态 |
---|---|
1 | 1 |
Where the authentication status 0x00 means success and 0x01 means failure.
Code implementation:
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(" The agreement is not socks5 agreement ")
}
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)
if username != s.UNAME || passwd != s.PASSWD {
return errors.New(" Account password error ")
}
/**
Response client , Response client connection successful
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
}
However, in fact, now everyone is used to using encrypted stream for encryption, rarely in the form of username and password encryption, the following chapter will introduce a confusing encryption method for SOCKS.
(3) Request information
After the authentication is completed, the client can send the request information. If the authentication method has special encapsulation requirements, the request must be encapsulated and decrypted in the way defined by the method, in the following original format:
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
1 | 1 | 0x00 | 1 | 动态 | 2 |
Code implementation:
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(" Request protocol error ")
}
s.VER = b[0]
if s.VER != 5 {
return errors.New(" The agreement is not socks5 agreement ")
}
s.CMD = b[1]
if s.CMD != 1 {
return errors.New(" The client request type is not a proxy connection , Other features are temporarily not supported .")
}
s.RSV = b[2] //RSV Reserved word end, value length is 1 bytes
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 Address wrong ")
}
s.DSTPORT = binary.BigEndian.Uint16(b[n-2:n])
// DSTADDR Replacing the IP Address can be prevented DNS Pollution and closure
s.RAWADDR = &net.TCPAddr{
IP: s.DSTADDR,
Port: int(s.DSTPORT),
}
/**
Response client , Response client connection successful
+----+-----+-------+------+----------+----------+
|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
}
(4) Finally, the information can be forwarded
Code implementation:
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()