【Mongoose笔记】SOCKS5 服务器

【Mongoose笔记】socks5 服务器

简介

Mongoose 笔记系列用于记录学习 Mongoose 的一些内容。

Mongoose 是一个 C/C++ 的网络库。它为 TCP、UDP、HTTP、WebSocket、MQTT 实现了事件驱动的、非阻塞的 API。

项目地址:

https://github.com/cesanta/mongoose

学习

下面学习一下 Mongoose 项目代码中的 socks5-server 示例程序 ,这个示例程序是一个简易的 SOCKS5 代理服务器,只实现了SOCKS5 TCP 服务的一个子集,这个示例程序允许客户端通过这个SOCKS5 代理服务器与所需的 TCP 服务器交换数据,无需用户认证。

示例程序代码如下:

// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Example socks5 server. To test,
//   1. Run `make` to start this server on port 1080
//   2. Run `curl`

#include "mongoose.h"

static const char *s_lsn = "tcp://localhost:1080";  // Listening address

enum {
  VERSION = 5,                  // Socks protocol version
  STATE_HANDSHAKE = 0,          // Connection state: in handshake
  STATE_REQUEST = 1,            // Connection state: connecting
  STATE_ESTABLISHED = 2,        // Connection state: established
  HANDSHAKE_NOAUTH = 0,         // Handshake method - no authentication
  HANDSHAKE_GSSAPI = 1,         // Handshake method - GSSAPI auth
  HANDSHAKE_USERPASS = 2,       // Handshake method - user/password auth
  HANDSHAKE_FAILURE = 0xff,     // Handshake method - failure
  CMD_CONNECT = 1,              // Command: CONNECT
  CMD_BIND = 2,                 // Command: BIND
  CMD_UDP_ASSOCIATE = 3,        // Command: UDP ASSOCIATE
  ADDR_TYPE_IPV4 = 1,           // Address type: IPv4
  ADDR_TYPE_DOMAIN = 3,         // Address type: Domain name
  ADDR_TYPE_IPV6 = 4,           // Address type: IPv6
  RESP_SUCCESS = 0,             // Response: success
  RESP_FAILURE = 1,             // Response: failure
  RESP_NOT_ALLOWED = 2,         // Response status
  RESP_NET_UNREACHABLE = 3,     // Response status
  RESP_HOST_UNREACHABLE = 4,    // Response status
  RESP_CONN_REFUSED = 5,        // Response status
  RESP_TTL_EXPIRED = 6,         // Response status
  RESP_CMD_NOT_SUPPORTED = 7,   // Response status
  RESP_ADDR_NOT_SUPPORTED = 8,  // Response status
};

//  https://www.ietf.org/rfc/rfc1928.txt paragraph 3, handle client handshake
//  +----+----------+----------+
//  |VER | NMETHODS | METHODS  |
//  +----+----------+----------+
//  | 1  |    1     | 1 to 255 |
//  +----+----------+----------+
static void handshake(struct mg_connection *c) {
  struct mg_iobuf *r = &c->recv;
  if (r->buf[0] != VERSION) {
    c->is_closing = 1;
  } else if (r->len > 2 && (size_t) r->buf[1] + 2 <= r->len) {
    /* https://www.ietf.org/rfc/rfc1928.txt paragraph 3 */
    uint8_t reply[2] = {VERSION, HANDSHAKE_FAILURE};
    int i;
    for (i = 2; i < r->buf[1] + 2; i++) {
      // TODO(lsm): support other auth methods
      if (r->buf[i] == HANDSHAKE_NOAUTH) reply[1] = r->buf[i];
    }
    mg_iobuf_del(r, 0, 2 + r->buf[1]);
    mg_send(c, reply, sizeof(reply));
    c->data[0] = STATE_REQUEST;
  }
}

static void disband(struct mg_connection *c) {
  struct mg_connection *c2 = (struct mg_connection *) c->fn_data;
  if (c2 != NULL) {
    c2->is_draining = 1;
    c2->fn_data = NULL;
  }
  c->is_draining = 1;
  c->fn_data = NULL;
}

static void exchange(struct mg_connection *c) {
  struct mg_connection *c2 = (struct mg_connection *) c->fn_data;
  if (c2 != NULL) {
    mg_send(c2, c->recv.buf, c->recv.len);
    mg_iobuf_del(&c->recv, 0, c->recv.len);
  } else {
    c->is_draining = 1;
  }
}

static void fn2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_READ) {
    exchange(c);
  } else if (ev == MG_EV_CLOSE) {
    disband(c);
  }
  (void) ev_data;
  (void) fn_data;
}

//  Request, https://www.ietf.org/rfc/rfc1928.txt paragraph 4
//  +----+-----+-------+------+----------+----------+
//  |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
//  +----+-----+-------+------+----------+----------+
//  | 1  |  1  | X'00' |  1   | Variable |    2     |
//  +----+-----+-------+------+----------+----------+
static void request(struct mg_connection *c) {
  struct mg_iobuf *r = &c->recv;
  uint8_t *p = r->buf, addr_len = 4, reply = RESP_SUCCESS;
  int ver, cmd, atyp;
  char addr[1024];

  if (r->len < 8) return;  // return if not fully buffered. min DST.ADDR is 2
  ver = p[0];
  cmd = p[1];
  atyp = p[3];

  // TODO(lsm): support other commands
  if (ver != VERSION || cmd != CMD_CONNECT) {
    reply = RESP_CMD_NOT_SUPPORTED;
  } else if (atyp == ADDR_TYPE_IPV4) {
    addr_len = 4;
    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
    snprintf(addr, sizeof(addr), "tcp://%d.%d.%d.%d:%d", p[4], p[5], p[6], p[7],
             p[8] << 8 | p[9]);
    c->fn_data = mg_connect(c->mgr, addr, fn2, c);
  } else if (atyp == ADDR_TYPE_IPV6) {
    addr_len = 16;
    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
    snprintf(addr, sizeof(addr), "tcp://[%x:%x:%x:%x:%x:%x:%x:%x]:%d",
             p[4] << 8 | p[5], p[6] << 8 | p[7], p[8] << 8 | p[9],
             p[10] << 8 | p[11], p[12] << 8 | p[13], p[14] << 8 | p[15],
             p[16] << 8 | p[17], p[18] << 8 | p[19], p[20] << 8 | p[21]);
    c->fn_data = mg_connect(c->mgr, addr, fn2, c);
  } else if (atyp == ADDR_TYPE_DOMAIN) {
    addr_len = p[4] + 1;
    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
    snprintf(addr, sizeof(addr), "tcp://%.*s:%d", p[4], p + 5,
             p[4 + addr_len] << 8 | p[4 + addr_len + 1]);
    c->fn_data = mg_connect(c->mgr, addr, fn2, c);
  } else {
    reply = RESP_ADDR_NOT_SUPPORTED;
  }

  //  Reply, https://www.ietf.org/rfc/rfc1928.txt paragraph 5
  //
  //  +----+-----+-------+------+----------+----------+
  //  |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
  //  +----+-----+-------+------+----------+----------+
  //  | 1  |  1  | X'00' |  1   | Variable |    2     |
  //  +----+-----+-------+------+----------+----------+
  {
    uint8_t buf[] = {VERSION, reply, 0};
    mg_send(c, buf, sizeof(buf));
  }
  mg_send(c, r->buf + 3, addr_len + 1 + 2);
  mg_iobuf_del(r, 0, 6 + addr_len);  // Remove request from the input stream
  c->data[0] = STATE_ESTABLISHED;   // Mark ourselves as connected
}

static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_READ) {
    // We use the first label byte as a state
    if (c->data[0] == STATE_HANDSHAKE) handshake(c);
    if (c->data[0] == STATE_REQUEST) request(c);
    if (c->data[0] == STATE_ESTABLISHED) exchange(c);
  } else if (ev == MG_EV_CLOSE) {
    disband(c);
  }
  (void) fn_data;
  (void) ev_data;
}

int main(void) {
  struct mg_mgr mgr;                     // Event manager
  mg_log_set(MG_LL_DEBUG);               // Set log level
  mg_mgr_init(&mgr);                     // Initialise event manager
  mg_listen(&mgr, s_lsn, fn, NULL);      // Create client connection
  while (true) mg_mgr_poll(&mgr, 1000);  // Infinite event loop
  mg_mgr_free(&mgr);                     // Free resources
  return 0;
}

下面从main函数开始分析代码。

定义变量,struct mg_mgr是用于保存所有活动连接的事件管理器。

  struct mg_mgr mgr;                     // Event manager

设置 Mongoose 日志记录级别,设置等级为 MG_LL_DEBUG

  mg_log_set(MG_LL_DEBUG);               // Set log level

初始化一个事件管理器,也就是将上面定义的struct mg_mgr变量 mgr 中的数据进行初始化。

  mg_mgr_init(&mgr);                     // Initialise event manager

启动 SOCKS5 服务器,通过 mg_listen 函数创建一个监听连接,监听地址s_lsn。其中fn是事件处理函数。

  mg_listen(&mgr, s_lsn, fn, NULL);      // Create client connection

其中s_lsn是一个全局静态变量,值为tcp://localhost:1080

SOCKS5 使用 TCP 传输,默认端口为 1080。

static const char *s_lsn = "tcp://localhost:1080";  // Listening address

然后是事件循环,mg_mgr_poll 遍历所有连接,接受新连接,发送和接收数据,关闭连接,并为各个事件调用事件处理函数。

  while (true) mg_mgr_poll(&mgr, 1000);  // Infinite event loop

调用 mg_mgr_free 关闭所有连接,释放所有资源。

  mg_mgr_free(&mgr);                     // Free resources

分析完main函数后,我们看下事件处理函数fn的代码。

这个 SOCKS5 服务器的工作过程分为3个阶段,分别为握手(handshake),请求(request),交换(exchange)。当前的状态被记录在data中。

判断是否收到MG_EV_READ事件,当有从套接字socket接收到数据时,就会发送MG_EV_READ事件。当接收到MG_EV_READ事件后,判断当前的状态是处于什么阶段,就进入对应的状态处理函数中。

static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_READ) {
    // We use the first label byte as a state
    if (c->data[0] == STATE_HANDSHAKE) handshake(c);
    if (c->data[0] == STATE_REQUEST) request(c);
    if (c->data[0] == STATE_ESTABLISHED) exchange(c);
  } else if (ev == MG_EV_CLOSE) {
    disband(c);
  }
  (void) fn_data;
  (void) ev_data;
}

首先看下握手阶段(Handshake phase)。

代码中的注释部分是客户端握手消息的格式。

//  https://www.ietf.org/rfc/rfc1928.txt paragraph 3, handle client handshake
//  +----+----------+----------+
//  |VER | NMETHODS | METHODS  |
//  +----+----------+----------+
//  | 1  |    1     | 1 to 255 |
//  +----+----------+----------+
static void handshake(struct mg_connection *c) {

定义一个指针变量r指向c->recvIO 缓冲区,接收到的数据会放入其中。

  struct mg_iobuf *r = &c->recv;

检测客户端的版本是否是 SOCKS5,如果不是则关闭连接。将c->is_closing置 1 将立即关闭并释放连接。

  if (r->buf[0] != VERSION) {
    c->is_closing = 1;
  }

判断接收到的数据长度是否满足要求,用于判断接收到的数据是否包含整个客户端的握手消息。其中r->len > 2是因为握手消息的格式分为 3 个部分,所以长度一定会大于 2;然后r->buf[1]NMETHODS字段,表示METHODS字段的数量,再加上VERNMETHODS这两个字段的长度各为 1 ,所以整个握手包的长度为r->buf[1] + 2,因此完整的握手消息需要r->buf[1] + 2 <= r->len

  } else if (r->len > 2 && (size_t) r->buf[1] + 2 <= r->len) {

定义应答消息。

    /* https://www.ietf.org/rfc/rfc1928.txt paragraph 3 */
    uint8_t reply[2] = {VERSION, HANDSHAKE_FAILURE};

应答消息的格式如下:

                         +----+--------+
                         |VER | METHOD |
                         +----+--------+
                         | 1  |   1    |
                         +----+--------+

遍历握手消息,寻找支持的认证方法。这个程序目前只支持无需认证这个一种方法。

    int i;
    for (i = 2; i < r->buf[1] + 2; i++) {
      // TODO(lsm): support other auth methods
      if (r->buf[i] == HANDSHAKE_NOAUTH) reply[1] = r->buf[i];
    }

调用mg_iobuf_del函数清除已处理过的接收数据。mg_iobuf_del函数用于删除从 offset 开始的 len 个字节,并移动剩余的字节。

    mg_iobuf_del(r, 0, 2 + r->buf[1]);

将要回复应答的数据发送出去。mg_send函数将把size字节的data数据追加到输出缓冲区,以便稍后由事件管理器发送。

    mg_send(c, reply, sizeof(reply));

最后修改状态,表示进入请求阶段。

    c->data[0] = STATE_REQUEST;
  }
}

接下来看下请求阶段(Request phase)。

代码中的注释部分是客户端请求消息的格式。

//  Request, https://www.ietf.org/rfc/rfc1928.txt paragraph 4
//  +----+-----+-------+------+----------+----------+
//  |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
//  +----+-----+-------+------+----------+----------+
//  | 1  |  1  | X'00' |  1   | Variable |    2     |
//  +----+-----+-------+------+----------+----------+
static void request(struct mg_connection *c) {

定义了一些变量。其中定义了一个指针变量r指向c->recvIO 缓冲区,接收到的数据会放入其中。

  struct mg_iobuf *r = &c->recv;
  uint8_t *p = r->buf, addr_len = 4, reply = RESP_SUCCESS;
  int ver, cmd, atyp;
  char addr[1024];

判断接收到的数据长度是否小于 8,完整的数据包需要大于等于 8。

  if (r->len < 8) return;  // return if not fully buffered. min DST.ADDR is 2

给变量赋值,分别代表版本,命令和地址类型。

  ver = p[0];
  cmd = p[1];
  atyp = p[3];

判断客户端的版本是否是 SOCKS5,是否是连接请求,如果不是则回复表示命令不支持。

这个程序目前只支持连接请求这一种请求。

  // TODO(lsm): support other commands
  if (ver != VERSION || cmd != CMD_CONNECT) {
    reply = RESP_CMD_NOT_SUPPORTED;
  } 

判断地址类型是否是 IP V4。如果是则地址长度为 4,然后判断接收到的数据长度是否是一个完整数据包,如果不是则返回。接着将 IP 地址与端口号格式化为指定格式放入addr中。最后调用mg_connect创建连接,fn2是事件处理函数。

  } else if (atyp == ADDR_TYPE_IPV4) {
    addr_len = 4;
    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
    snprintf(addr, sizeof(addr), "tcp://%d.%d.%d.%d:%d", p[4], p[5], p[6], p[7],
             p[8] << 8 | p[9]);
    c->fn_data = mg_connect(c->mgr, addr, fn2, c);
  } 

判断地址类型是否是 IP V6。如果是则地址长度为 16,然后判断接收到的数据长度是否是一个完整数据包,如果不是则返回。接着将 IP 地址与端口号格式化为指定格式放入addr中。最后调用mg_connect创建连接。

  } else if (atyp == ADDR_TYPE_IPV6) {
    addr_len = 16;
    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
    snprintf(addr, sizeof(addr), "tcp://[%x:%x:%x:%x:%x:%x:%x:%x]:%d",
             p[4] << 8 | p[5], p[6] << 8 | p[7], p[8] << 8 | p[9],
             p[10] << 8 | p[11], p[12] << 8 | p[13], p[14] << 8 | p[15],
             p[16] << 8 | p[17], p[18] << 8 | p[19], p[20] << 8 | p[21]);
    c->fn_data = mg_connect(c->mgr, addr, fn2, c);
  } 

判断地址类型是否是域名。如果是则设置地址长度为p[4] + 1,地址类型为域名时,DST.ADDR字段的第一字节是长度域,然后判断接收到的数据长度是否是一个完整数据包,如果不是则返回。接着将域名与端口号格式化为指定格式放入addr中。最后调用mg_connect创建连接。

  } else if (atyp == ADDR_TYPE_DOMAIN) {
    addr_len = p[4] + 1;
    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
    snprintf(addr, sizeof(addr), "tcp://%.*s:%d", p[4], p + 5,
             p[4 + addr_len] << 8 | p[4 + addr_len + 1]);
    c->fn_data = mg_connect(c->mgr, addr, fn2, c);
  } 

其他的地址类型不支持。

  } else {
    reply = RESP_ADDR_NOT_SUPPORTED;
  }

代码中的注释部分是应答消息的格式。

填入应答数据的前 3 个字段,使用mg_send函数将数据放入发送缓冲区。

  //  Reply, https://www.ietf.org/rfc/rfc1928.txt paragraph 5
  //
  //  +----+-----+-------+------+----------+----------+
  //  |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
  //  +----+-----+-------+------+----------+----------+
  //  | 1  |  1  | X'00' |  1   | Variable |    2     |
  //  +----+-----+-------+------+----------+----------+
  {
    uint8_t buf[] = {VERSION, reply, 0};
    mg_send(c, buf, sizeof(buf));
  }

将应答消息的剩余部分放入发送缓冲区,并调用mg_iobuf_del清除已处理过的接收数据。最后修改状态,表示进入交换阶段。

  mg_send(c, r->buf + 3, addr_len + 1 + 2);
  mg_iobuf_del(r, 0, 6 + addr_len);  // Remove request from the input stream
  c->data[0] = STATE_ESTABLISHED;   // Mark ourselves as connected
}

接下来看下交换阶段(Exchange phase)。交换阶段要做的事就是将来自客户端的数据转发到服务器,将来自服务器的数据转发到客户端。这里通过exchange函数来实现该功能。

如果c->fn_data不为空,将收到的数据发送到另外一端,并在接收缓冲区清除这段数据。否则就将is_draining置位,这样会发送剩余数据,然后关闭并释放。

static void exchange(struct mg_connection *c) {
  struct mg_connection *c2 = (struct mg_connection *) c->fn_data;
  if (c2 != NULL) {
    mg_send(c2, c->recv.buf, c->recv.len);
    mg_iobuf_del(&c->recv, 0, c->recv.len);
  } else {
    c->is_draining = 1;
  }
}

示例程序中一共有两处调用exchange函数,一个是fn函数中,用于将来自客户端的数据转发到服务器。

    if (c->data[0] == STATE_ESTABLISHED) exchange(c);

另一个是在请求阶段时调用mg_connect函数创建连接的事件处理函数fn2。用于将接收到来自服务器的数据转发到客户端。

static void fn2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_READ) {
    exchange(c);
  } else if (ev == MG_EV_CLOSE) {
    disband(c);
  }
  (void) ev_data;
  (void) fn_data;
}

fnfn2函数中,如果收到MG_EV_CLOSE事件,表示连接关闭,调用disband函数进行关闭处理。

  } else if (ev == MG_EV_CLOSE) {
    disband(c);
  }

将两边连接的is_draining置位,刷新缓冲区然后关闭并释放,然后将fn_dataNULL

static void disband(struct mg_connection *c) {
  struct mg_connection *c2 = (struct mg_connection *) c->fn_data;
  if (c2 != NULL) {
    c2->is_draining = 1;
    c2->fn_data = NULL;
  }
  c->is_draining = 1;
  c->fn_data = NULL;
}

socks5-server 的示例程序代码就都解析完了,下面实际运行一下 socks5-server 程序进行测试验证。

打开示例程序,编译并运行:

pi@raspberrypi:~ $ cd Desktop/study/mongoose/examples/socks5-server/
pi@raspberrypi:~/Desktop/study/mongoose/examples/socks5-server $ make
cc ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES  -o example main.c
./example
137183022 3 net.c:182:mg_listen         1 0x4 tcp://localhost:1080

使用 curl 命令行工具来进行测试,指定 HTTP 请求通过localhost:1080的 socks5 代理发出。

pi@raspberrypi:~ $ curl -x socks5h://localhost:1080 -k -X GET http://www.baidu.com
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

看下我们的 SOCKS5 代理服务器的日志:

pi@raspberrypi:~ $ cd Desktop/study/mongoose/examples/socks5-server/
pi@raspberrypi:~/Desktop/study/mongoose/examples/socks5-server $ make
cc ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES  -o example main.c
./example
137183022 3 net.c:182:mg_listen         1 0x4 tcp://localhost:1080
137185e28 3 sock.c:417:accept_conn      2 0x5 accepted 127.0.0.1.63234 -> 127.0.0.1.1080
137185e29 3 sock.c:273:read_conn        2 0x5 snd 0/0 rcv 0/2048 n=4 err=0
137185e29 3 sock.c:284:write_conn       2 0x5 snd 2/2048 rcv 0/2048 n=2 err=0
137185e29 3 sock.c:273:read_conn        2 0x5 snd 0/2048 rcv 0/2048 n=20 err=0
137185e29 3 net.c:159:mg_connect        3 0xffffffffffffffff tcp://www.baidu.com:80
137185e29 3 net.c:159:mg_connect        4 0xffffffffffffffff udp://8.8.8.8:53
137185e2a 3 sock.c:146:mg_send          4 0x6 0:0 31 err 0
137185e2a 3 sock.c:284:write_conn       2 0x5 snd 20/2048 rcv 0/2048 n=20 err=0
137185e2a 3 sock.c:273:read_conn        2 0x5 snd 0/2048 rcv 0/2048 n=77 err=0
137185e57 3 sock.c:273:read_conn        4 0x6 snd 0/0 rcv 0/2048 n=90 err=9
137185e58 3 dns.c:165:dns_cb            3 www.a.shifen.com is 14.215.177.38
137185e58 3 sock.c:361:mg_connect_resol 3 0x7 -> 14.215.177.38:80 pend
137185e69 3 sock.c:284:write_conn       3 0x7 snd 77/2048 rcv 0/0 n=77 err=115
137185e7f 3 sock.c:273:read_conn        3 0x7 snd 0/2048 rcv 0/2048 n=2048 err=115
137185e7f 3 sock.c:273:read_conn        3 0x7 snd 0/2048 rcv 0/2048 n=733 err=115
137185e7f 3 sock.c:284:write_conn       2 0x5 snd 2781/4096 rcv 0/2048 n=2781 err=115
137185e82 3 sock.c:273:read_conn        2 0x5 snd 0/4096 rcv 0/2048 n=-1 err=115
137185e83 3 net.c:136:mg_close_conn     2 0x5 closed
13718626b 3 net.c:136:mg_close_conn     3 0x7 closed

【参考资料】

examples/socks5-server

Documentation

rfc1928


本文链接:https://blog.csdn.net/u012028275/article/details/129769177

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值