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

VER is the VERSION of SOCKS, so this should be 0x05; NMETHODS is the length of the METHODS section; METHODS is a list of client-supported authentication methods, each of which takes 1 byte. The current definition is: 0x00 Does not require certification 0x01 GSSAPI 0x02 User name and password authentication x03-0x7F Allocated (retained) by IANA 0x80-0ES53en reserved for private methods 0xFF unacceptable method

Server reply available method for client:

VER METHOD
1 1

VER is the VERSION of SOCKS, this should be 0x05; METHOD is the method selected by the server. If a return of 0xFF indicates that no authentication method has been selected, the client needs to close the connection.

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 methods

Here 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

VER is the VERSION of SOCKS, here it should be 0x05; CMD is the command code for SOCK 0x01 represents the CONNECT request 0x02 represents the BIND request 0x03 means UDP forwarding RSV 0x00 reserved ATYP DST ADDR type DST.ADDR destination address 0x01 IPv4 address, ES131en. ADDR part 4-byte length 0x03 domain name, DST.ADDR the first byte is the domain name length, DST.ADDR the rest of the content is the domain name, no \0 ending. 0x04 IPv6 address, 16 bytes long. The destination port represented by the PORT network byte order

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()


Related articles: