SoFunction
Updated on 2025-03-04

Implementation of manually writing a forwarding proxy service based on Go

Since companies often need to work in other places and need to use the intranet environment during debugging, they manually wrote a proxy forwarding server for their brothers to use:socks5proxy

In terms of selection, Go is selected in language, which is simple and clear, and socks5 is selected for the forwarding protocol.

Introduction to SOCKS5 protocol

SOCKS is a network transmission protocol, mainly used for intermediate transmission of communication between clients and external network servers. SOCKS is the abbreviation of "SOCKetS". SOCKS5 is an upgraded version of SOCKS4, which mainly includes authentication, IPv6, and UDP support.

The SOCKS5 protocol can be divided into three parts:

(1) Agreement version and authentication method
(2) Perform the corresponding authentication according to the authentication method
(3) Request information

(1) Agreement version and authentication method

After creating a TCP connection with the SOCKS5 server, the client needs to send a request to the protocol version and authentication method first.

VER NMETHODS METHODS
1 1 1-255
  • VER is the SOCKS version, it should be 0x05 here;
  • NMETHODS is the length of the METHODS part;
  • METHODS is a list of authentication methods supported by the client, each method accounts for 1 byte. The current definition is:
    • 0x00 No authentication required
    • 0x01 GSSAPI
    • 0x02 Username and password authentication
    • 0x03 - 0x7F allocated by IANA (reserved)
    • 0x80 - 0xFE is reserved for private methods
    • 0xFF Unacceptable method

How to reply to the client for server:

VER METHOD
1 1
  • VER is the SOCKS version, it should be 0x05 here;
  • METHOD is the method selected by the server. If 0xFF is returned, it means that no authentication method is selected, the client needs to close the connection.

Code implementation:

type ProtocolVersion struct {
  VER uint8
  NMETHODS uint8
  METHODS []uint8
}


func (s *ProtocolVersion) handshake(conn ) error {
  b := make([]byte, 255)
  n, err := (b)
  if err != nil {
    (err)
    return err
  }
   = b[0] //ReadByte reads and returns a single byte, the first parameter is the version number of socks   = b[1] //nmethods is the length of the methods.  nmethods length is 1 byte  if n != int(2+) {
    return ("Protocol error, sNMETHODS is wrong")
  }
   = b[2:2+] //Read the specified length information and read the bytes of the exact length of len(buf).  If the number of bytes is not the specified length, the error message and the correct number of bytes will be returned
  if  != 5 {
    return ("This protocol is not the socks5 protocol")
  }

  //The server responds to the client message:  //The first parameter indicates that the version number is 5, that is, the socks5 protocol.  // The second parameter indicates the authentication method selected by the server, 0 means no password access is required, and 2 means that the user name and password are required for verification.  resp :=[]byte{5, 0} 
  (resp)
  return nil
} 

(2) Perform the corresponding authentication according to the authentication method

The SOCKS5 protocol provides 5 authentication methods:

  1. 0x00 No authentication required
  2. 0x01 GSSAPI
  3. 0x02 Username and password authentication
  4. 0x03 - 0x7F allocated by IANA (reserved)
  5. 0x80 - 0xFE is reserved for private methods

Here we mainly introduce username and password authentication. After negotiating the authentication of username and password on the client and server, the client issues the username and password:

Authentication protocol version Username length username Password length password
1 1 dynamic 1 dynamic

After the server is authenticated, the following response is issued:

Authentication protocol version Identification status
1 1

The authentication status 0x00 indicates success and 0x01 indicates failure.

Code implementation:

type Socks5Auth struct {
  VER uint8
  ULEN uint8
  UNAME string
  PLEN uint8
  PASSWD string
}

func (s *Socks5Auth) authenticate(conn ) error {
  b := make([]byte, 128)
  n, err := (b)
  if err != nil{
    (err)
    return err
  }

   = b[0]
  if  != 5 {
    return ("This protocol is not the socks5 protocol")
  }

   = b[1]
   = string(b[2:2+])
   = b[2++1]
   = string(b[n-int():n])
  (, )
  if username !=  || passwd !=  {
    return ("Incorrect account password")
  }

  /**
   Respond to the client, respond to the client connection successfully
   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}
  (resp) 

  return nil
}

But in fact, now everyone is accustomed to using encrypted streams to encrypt, and rarely uses the form of username and password to encrypt. The following chapter will introduce an obfuscated 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 manner defined by the method, and its original format is as follows:

VER CMD RSV ATYP
1 1 0x00 1 dynamic 2
  • VER is the SOCKS version, it should be 0x05 here;
  • CMD is the command code of SOCK
    • 0x01 means CONNECT request
    • 0x02 means BIND request
    • 0x03 means UDP forwarding
  • RSV 0x00, reserved
  • ATYP Type
  • Destination address
    • 0x01 IPv4 address, part 4 byte length
    • 0x03 Domain name, the first byte is the domain name length, the remaining content is the domain name, and there is no ending of \0.
    • 0x04 IPv6 address, 16 byte length.
  • Destination port represented by network endianness

Code implementation:

type Socks5Resolution struct {
  VER uint8
  CMD uint8
  RSV uint8
  ATYP uint8
  DSTADDR []byte
  DSTPORT uint16
  DSTDOMAIN string
  RAWADDR *
}

func (s *Socks5Resolution) lstRequest(conn ) error {
  b := make([]byte, 128)
  n, err := (b)
  if err != nil || n < 7 {
    (err)
    return ("Request Agreement Error")
  }
   = b[0]
  if  != 5 {
    return ("This protocol is not the socks5 protocol")
  }

   = b[1]
  if  != 1 { 
    return ("The client request type is not a proxy connection, and other functions are not supported for the time being.")
  }
   = b[2] //RSV reserves word end, value length is 1 byte
   = b[3]

  switch  {
  case 1:
    // IP V4 address: X'01'
     = b[4 : 4+net.IPv4len]
  case 3:
    // DOMAINNAME: X'03'
     = string(b[5:n-2])
    ipAddr, err := ("ip", )
 if err != nil {
  return err
 }
     = 
  case 4:
    // IP V6 address: X'04'
     = b[4 : 4+net.IPv6len]
 default:
 return ("IP address error")
  }

   = .Uint16(b[n-2:n])
  // All DSTADDRs are replaced with IP addresses, which can prevent DNS pollution and ban   = &{
 IP:  ,
 Port: int(),
  }
  
  /**
   Respond to the client, respond to the client connection successfully
     +----+-----+-----+-------+--------+
     |VER | REP | RSV | ATYP | | |
     +----+-----+-----+-------+--------+
     | 1 | 1 | X'00' | 1 | Variable | 2 |
     +----+-----+-----+-------+--------+
   */
 resp := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
  (resp) 

  return nil
}

(4) Finally forward the information

Code implementation:

  wg := new()
  (2)

  go func() {
 defer ()
 defer ()
    (dstServer, client)
  }()

  go func() {
 defer ()
    defer ()
    (client, dstServer)
  }()

  ()

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.