Copyright statement: this article original articles for bloggers, follow the < a href = “creativecommons.org/licenses/by…” Rel =”noopener”> CC 4.0 BY SA Link to this article: blog.csdn.net/weixin_4096…

In the previous article, you were introduced to a new coroutine language, Melang. Today, I’m going to bring you the first corporate campaign of this language. Although it’s a small project, it’s of great significance for a new language. And, the use of this language, so that the entire program structure is very clear and modular. Because the author company wants to build an agent service for other network segment machines to use the Internet, so there is this project. Note: This article is intended to introduce language features and usage only. Readers are not encouraged to violate national policies and regulations. Do not use this article for any purpose other than technical discussion.

Program structure

In previous articles, we explained that each script task in Melang is a coroutine, that the schedules between coroutines are preemptively scheduled, that the environments between coroutines are isolated, and that a coroutine can pull up other coroutines. Therefore, the Socks5 agent in this paper will adopt the mode of coroutine concurrency, and the coroutine structure is as follows:Each working coroutine handles a TCP connection independently. Therefore, when accessing the Web site, multiple TCP will be initiated by the browser to the proxy, and the main coroutine will complete the establishment of TCP, and then pull up an independent working coroutine to handle the protocol and data transceiver on the TCP. When the TCP connection is disconnected, the work coroutine exits and is released. Note: The proxies presented in this article currently only support TCP proxies.

Socks5

Here is a reminder of the Socks5 protocol. This protocol is relatively simple, and the general process is as follows:

  1. After TCP is established, the proxy server receives handshake packets from the client
  2. The server authenticates the authentication mode in the handshake packet and replies the response packet
  3. The destination address (domain name or IP address) and port number of the proxy data sent by the client are verified
  4. The proxy server attempts to establish TCP to the IP address and then returns a response packet to the client
  5. After the current four steps are completed, the data transmission phase begins

implementation

More nonsense than code on the table. The code is divided into two files, one is the main coroutine script proxy. MLN, the other is the working coroutine script worker.mln, we give:

proxy.mln
recvTimeout = 50; Fd = @ mln_tcp_listen (' 0.0.0.0 ', '1080'); @mln_print('Ready'); while (1) { sockfd = @mln_tcp_accept(fd); if (sockfd) { conf = [ 'fd': sockfd, 'recvTimeout': recvTimeout, ]; @mln_eval('worker.mln', @mln_json_encode(conf)); } fi }Copy the code

As you can see, the main coroutine’s task is very simple:

  1. Establish the listening socket
  2. An infinite loop establishes TCP and pulls up a worker.mln task for each TCP for processing.
worker.mln
conf = @mln_json_decode(EVAL_DATA); localFd = @mln_int(conf['fd']); recvTimeout = @mln_int(conf['recvTimeout']); state = 1; localSend = ''; remoteSend = ''; remoteFd = nil; @closeLocalSock() { @mln_tcp_close(_localFd); _localFd = nil; _localSend = ''; } @closeRemoteSock() { @mln_tcp_close(_remoteFd); _remoteFd = nil; _remoteSend = ''; } @localRecvHandler() { if (_state == 1) { if (@mln_strlen(_remoteSend) < 3 || @mln_bin2int(_remoteSend[0]) ! = 5) { @closeLocalSock(); return; } fi n = @mln_bin2int(_remoteSend[1]); if (@mln_strlen(_remoteSend) < n+2) { @closeLocalSock(); return; } fi for (i = 0; i < n; ++i) { if (@mln_bin2int(_remoteSend[2+i]) == 0) { break; } fi } if (i >= n) { @closeLocalSock(); return; } fi ret = @mln_tcp_send(_localFd, @mln_int2bin(5)[-1]+@mln_int2bin(0)[-1]); if (! ret) { @closeLocalSock(); return; } fi _remoteSend = @mln_split(_remoteSend, n+2); _state = 2; } else if (_state == 2) { arr = [5, 7, 0, 1, 0, 0, 0, 0, 0, 0]; err = ''; for (i = 0; i < @mln_size(arr); ++i) { err += @mln_int2bin(arr[i])[-1]; } len = @mln_strlen(_remoteSend); if (len < 8 || @mln_bin2int(_remoteSend[0]) ! = 5 || @mln_bin2int(_remoteSend[1]) ! = 1 || @mln_bin2int(_remoteSend[2]) ! = 0) { goto fail; } fi type = @mln_bin2int(_remoteSend[3]); addr = ''; if (type == 1) { if (len < 10) { goto fail; } fi for (i = 0; i < 4; ++i) { addr += @mln_str(@mln_bin2int(_remoteSend[4+i])); if (i < 3) { addr += '.'; } fi } n = 8; } else if (type == 3) { n = 5+@mln_bin2int(_remoteSend[4]); if (len < n+2) { goto fail; } fi addr = @mln_split(_remoteSend, 5, @mln_bin2int(_remoteSend[4])); } else if (type == 4) { if (len < 22) { goto fail; } fi for (i = 0; i < 8; ++i) { addr += @mln_bin2hex(_remoteSend[4+i*2]); addr += @mln_bin2hex(_remoteSend[4+i*2+1]); if (i < 7) { addr += ':'; } fi } n = 20; } else { goto fail; } if (len < n + 2) { goto fail; } fi port = (@mln_bin2int(_remoteSend[n])<<8)|@mln_bin2int(_remoteSend[n+1]); @mln_print('connect['+addr+']'); ret = @mln_tcp_connect(addr, @mln_str(port), 30000); if (! ret) { goto fail; } fi _remoteFd = ret; ret = ''+@mln_int2bin(5)[-1]+@mln_int2bin(0)[-1]+@mln_int2bin(0)[-1]+_remoteSend[3]; ret += @mln_split(_remoteSend, 4, n - 2); ret = @mln_tcp_send(_localFd, ret); if (! ret) { @closeRemoteSock(); @closeLocalSock(); return; } fi _remoteSend = @mln_split(_remoteSend, n+2); _state = 3; } else { ret = @mln_tcp_send(_remoteFd, _remoteSend); if (! ret) { @closeRemoteSock(); } else { _remoteSend = ''; } } return; fail: @mln_tcp_send(_localFd, err); @closeLocalSock(); return; } //@mln_print(''+localFd); while (1) { if (localFd) { if (state == 3 && ! remoteFd && ! localSend) { @closeLocalSock(); } else { res = @mln_tcp_recv(localFd, recvTimeout); if (res) { if (@mln_is_bool(res)) { @closeLocalSock(); } else { remoteSend += res; } } else if (@mln_is_bool(res)) { @closeLocalSock(); } fi } } fi if (remoteFd) { if (state == 3 && ! localFd && ! remoteSend) { @closeRemoteSock(); } else { res = @mln_tcp_recv(remoteFd, recvTimeout); if (res) { if (@mln_is_bool(res)) { @closeRemoteSock(); } else { localSend += res; } } else if (@mln_is_bool(res)) { @closeRemoteSock(); } fi } } fi if (@mln_is_nil(localFd) && @mln_is_nil(remoteFd)) { break; } fi if (remoteSend) { @localRecvHandler(); } fi if (localSend) { ret = @mln_tcp_send(_localFd, _localSend); _localSend = ''; if (! ret) { @closeLocalSock(); } fi } fi } @mln_print('quit');Copy the code

The worker coroutine is less than 200 lines long.

  • Conf is the configuration passed from the main coroutine;
  • The locaFd is the socket you just created;
  • RecvTimeout is the timeout (milliseconds) for TCP to receive data. Since Melang’s code is synchronous code (executing asynchronously at the bottom), there needs to be a mechanism to prevent blocking on a function for a long time. But because it’s synchronous mode code, the execution process is very clear;
  • State is used to mark the status of SOcks5:1- processing the handshake packet, 2- processing the destination address packet and establishing the connection, 3- transparent data transmission.
  • RemoteFd is a TCP socket set to the destination address when state is 2;
  • LocalSend and remoteSend are two send buffers. LocalSend is the data sent to localFd and remoteSend is the data sent to remoteFd (not if state is not 3).
  • The closeLocalSock function is used to close the TCP connection with the client and clear the send buffer.
  • The closeRemoteSock function is used to close the TCP connection to the destination address and empty the send buffer.
  • The localRecvHandler function is used to handle the socket data to communicate with the client. It is relatively verbose because there are two phases of socks5-related processing.
  • While loop, this loop is used to receive data on two TCP, and then processing, and then sent by the specified TCP;

As an extra note, you can see some variables in the function that precede the global variable name with an underscore, which is still a global variable in this case. Without an underscore, the variable name will only be searched within the scope of the current function, so the value of the variable above the call stack cannot be obtained, and the variable with an underscore will be queried from inside out of the call stack.

At the end

Interested readers can go to Melang’s Github repo (github.com/Water-Melon…) Download Melang from README and install it. Thank you for reading, and welcome to leave comments or private messages.