【Mongoose笔记】SOCKS5 服务器

article/2023/6/4 16:13:29

【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 addressenum {VERSION = 5,                  // Socks protocol versionSTATE_HANDSHAKE = 0,          // Connection state: in handshakeSTATE_REQUEST = 1,            // Connection state: connectingSTATE_ESTABLISHED = 2,        // Connection state: establishedHANDSHAKE_NOAUTH = 0,         // Handshake method - no authenticationHANDSHAKE_GSSAPI = 1,         // Handshake method - GSSAPI authHANDSHAKE_USERPASS = 2,       // Handshake method - user/password authHANDSHAKE_FAILURE = 0xff,     // Handshake method - failureCMD_CONNECT = 1,              // Command: CONNECTCMD_BIND = 2,                 // Command: BINDCMD_UDP_ASSOCIATE = 3,        // Command: UDP ASSOCIATEADDR_TYPE_IPV4 = 1,           // Address type: IPv4ADDR_TYPE_DOMAIN = 3,         // Address type: Domain nameADDR_TYPE_IPV6 = 4,           // Address type: IPv6RESP_SUCCESS = 0,             // Response: successRESP_FAILURE = 1,             // Response: failureRESP_NOT_ALLOWED = 2,         // Response statusRESP_NET_UNREACHABLE = 3,     // Response statusRESP_HOST_UNREACHABLE = 4,    // Response statusRESP_CONN_REFUSED = 5,        // Response statusRESP_TTL_EXPIRED = 6,         // Response statusRESP_CMD_NOT_SUPPORTED = 7,   // Response statusRESP_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 methodsif (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 2ver = p[0];cmd = p[1];atyp = p[3];// TODO(lsm): support other commandsif (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 streamc->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 stateif (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 managermg_log_set(MG_LL_DEBUG);               // Set log levelmg_mgr_init(&mgr);                     // Initialise event managermg_listen(&mgr, s_lsn, fn, NULL);      // Create client connectionwhile (true) mg_mgr_poll(&mgr, 1000);  // Infinite event loopmg_mgr_free(&mgr);                     // Free resourcesreturn 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 stateif (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 methodsif (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 commandsif (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 streamc->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

http://www.ngui.cc/article/show-1007640.html

相关文章

重启Android后SystemProperties属性变化

重启Android后SystemProperties属性变化1、SystemProperties属性加载2、PropertySet条件限制3、SystemProperties属性变化android12-release1、SystemProperties属性加载 查看 SystemProperties属性加载 属性映射区域LoadPath("/dev/__properties__/property_info")…

CentOs7 + Stable Diffusion + Novel AI实现AI绘画

前提条件 GPU服务器含有NVIDIA显卡安装Git环境&#xff08;版本 > 1.8.5&#xff09; 查看版本&#xff1a;git version 升级git&#xff1a;yum install -y https://repo.ius.io/ius-release-el7.rpm && yum install -y epel-release && yum erase -y git…

SpringCloud:SpringAMQP介绍

Spring AMQP是基于RabbitMQ封装的一套模板&#xff0c;并且还利用SpringBoot对其实现了自动装配&#xff0c;使用起来非常方便。Spring AMQP官方地址 Spring AMQP提供了三个功能&#xff1a; 自动声明队列、交换机及其绑定关系基于注解的监听器模式&#xff0c;异步接收消息封…

【致敬未来的攻城狮计划】连续打卡第4天+物联网操作系统概述

开启攻城狮的成长之旅&#xff01;这是我参与的由 CSDN博客专家 架构师李肯&#xff08;http://yyds.recan-li.cn&#xff09;和 瑞萨MCU &#xff08;https://www.renesas.cn/cn/zh&#xff09; 联合发起的「 致敬未来的攻城狮计划 」的第 4 天&#xff0c;点击查看活动计划详…

hastcat

hashcat 下载地址: https://hashcat.net/hashcat/ 案例 Usage: hashcat [options]... hash|hashfile|hccapxfile [dictionary|mask|directory]...https://xz.aliyun.com/t/4008破解linux shadow /etc/shadow中密码格式: $id$salt$encrypted如:$1$2eWq10AC$NaQqalCk3 1表…

ssm+vue在线课程培训系统java

在线培训系统的需求和管理上的不断提升&#xff0c;在线培训系统的潜力将无限扩大&#xff0c;在线培训系统在业界被广泛关注&#xff0c;本网站及对此进行总体分析&#xff0c;将在线培训系统信息管理的发展提供学校的管理帮助更大。 根据现有的模块&#xff0c;除管理员对系统…

docker安装Redis高可用(一主二从三哨兵)

本次教程使用docker swarm安装 准备三台机器 hostIP用途node1192.168.31.130redis-master01&#xff0c;redis哨兵节点01node2192.168.31.131redis-slave01, redis哨兵节点02node3192.168.31.132redis-slave02 redis哨兵节点02 注意事项&#xff1a; 1&#xff1a;需要保证三…

(数字图像处理MATLAB+Python)第二章数字图像处理基础-第二节:色度学基础与颜色模型

文章目录一&#xff1a;颜色匹配二&#xff1a;CIE 1931-RGB系统三&#xff1a;CIE 1931标准色度系统四&#xff1a;CIE 1976Lab均匀颜色空间五&#xff1a;孟塞尔表色系统&#xff08;1&#xff09;孟塞尔明度(Value&#xff0c;记为V)&#xff08;2&#xff09;孟塞尔彩度(Ch…

Linq使用where sql in (...)

List<int?> 机构编号数组 new List<int?>();var begin new DateTime(year, 1, 1);var end begin.AddYears(1);IQueryable<DtoReport健康体检表> Q_健康体检表 es.jktjEntitys.Where(m > m.体检日期 > begin && m.体检日期 < end).Whe…

linux 产生随机数 并遍历

1、产生随机数 varRANDOMvarRANDOM varRANDOMvar[ $var % 150 ] 2、产生不重复的随机数 $ entries($(shuf -i 0-149 -n 15)) $ echo “${entries[]}” 3、对随机数排序 $ entries($(shuf -i 0-149 -n 15 | sort -n)) $ echo “entries[]"12224549546678798393118119124140…