libwebsockets Secure Streams 静态策略实战:从 JSON 到嵌入式编译期策略

原创2026-10-13 15:24:171,726 阅读
文章标签:网络通信WebSocket嵌入式

libwebsockets Secure Streams 静态策略实战:从 JSON 到嵌入式编译期策略

本篇以 lws minimal secure streams static policy 示例为主体,讲解如何用"静态编译期策略"驱动 libwebsockets 的 Secure Streams 客户端:应用只需说出"我要哪个 streamtype",而端点、端口、TLS 信任栈、协议、重试策略等全部由 policy 数据库决定。读完本文,你可以理解示例的完整运行流程、静态策略 JSON 的每个字段如何映射为编译期 C 结构,以及如何在空间受限平台上用 policy2c 工具离线生成策略头文件。

1. 示例定位:策略与载荷严格解耦

Secure Streams 是 libwebsockets 的一个客户端 API,它严格地把连接策略(policy)与载荷(payload)解耦。用户代码只关心 stream type 名称和 payload,而一组在 lws_context 创建时设定的 policy 数据库决定了连接的一切策略——包括端点、TLS CA、甚至线上协议本身(h1、h2、ws、mqtt 等),这一点在 secure-streams 总览 中有明确说明。

本示例(位于 minimal-secure-streams-staticpolicy 目录)做的是:

  • 应用访问 https://warmcat.com,读取该处的 index.html;
  • 它使用的是由 policy2c 示例从 JSON 生成的静态 Secure Streams policy,而不是运行期动态解析的 JSON policy。

这正好对应 lws_context_creation_info 中的两种策略入口形式(见 lws-context-vhost.h):

#if defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY)
	const struct lws_ss_policy *pss_policies; /**< CONTEXT: point to first
	 * in a linked-list of streamtype policies prepared by user code */
#else
	const char *pss_policies_json; /**< CONTEXT: point to a string
	 * containing a JSON description of the secure streams policies. ... */
#endif

即:在 LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY 编译配置下,.pss_policies 指向一个由用户代码(通常是生成代码)准备好的 struct lws_ss_policy 链表;否则 .pss_policies_json 指向运行期解析的 JSON 字符串。本示例走的是前者。

2. 构建与运行

2.1 构建

$ cmake . && make

该示例的 CMakeLists.txt 声明了明确的构建前提(第 11~17 行),这些就是"适用前提":

require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY 1 requirements)
# the generated static policy carries mqtt streamtypes
require_lws_config(LWS_ROLE_MQTT 1 requirements)
require_lws_config(USE_WOLFSSL 0 requirements)

也就是说,libwebsockets 库必须启用 LWS_ROLE_H1、保留客户端支持、启用 LWS_WITH_SECURE_STREAMS 且以 LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY 方式构建;由于生成的静态策略里还携带了 mqtt 类型的 streamtype,LWS_ROLE_MQTT 也必须启用。

2.2 命令行选项

Commandline option Meaning
-d <loglevel> Debug verbosity in decimal, eg, -d15

2.3 运行输出(README 记录的完整示例)

$ ./lws-minimal-secure-streams-staticpolicy
[2020/03/26 15:49:12:6640] U: LWS secure streams static policy test client [-d<verb>]
[2020/03/26 15:49:12:7067] N: lws_create_context: using ss proxy bind '(null)', port 0, ads '(null)'
[2020/03/26 15:49:12:7567] N: lws_tls_client_create_vhost_context: using mem client CA cert 914
[2020/03/26 15:49:12:7597] N: lws_tls_client_create_vhost_context: using mem client CA cert 1011
[2020/03/26 15:49:12:7603] N: lws_tls_client_create_vhost_context: using mem client CA cert 1425
[2020/03/26 15:49:12:7605] N: lws_tls_client_create_vhost_context: using mem client CA cert 1011
[2020/03/26 15:49:12:9713] N: lws_system_cpd_set: setting CPD result OK
[2020/03/26 15:49:13:9625] N: ss_api_amazon_auth_rx: acquired 588-byte api.amazon.com auth token, exp 3600s
[2020/03/26 15:49:13:9747] U: myss_state: LWSSSCS_CREATING, ord 0x0
[2020/03/26 15:49:13:9774] U: myss_state: LWSSSCS_CONNECTING, ord 0x0
[2020/03/26 15:49:14:1897] U: myss_state: LWSSSCS_CONNECTED, ord 0x0
[2020/03/26 15:49:14:1926] U: myss_rx: len 1520, flags: 1
[2020/03/26 15:49:14:1945] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:1946] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:1947] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:1948] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:1949] U: myss_rx: len 583, flags: 0
[2020/03/26 15:49:14:2087] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2089] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2090] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2091] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2092] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2093] U: myss_rx: len 583, flags: 0
[2020/03/26 15:49:14:2109] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2110] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2111] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2112] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2113] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2114] U: myss_rx: len 583, flags: 0
[2020/03/26 15:49:14:2135] U: myss_rx: len 1520, flags: 0
[2020/03/26 15:49:14:2136] U: myss_rx: len 1358, flags: 0
[2020/03/26 15:49:14:2136] U: myss_rx: len 0, flags: 2
[2020/03/26 15:49:14:2138] U: myss_state: LWSSSCS_QOS_ACK_REMOTE, ord 0x0
[2020/03/26 15:49:14:2139] N: myss_state: LWSSSCS_QOS_ACK_REMOTE
[2020/03/26 15:49:14:2170] U: myss_state: LWSSSCS_DISCONNECTED, ord 0x0
[2020/03/26 15:49:14:2192] U: myss_state: LWSSSCS_DESTROYING, ord 0x0
[2020/03/26 15:49:14:2265] E: lws_context_destroy3
[2020/03/26 15:49:14:2282] U: Completed: OK

从这份日志可以读出一次完整生命周期:context 创建时为策略中的信任栈逐条装载内存 CA 证书(using mem client CA cert ...);CPD(captive portal detection, captive portal 探测)完成并置为 OK——这与策略中定义的 captive_portal_detect streamtype 相对应;随后流状态机依次经过 CREATING → CONNECTING → CONNECTED,myss_rx 回调按分片收到整个 index.html(最后一片 flags: 2 即 LWSSS_FLAG_EOM 消息结束标志),应用收到 EOM 后主动退出事件循环,流走 QOS_ACK_REMOTE → DISCONNECTED → DESTROYING 收尾,最终打印 Completed: OK。

3. 源码走读:minimal-secure-streams.c

示例主程序 minimal-secure-streams.c 不到 300 行,是理解"静态策略客户端写法"的最佳范本。

3.1 静态策略的引入

/*
 * This is example builds with a static policy autogenerated from a JSON
 * policy...
 */
#include "static-policy.h"

第 55 行直接 include 由 policy2c 生成的 static-policy.h,整个策略——证书、信任栈、重试、全部 streamtype——在编译期就是常量数据。

3.2 应用私有状态与 payload 三回调

typedef struct myss {
	struct lws_ss_handle		*ss;
	void				*opaque_data;
	/* ... application specific state ... */
	lws_sorted_usec_list_t		sul;
} myss_t;

myss_t 携带 lws_ss_handle * 与不透明数据,通过 lws_ss_info_t 中的 offsetof 偏移告诉框架如何找到自己的字段。payload 接口是三个回调:

  • myss_rx(第 81~99 行):打印每片 rx 数据并 hexdump;关键是判断 flags & LWSSS_FLAG_EOM——收到完整消息即把 bad = 0 并 lws_default_loop_exit(context),表示示例任务完成;
  • myss_tx:本例是纯读页面,直接返回 0;
  • myss_state(第 110~139 行):流状态机回调,示例在三个状态点做事:
switch (state) {
case LWSSSCS_CREATING:
	/* 设置 metadata:url 里的 ${uptag} 会用它展开 */
	if (lws_ss_set_metadata(m->ss, "uptag", "myuptag123", 10))
		lwsl_err("%s set metadata uptag failed\n", __func__);
	if (lws_ss_set_metadata(m->ss, "ctype", "myctype", 7))
		lwsl_err("%s set metadata ctype failed\n", __func__);
	return lws_ss_client_connect(m->ss);

case LWSSSCS_ALL_RETRIES_FAILED:
	/* 重试耗尽:退出应用并标记 FAIL */
	lws_default_loop_exit(context);
	break;
case LWSSSCS_QOS_ACK_REMOTE:
	lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__);
	break;
default:
	break;
}

注意 LWSSSCS_CREATING 时先 lws_ss_set_metadata() 再 lws_ss_client_connect():策略中 mintest 的 url 是 index.html?uptag=${uptag},${uptag} 就是在这里设置的 metadata 名 uptag 的占位符展开机制——应用不拼 URL,只给名字赋的值。

3.3 与 lws system 状态机集成:何时建流、何时备认证

示例用 register_notifier_list 注册了一个系统状态通知器(第 141~195 行),把"创建流"这一动作挂到系统到达 LWS_SYSTATE_OPERATIONAL 的时刻,而不是在 main 里立即做:

static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
		    int current, int target)
{
	switch (target) {

	case LWS_SYSTATE_REGISTERED:
		/* 如果 AUTH root token 还没有,就注册一个现成的 root token */
		...
		lws_system_blob_direct_set(ab,
				(const uint8_t *)canned_root_token_payload,
				strlen(canned_root_token_payload));
		break;

	case LWS_SYSTATE_OPERATIONAL:
		if (current == LWS_SYSTATE_OPERATIONAL) {
			/* We're making an outgoing secure stream ourselves */
			memset(&ssi, 0, sizeof(ssi));
			ssi.handle_offset = offsetof(myss_t, ss);
			ssi.opaque_user_data_offset = offsetof(myss_t,
						       opaque_data);
			ssi.rx = myss_rx;
			ssi.tx = myss_tx;
			ssi.state = myss_state;
			ssi.user_alloc = sizeof(myss_t);
			ssi.streamtype = "mintest";

			if (lws_ss_create(context, 0, &ssi, NULL, NULL,
					  NULL, NULL)) { ... }
		}
		break;
	}
	return 0;
}

这里体现了 Secure Streams 的典型协作模式:应用只声明 ssi.streamtype = "mintest",其余一切(连到 warmcat.com:443、走 h1、GET、TLS 信任 le_via_isrg)由策略决定。LWS_SYSTATE_REGISTERED 分支则演示了 lws_system blob 机制:AUTH root token 若未置,则从一段现成字符串(示例中 canned_root_token_payload,对应策略里 api_amazon_com_auth 的 refresh token 流程)直接写入——direct_set 只存指针,被指对象必须具有足够长的生命周期。

main 里同样设置了设备身份类 blob(第 290~309 行),策略里 http_dsn_header / http_fwv_header / http_devtype_header 三个头会分别携带它们:

lws_system_blob_direct_set(lws_system_get_blob(context,
	   LWS_SYSBLOB_TYPE_DEVICE_SERIAL, 0),
	   (const uint8_t *)"SN12345678", 10);
lws_system_blob_direct_set(lws_system_get_blob(context,
	   LWS_SYSBLOB_TYPE_DEVICE_FW_VERSION, 0),
	   (const uint8_t *)"v0.01", 5);
/* ..._heap_append() 方式追加(buflist 式,可多次追加、context 销毁时释放) */
lws_system_blob_heap_append(lws_system_get_blob(context,
	    LWS_SYSBLOB_TYPE_DEVICE_TYPE, 0),
	    (const uint8_t *)"spacerocket", 11);

3.4 context 创建与静态策略的挂钩

lws_context_info_defaults(&info, NULL);
lws_cmdline_option_handle_builtin(argc, argv, &info);
...
info.fd_limit_per_thread = LWS_FD_LIMIT_PER_THREAD_MIN + 6;
info.port = CONTEXT_PORT_NO_LISTEN;
...
info.pss_policies = &_ss_static_policy_entry;
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
	       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;

第 263~265 行是静态策略模式的两行关键配置:

  • info.pss_policies = &_ss_static_policy_entry;——把生成头里 #define _ss_static_policy_entry _ssp_api_amazon_com_auth 定义的链表头(见 static-policy.h 第 1587 行)挂进 context 信息;
  • LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT——由于本进程自己完成 TLS 客户端连接(不经过 ss proxy),需要自行完成 TLS 库全局初始化。源码头注释也说明:同一份源码可以编成"自带静态策略"和"走 -proxy 示例"两种形态,走 proxy 的形态甚至不初始化 TLS 库,因为代理侧包办一切。

事件循环是最朴素的形态:

while (n >= 0)
	n = lws_service(context, 0);

lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");

myss_rx 收到 EOM 后 lws_default_loop_exit() 让 lws_service 返回 -1,进程以 bad(0 = OK)为退出码结束。

4. static-policy.json:策略数据库的结构

static-policy.json 是"唯一事实源",policy2c 从它生成 C 代码。整体分五大块:

{
	"release": "01234567",
	"product": "myproduct",
	"schema-version": 1,
	"retry": [ { "default": {
		"backoff": [1000, 2000, 3000, 5000, 10000],
		"conceal": 5,
		"jitterpc": 20,
		"svalidping": 30,
		"svalidhup": 35
	} } ],
	"certs": [ { "isrg_root_x1": "MIIFaz..." }, ... ],
	"trust_stores": [
		{ "name": "le_via_isrg", "stack": ["isrg_root_x2", "LEX3_isrg_root_x2"] },
		{ "name": "api_amazon_com", "stack": ["digicert_global_ca_g2", "digicert_global_root_g2"] },
		{ "name": "avs_via_starfield", "stack": ["starfield_class_2_ca", "starfield_services_root_ca"] },
		{ "name": "mqtt_amz_iot", "stack": ["amazon_root_ca_1", "starfield_class_2_ca", "starfield_services_root_ca"] }
	],
	"s": [ ... 各 streamtype 定义 ... ]
}
  • retry:命名的重试策略集。default 的 backoff 是毫秒级退避表(最多 5 级),conceal 是"掩盖"重试次数的计数,jitterpc 是抖动百分比,svalidping / svalidhup 分别是距最近有效 ping / 有效挂起的秒数上限(空闲判活依据);
  • certs:base64 编码的 DER 形式 X.509 证书池,每条以名字(如 isrg_root_x2)索引。示例里只内置了实际用到的几个根/中间 CA——这正是嵌入式场景的做法:不依赖操作系统 CA 库,信任栈白名单化;
  • trust_stores:把若干 certs 按名字组合成具名信任栈(如 le_via_isrg),供 streamtype 以 tls_trust_store 引用;
  • s:streamtype 数组。示例覆盖了 11 个 streamtype:api_amazon_com_auth、avs_event / avs_metadata / avs_audio(Alexa 语音服务的 h2 长轮询与 multipart 流)、mintest、h2longpolltest、mintest-fail、minpost、mqtt_test / mqtt_test1、captive_portal_detect。本应用真正使用的是 mintest,其余条目用于展示策略的表达能力(也解释了 CMake 为何要求 LWS_ROLE_MQTT)。

本例使用的 mintest 条目(static-policy.json 第 116~137 行):

"mintest": {
	"endpoint": "warmcat.com",
	"port": 443,
	"protocol": "h1",
	"http_method": "GET",
	"http_url": "index.html?uptag=${uptag}",
	"http_dsn_header": "x-dsn:",
	"http_fwv_header": "x-fw-version:",
	"http_devtype_header": "x-devtype:",
	"metadata": [
		{ "uptag": "X-Upload-Tag:" },
		{ "ctype": "Content-Type:" },
		{ "xctype": "X-Content-Type:" }
	],
	"plugins": [],
	"tls": true,
	"opportunistic": true,
	"retry": "default",
	"tls_trust_store": "le_via_isrg"
}

字段含义逐一对应到 struct lws_ss_policy(定义见 lws-secure-streams-policy.h 第 226~366 行):

JSON 字段 作用 对应 C 结构成员
endpoint / port 要连接的 DNS 名与端口 .endpoint / .port
protocol "h1" / "h2" / "h3" / "ws" / "mqtt" 等 .protocol(0=LWSSSP_H1,1=LWSSSP_H2…)
http_method / http_url 请求方法与 url,url 支持 ${name} 占位符 .u.http.method / .u.http.url
http_dsn_header 等 用 lws_system blob(设备序列号/固件版本/设备类型)填充的请求头名 .u.http.blob_header[LWSSS_HBI_DSN/FWV/TYPE]
metadata 声明允许设置哪些 metadata、以及它们是否同时作为 HTTP 头发出(值如 "X-Upload-Tag:") .metadata 链表 / .metadata_count
tls / tls_trust_store 强制 TLS 并指定信任栈 .flags 的 TLS 位 + .trust.store
opportunistic 连接按需惰性建立:不主动连,客户端要写时才连 .flags 的 OPPORTUNISTIC 位
retry 引用 retry 数组里的具名策略 .retry_bo
http_auth_header / http_auth_preamble 认证头与 Bearer 之类前缀(avs 系列使用) .u.http.blob_header[LWSSS_HBI_AUTH] / .u.http.auth_preamble
long_poll / nailed_up 长轮询、连接"钉住"整个流生命周期 .flags 的 LONG_POLL / NAILED_UP 位
http_multipart_name / http_mime_content_type multipart 段名与 content-type .u.http.multipart_*
rideshare 复用另一个已存在 streamtype 的连接 .rideshare_streamtype

5. static-policy.h:生成 C 的形态与 flags 解码

static-policy.h 文件头注明 "Autogenerated from the following JSON policy",#if 0 块内完整保留了源 JSON(含 Original JSON size: 15493 标注),其后的内容依次是:

① 重试策略——JSON retry.default 一比一映射为 lws_retry_bo_t(第 229~239 行):

static const uint32_t _rbo_bo_0[] = {
 1000,  2000,  3000,  5000,  10000,
};
static const lws_retry_bo_t _rbo_0 = {
	.retry_ms_table = _rbo_bo_0,
	.retry_ms_table_count = 5,
	.conceal_count = 5,
	.secs_since_valid_ping = 30,
	.secs_since_valid_hangup = 35,
	.jitter_percent = 20,
};

② 证书与信任栈——每个 base64 证书被解码为 DER 字节数组 _ss_der_<name><a href="https://link.gitcode.com/i/7822ffde7824cefcc20380491e6d6828" target="_blank">],并包进 lws_ss_x509_t(见 [lws-secure-streams-policy.h 第 73~79 行 的 vhost_name / ca_der / ca_der_len 三字段);信任栈则用 lws_ss_trust_store_t 把若干 x509 指针按 stack 顺序装进 ssx509[6] 数组,例如:

static const lws_ss_trust_store_t _ss_ts_mqtt_amz_iot = {
	.name = "mqtt_amz_iot",
	.ssx509 = {
		&_ss_x509_starfield_services_root_ca,
		&_ss_x509_starfield_class_2_ca,
		&_ss_x509_amazon_root_ca_1,
	}
};

③ streamtype 策略链表——每个 JSON s 条目变成一个 lws_ss_policy 实例(_ssp_mintest、_ssp_avs_event 等),用 .next 指针串成链,且注意生成顺序与 JSON 中相反:链表尾是 api_amazon_com_auth,链表头经宏指向它。以 mintest 为例(static-policy.h 第 1488~1505 行):

_ssp_mintest = {
	.next = (void *)&_ssp_h2longpolltest,
	.streamtype = "mintest",
	.endpoint = "warmcat.com",
	.metadata = (void *)&_md_mintest_uptag,
	.u = {
		.http = {
			.method = "GET",
			.url = "index.html?uptag=${uptag}",
		}
	},
	.retry_bo = &_rbo_0,
	.flags = 0x11,
	.port = 443,
	.metadata_count = 3,
	.protocol = 0,
	.trust = {.store = &_ss_ts_le_via_isrg},
},

④ 入口宏与占用估算(文件尾两行):

#define _ss_static_policy_entry _ssp_api_amazon_com_auth
/* estimated footprint 10720 (when sizeof void * = 64-bit) */

整个策略数据库的静态占用约 10.7 KB——这就是"静态策略"模式在空间受限平台上成立的原因:无运行期 JSON 解析器、无字符串查找表,只有 const 数据。

flags 位解码:.flags 是 LWSSSPOLF_* 位集合(定义见 lws-secure-streams-policy.h 第 81~144 行)。以 mintest 为例,0x11 = OPPORTUNISTIC(1<<0) | TLS(1<<4),与 JSON 的 "opportunistic": true, "tls": true 完全对应;h2longpolltest 的 0x32 = NAILED_UP | TLS | LONG_POLL,对应 JSON 的 nailed_up + tls + long_poll;api_amazon_com_auth 的 0x1291 = X_WWW_FORM_URLENCODED | H2_QUIRK_OVERFLOWS_TXCR | HTTP_NO_CONTENT_LENGTH | TLS | OPPORTUNISTIC,对应 JSON 里的 http_www_form_urlencoded、h2q_oflow_txcr、http_no_content_length、tls、opportunistic 五个布尔量。对照这两处即可掌握任意策略条目的"翻译规则"。

6. 用 policy2c 重新生成策略头

示例自带的 static-policy.h 是随仓库提交的成品,实际维护流程由姊妹示例 minimal-secure-streams-policy2c 完成:它从 stdin 读入 JSON 策略,把等价 C 结构打印到 stdout。按其 README:

$ cmake . && make

$ cat mypolicy.json | lws-minimal-secure-streams-policy2c

stdout 输出即上文第 5 节所示形态的代码(_rbo_0、_ss_der_*、_ss_x509_*、信任栈与 _ssp_* 链表)。policy2c 依赖 libwebsockets 同时启用 LWS_ROLE_H1、LWS_ROLE_H2、LWS_ROLE_WS、LWS_ROLE_MQTT(它必须能处理任何策略内容)。工作流因此是:

  1. 在 static-policy.json 中维护/修改策略(加 streamtype、换 CA、调 backoff);
  2. 运行 policy2c 重新生成 static-policy.h;
  3. 重新构建应用,#include "static-policy.h" 即可生效。

这样既保留了"以 JSON 为唯一事实源、便于人工审阅与维护"的优点,又让目标平台完全不付出运行期解析的成本。

7. 适用场景与注意事项

结合 CMakeLists.txt 的约束与源码注释,可以归纳出该模式的适用边界:

  • 空间受限 / 无文件系统平台:策略是纯 const 数据,无需运行期 JSON 解析与文件读取,LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY 构建下 context 只接受 pss_policies 链表而不接受 JSON 字符串;
  • 策略白名单化:CA 证书池与信任栈在生成时固化,等价于"最小信任面",适合 IoT 设备只访问少数固定端点;
  • 需要 TLS:示例用 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT 自行完成 TLS 全局初始化(不经过 ss proxy 的形态);
  • 同一源码双形态:源码注释(第 14~21 行)说明该文件也可编成走 -proxy 示例的形态,此时应用侧不初始化 TLS,由 proxy 完成连接——即静态策略客户端既可"自带策略直连",也可"零策略连代理"。

调试时 -d15 级别日志能把 CPD 探测、TLS 信任栈装载、认证 token 获取、流状态迁移与 rx 分片细节全部呈现(见第 2.3 节输出),配合 myss_rx 中的 lwsl_hexdump_info() 即可定位到字节级问题。示例结束时以 bad 变量(收到 EOM 前为 1)作为进程退出码,方便脚本化验证。

登录后查看全文
libwebsockets