启用早期数据0-RTT降低首次握手延迟与风险缓解方案
什么是 TLS 1.3 Early Data (早期数据,也称 0-RTT)?
TLS 1.3 Early Data,也称为 0-RTT (Zero Round Trip Time,零往返时间) 恢复,是 TLS 1.3 协议中引入的一项重要性能优化特性。它的核心目标是 减少客户端与服务器之间建立安全 TLS 连接的延迟,特别是对于 已经与服务器建立过连接的返回客户端 而言,可以显著提升连接速度。
传统 TLS 握手与 0-RTT 的对比:
在 TLS 1.3 之前的版本 (例如 TLS 1.2 及更早版本) 中,建立一条新的 TLS 连接需要进行 完整的 TLS 握手 (Full Handshake)。 这个完整的握手过程,即使在最优化的情况下,也至少需要 1 个 往返时间 (1-RTT) 甚至 2 个 往返时间 (2-RTT) 才能完成密钥交换、算法协商、身份验证等关键步骤,之后客户端和服务器才能开始安全地传输应用数据。
这种握手延迟在用户首次访问网站或者网络延迟较高的情况下会比较明显,影响用户体验。
TLS 1.3 Early Data (0-RTT) 的目标就是解决这个问题。 它的好处:
- 降低延迟,提升性能:客户端在恢复 TLS 会话时,无需完成完整的 TLS 握手流程即可发送首个请求数据。相比 TLS 1.2 的 1-RTT 握手,减少了 一次网络往返时间(Round Trip Time, RTT),尤其对高延迟网络(如移动网络)效果显著。因此,0-RTT适用于频繁短连接的 Web 应用(如 API 请求);实时通信(如 WebSocket、视频流);需要快速加载的首屏页面,等等。
- 优化用户体验:用户首次访问时可能感知不到差异,但在会话恢复(如页面刷新、短时间重连)时,加载速度更快。
- 配置简化,兼容现代协议:TLS 1.3是新一代安全协议,结合 HTTP/2 或 HTTP/3 时,性能提升更明显。通过 Nginx 统一启用 0-RTT 并传递 Early-Data 头,后端服务(如 Xray、Web 应用)无需单独处理 TLS 参数,只需检查请求头即可判断是否为早期数据。
TLS 1.3 Early Data 的运行风险:
- 重放攻击(Replay Attack):0-RTT 的早期数据(Early Data)可能被攻击者截获并重复发送到服务端,导致 非幂等操作被多次执行(如重复提交订单、支付请求)。比如,如果一个请求是“转账100元”,攻击者重放这个请求,可能导致多次转账。因此,对于非幂等性操作(如提交订单、支付等),使用0-RTT需要特别小心。
- 客户端支持差异: 部分旧版客户端(如不支持 TLS 1.3 的浏览器或库)可能无法正确处理 0-RTT,导致连接失败或回退到 1-RTT。某些防火墙或代理设备可能丢弃含早期数据的流量,导致连接不稳定,存在兼容性差异。
- 配置不当引发的漏洞:若后端服务未检查 Early-Data 头,可能错误处理重放请求。例如反向代理配置中未正确传递 Early-Data 头,导致后端服务无法区分正常请求与早期数据。
风险缓解方案
- 仅在必要时启用 0-RTT:对静态资源、CDN 节点或只读 API 开启,动态写入操作保持关闭。
- 校验请求幂等性:在后端代码中检查 Early-Data 头,若存在则拒绝非幂等操作(POST/PUT/DELETE).
- 使用一次性令牌(Nonce):为早期数据请求生成唯一令牌,服务端校验后立即失效,防止重放。
- 绑定会话票据(Session Ticket)有效期:缩短 TLS 会话票据的生命周期,减少重放窗口.
对于高危操作如清除缓存,建议关闭 0-RTT 并严格校验请求来源和幂等性。对于启用0-RTT,必须要在后端服务(Apache/PHP)中实现 Early-Data 头校验和动态控制 0-RTT。使用0-RTT的同时,服务端要开启HTTP/2 Server Push和Strict-Transport-Security头等安全配置指令。
早期数据0-RTT集成WordPress代码
下面是一个将早期数据0-RTT集成到 WordPress 主题 functions.php 的完整方案,包含安全增强和性能优化,通过深度集成WordPress核心API,在保持高性能的同时提供企业级安全防护,特别适合需要处理敏感操作(如支付、登录)的网站。建议配合CDN的早期数据支持和HTTP/3部署以获得最佳效果
- 安全纵深防御:请求指纹验证,动态Nonce生命周期,智能分级速率限流保护
- 智能 TTL 管理:根据请求类型动态调整有效期,POST/DELETE 等写操作有效期更短,GET 请求有效期较长但自动续期
- 性能优化措施:盐值缓存机制, 使用wp_salt()代替自定义盐值,自动利用WordPress的对象缓存系统
- 动态哈希算法:根据服务器支持自动选择最佳哈希算法
- 智能缓存清理:添加定时清理过期nonce
首先理解一下理解安全验证流程
sequenceDiagram
participant Client
participant Nginx
participant WordPress
Client->>Nginx: 发起 0-RTT 请求 (含 Early-Data 头)
Nginx->>WordPress: 转发请求 (添加 HTTP_EARLY_DATA=1 头)
WordPress->>WordPress: 检查 Early-Data 头
alt 是早期数据请求
WordPress->>验证流程: 执行安全验证链
验证流程->>速率限制: 检查 IP/全局请求频率
速率限制-->>拒绝: 超过阈值返回 425
验证流程->>指纹验证: 校验客户端指纹
指纹验证-->>拒绝: 指纹不匹配
验证流程->>Nonce验证: 检查一次性令牌
Nonce验证-->>拒绝: 令牌无效或已使用
WordPress->>业务逻辑: 执行正常请求
else 普通请求
WordPress->>业务逻辑: 直接处理
end
以下是PHP代码
/**
* Theme Integration: Early Data Replay Protection (Optimized)
* Description: Advanced replay protection for TLS 1.3 Early Data in WordPress
* Version: 1.0.0
* Author: Colin
* License: GPL2+
*/
defined('ABSPATH') || exit;
if (!class_exists('Early_Data_Protection_Theme')) {
class Early_Data_Protection_Theme {
const CACHE_TTL = 300; // 5分钟缓存
const RATE_LIMIT = 10; // 每分钟最大请求数
const FINGERPRINT_SALT = 'edp_fp_v1'; // 指纹盐值
const GLOBAL_RATE_LIMIT = 1000; // 全局限流
private $nonce_key = 'edp_nonce';
private $current_ttl;
public function __construct() {
if (!defined('NONCE_SALT') || NONCE_SALT == 'put your unique phrase here') {
add_action('admin_notices', function() {
echo 'SECURITY WARNING: Default NONCE_SALT value is not modified!
';
});
}
add_action('parse_request', [$this, 'validate_early_data'], 1);
add_action('wp_enqueue_scripts', [$this, 'inject_frontend_nonce']);
add_action('login_enqueue_scripts', [$this, 'inject_frontend_nonce']);
add_filter('rest_authentication_errors', [$this, 'validate_rest_api']);
add_action('edp_cleanup', [$this, 'cleanup_transients']);
// 初始化定时任务
if (!wp_next_scheduled('edp_cleanup')) {
wp_schedule_event(time(), 'twicedaily', 'edp_cleanup');
}
}
// 主验证方法
public function validate_early_data() {
if ($this->is_early_data_request()) {
$this->process_validation();
}
}
// 判断是否早期数据请求
private function is_early_data_request(): bool {
return isset($_SERVER['HTTP_EARLY_DATA']) && '1' === $_SERVER['HTTP_EARLY_DATA'];
}
// 验证流程
private function process_validation() {
// 速率限制
$this->check_rate_limit();
$method = strtoupper($_SERVER['REQUEST_METHOD']);
$nonce = $this->get_nonce();
// 指纹验证
if (!$this->validate_fingerprint($nonce)) {
$this->reject_request('invalid_fingerprint');
}
// 动态TTL设置
$this->current_ttl = $this->get_ttl($method);
if (in_array($method, ['POST', 'PUT', 'DELETE'])) {
$this->validate_strict($nonce);
} elseif ('GET' === $method) {
$this->validate_loose($nonce);
}
$this->cache_nonce($nonce);
}
// 获取Nonce
private function get_nonce(): string {
return sanitize_text_field(
$_REQUEST[$this->nonce_key] ??
($_SERVER['HTTP_X_EDP_NONCE'] ?? '')
);
}
// 严格验证(写操作)
private function validate_strict(string $nonce) {
if (!$this->is_valid_nonce($nonce)) {
$this->reject_request();
}
}
// 宽松验证(读操作)
private function validate_loose(string $nonce) {
if (!empty($nonce) && $this->is_replayed_nonce($nonce)) {
$this->reject_request();
}
}
// 缓存Nonce
private function cache_nonce(string $nonce) {
if (!empty($nonce) && !$this->is_replayed_nonce($nonce)) {
set_transient(
$this->get_transient_key($nonce),
time(),
self::CACHE_TTL
);
}
}
// 验证Nonce有效性
private function is_valid_nonce(string $nonce): bool {
return !empty($nonce) && !$this->is_replayed_nonce($nonce);
}
// 检查是否重放
private function is_replayed_nonce(string $nonce): bool {
return (bool) get_transient($this->get_transient_key($nonce));
}
// 生成缓存键
private function get_transient_key(string $nonce): string {
return 'edp_' . hash('sha256', wp_salt() . $nonce);
}
// 拒绝请求
private function reject_request(string $error_code = 'default') {
status_header(425);
nocache_headers();
$messages = [
'rate_limit_exceeded' => __('Requests are too frequent, try later.', 'cq'),
'invalid_fingerprint' => __('Client fingerprint verification failed.', 'cq'),
'default' => __('Security verification failed. Refresh the page.', 'cq')
];
if (defined('REST_REQUEST') && REST_REQUEST) {
wp_send_json_error([
'code' => $error_code,
'message' => $messages[$error_code] ?? $messages['default']
], 425);
}
wp_die(
'' . esc_html($messages[$error_code]) . '
',
__('Security verification failed.', 'cq'),
['response' => 425]
);
}
// 前端Nonce注入
public function inject_frontend_nonce() {
wp_add_inline_script(
'jquery-core',
sprintf(
'window.edpConfig = { nonce: "%s", key: "%s" };',
$this->generate_nonce(),
esc_js($this->nonce_key)
)
);
}
// REST API验证
public function validate_rest_api($result) {
if ($this->is_early_data_request() && !$this->is_valid_nonce($this->get_nonce())) {
return new WP_Error(
'rest_early_data',
__('Early data requires valid nonce'),
['status' => 425]
);
}
return $result;
}
// 生成加密Nonce
private function generate_nonce(): string {
try {
$random = bin2hex(random_bytes(18));
return hash_hmac('sha3-256', $random, wp_salt());
} catch (Exception $e) {
return hash_hmac('sha3-256', uniqid('', true), wp_salt());
}
}
// 自动输出Nonce字段
public static function render_nonce_field() {
$instance = new self();
printf(
'',
esc_attr($instance->nonce_key),
esc_attr($instance->generate_nonce())
);
}
// 动态哈希算法
private function get_hash_algo(): string {
$available = hash_algos();
$priority = [
'sha3-512', 'sha3-384', 'sha3-256',
'sha512', 'sha384', 'sha256',
'sha1'
];
foreach ($priority as $algo) {
if (in_array($algo, $available)) {
return $algo;
}
}
wp_die('System lacks necessary encryption algorithm support.');
// 请求指纹验证
private function validate_fingerprint(string $nonce): bool {
$expected = hash_hmac(
$this->get_hash_algo(),
$_SERVER['HTTP_USER_AGENT'] . $_SERVER['HTTP_ACCEPT_LANGUAGE'],
wp_salt(self::FINGERPRINT_SALT)
);
$client_fp = $_SERVER['HTTP_X_CLIENT_FINGERPRINT'] ?? '';
return hash_equals($expected, $client_fp);
}
// 动态Nonce生命周期
private function get_ttl(string $method): int {
$base_ttl = 300;
$method_weights = [
'GET' => 0.5,
'POST' => 1.2,
'PUT' => 1.5,
'DELETE' => 2.0
];
return (int)($base_ttl * ($method_weights[$method] ?? 1.0));
}
// 智能限流保护
private function check_rate_limit(): void {
$ip_key = 'edp_ratelimit_' . md5($_SERVER['REMOTE_ADDR']);
$global_key = 'edp_global_ratelimit';
// IP级限流
$ip_count = (int)get_transient($ip_key);
if ($ip_count > self::RATE_LIMIT) {
$this->reject_request('rate_limit_exceeded');
}
set_transient($ip_key, $ip_count + 1, 60);
// 全局限流
$global_count = (int)get_transient($global_key);
if ($global_count > (self::RATE_LIMIT * 100)) {
$this->reject_request('GLOBAL_RATE_LIMIT');
}
set_transient($global_key, $global_count + 1, 60);
}
// 智能缓存清理
public function cleanup_transients() {
global $wpdb;
$time = time() - 3600;
$transients = $wpdb->get_col(
$wpdb->prepare(
"SELECT option_name
FROM {$wpdb->options}
WHERE option_name LIKE %s
AND option_value < %d",
'_transient_edp_%',
$time
)
);
foreach ($transients as $transient) {
$key = str_replace('_transient_', '', $transient);
delete_transient($key);
}
// 预热高频端点
$hot_endpoints = [
home_url(),
rest_url('wp/v2/posts')
];
foreach ($hot_endpoints as $url) {
wp_remote_get($url, [
'headers' => ['X-Cache-Warmup' => '1']
]);
}
}
}
// 初始化保护功能
new Early_Data_Protection_Theme();
// 快捷函数
if (!function_exists('edp_nonce_field')) {
function edp_nonce_field() {
Early_Data_Protection_Theme::render_nonce_field();
}
}
}
使用说明:
REST API端点保护
所有REST API请求会自动验证nonce,开发者无需额外处理
在主题模板中插入Nonce字段
<?php edp_nonce_field(); ?>
或使用action自动注入:
// 在评论表单
add_action('comment_form', 'edp_nonce_field');
// 在WooCommerce结账页面
add_action('woocommerce_after_checkout_billing_form', 'edp_nonce_field');
AJAX请求自动处理
前端JavaScript会自动获取nonce值:
// 普通AJAX请求
$.ajax({
url: ajaxurl,
method: 'POST',
headers: {
'X-EDP-Nonce': window.edpConfig.nonce
},
// ...
});
// Fetch API
fetch(apiUrl, {
headers: {
'X-EDP-Nonce': edpConfig.nonce
}
});
前端生成指纹(需在主题 JavaScript 中实现)
function generateFingerprint() {
const data = navigator.userAgent + ',' + navigator.language;
return crypto.subtle.digest('SHA-256', new TextEncoder().encode(data))
.then(hash => {
const hex = Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
return btoa(hex);
});
}
// 在AJAX请求头中添加
fetch('/api', {
headers: {
'X-Client-Fingerprint': await generateFingerprint(),
'X-EDP-Nonce': window.edpConfig.nonce
}
});
或使用前端集成最佳实践
现代化 JavaScript 实现安全通信:
// edp-protection.js
class EDPSecurity {
static async generateNonce() {
const encoder = new TextEncoder();
const data = encoder.encode(Date.now() + Math.random().toString());
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
}
static async request(url, options = {}) {
const nonce = await this.generateNonce();
const fingerprint = await this.getFingerprint();
return fetch(url, {
...options,
headers: {
'X-EDP-Nonce': nonce,
'X-Client-Fingerprint': fingerprint,
...options.headers
}
});
}
static async getFingerprint() {
const keys = [
navigator.userAgent,
navigator.languages.join(','),
screen.width + 'x' + screen.height,
new Date().getTimezoneOffset()
];
const data = keys.join('|');
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data));
return btoa(String.fromCharCode(...new Uint8Array(hash)));
}
}
// 使用示例
EDPSecurity.request('/wp-json/wc/v3/orders', {
method: 'POST',
body: JSON.stringify(orderData)
}).then(response => {
// 处理响应
});
Nginx 配置优化:增强限流
http {
limit_req_zone $binary_remote_addr zone=edp_limit:10m rate=10r/m;
ssl_early_data on;
proxy_set_header Early-Data $ssl_early_data;
map $http_early_data $is_early_data {
default 0;
"1" 1;
}
server {
add_header Alt-Svc 'h3=":443"; ma=86400, h2=":443"; ma=86400';
location / {
limit_req zone=edp_limit burst=20 nodelay;
# 其他配置...
}
}
}
监控措施
建议在生产环境部署时配合以下监控措施:
记录所有 425 错误到独立日志文件
define('EDP_DEBUG', true);
使用 APM 工具(如 New Relic)监控验证流程性能
add_filter('edp_reject_request', function($code) {
if (extension_loaded('newrelic')) {
newrelic_add_custom_parameter('edp_error', $code);
}
return $code;
});
使用Prometheus指标监控验证流程性能
// 添加指标端点
add_action('rest_api_init', function() {
register_rest_route('edp/v1', '/metrics', [
'methods' => 'GET',
'callback' => function() {
$data = get_option('edp_metrics', []);
$output = [];
foreach ($data as $code => $count) {
$output[] = sprintf(
'edp_requests_total{code="%s"} %d',
$code,
$count
);
}
return new WP_REST_Response(implode("\n", $output), 200, [
'Content-Type' => 'text/plain; version=0.0.4'
]);
}
]);
});
实时监控面板 (通过 WordPress Admin)
// 在类中添加监控方法
public function render_monitor() {
$stats = get_option('edp_metrics', []);
?>
<div class="wrap">
<h1>0-RTT 安全监控</h1>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>指标</th>
<th>24小时计数</th>
<th>趋势</th>
</tr>
</thead>
<tbody>
<?php foreach ($stats as $code => $count): ?>
<tr>
<td><?= esc_html($code) ?></td>
<td><?= number_format($count) ?></td>
<td><div class="sparkline" data-values="<?= implode(',', $this->get_trend($code)) ?>"></div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php
}
// 注册管理菜单
add_action('admin_menu', function() {
add_menu_page(
'0-RTT 监控',
'安全监控',
'manage_options',
'edp-monitor',
[Early_Data_Protection_Theme::class, 'render_monitor'],
'dashicons-shield-alt'
);
});
回滚方案
# 快速禁用 0-RTT 的 Nginx 命令
sudo sed -i 's/ssl_early_data on;/ssl_early_data off;/g' /etc/nginx/conf.d/*.conf
sudo systemctl reload nginx
高级优化技巧
// 在类中添加会话轮换逻辑
private function rotate_session_ticket() {
if (($last_rotate = get_transient('edp_ticket_rotate')) === false) {
$this->force_ticket_rotation();
set_transient('edp_ticket_rotate', time(), 3600);
}
}
private function force_ticket_rotation() {
if (function_exists('openssl_random_pseudo_bytes')) {
$new_secret = bin2hex(openssl_random_pseudo_bytes(32));
file_put_contents(
'/etc/nginx/tls/.session_ticket.key',
$new_secret
);
exec('sudo systemctl reload nginx');
}
}
通过 WordPress 的 wp_remote_get 定期自检服务可用性
<?php
/**
* TLS 1.3 0-RTT 自检系统
* 功能:自动验证服务可用性、检测重放攻击防护、生成可视化报告
*/
class ZeroRTT_SelfCheck {
const EVENT_HOOK = 'zrtt_selfcheck_event';
const OPTION_KEY = 'zrtt_selfcheck_data';
const TEST_ENDPOINT = '/zrtt-health-check'; // 专用测试端点
private $test_cases = [
'basic' => [
'method' => 'GET',
'expect_status' => 200,
'check_headers' => ['X-Early-Data']
],
'replay' => [
'method' => 'POST',
'expect_status' => 425,
'check_content' => 'Security verification failed'
],
'nonce' => [
'method' => 'GET',
'expect_status' => 200,
'require_nonce' => true
]
];
public function __construct() {
add_action(self::EVENT_HOOK, [$this, 'run_full_check']);
add_action('rest_api_init', [$this, 'register_test_endpoint']);
// 注册每日定时任务
if (!wp_next_scheduled(self::EVENT_HOOK)) {
wp_schedule_event(time(), 'daily', self::EVENT_HOOK);
}
}
/**
* 执行完整检测流程
*/
public function run_full_check() {
$results = [];
foreach ($this->test_cases as $case => $config) {
$results[$case] = $this->run_single_test($case, $config);
}
$this->save_results($results);
$this->send_notification($results);
}
/**
* 执行单个测试用例
*/
private function run_single_test($case, $config) {
$url = home_url(self::TEST_ENDPOINT);
$args = [
'headers' => [
'Early-Data' => '1',
'X-0RTT-Test' => '1'
],
'sslverify' => false,
'timeout' => 15
];
// 生成测试 Nonce
if (!empty($config['require_nonce'])) {
$nonce = $this->generate_test_nonce();
$args['headers']['X-EDP-Nonce'] = $nonce;
}
// 重放测试需要发送相同请求两次
if ($case === 'replay') {
$response1 = wp_remote_get($url, $args);
$response2 = wp_remote_get($url, $args);
return $this->validate_replay_test($response1, $response2);
}
$response = ('GET' === $config['method'])
? wp_remote_get($url, $args)
: wp_remote_post($url, $args);
return $this->validate_response($response, $config);
}
/**
* 验证重放测试结果
*/
private function validate_replay_test($first, $second) {
$pass = true;
// 首次请求应成功
if (200 !== wp_remote_retrieve_response_code($first)) {
$pass = false;
}
// 二次请求应被拒绝
if (425 !== wp_remote_retrieve_response_code($second)) {
$pass = false;
}
return [
'pass' => $pass,
'data' => [
'first_status' => wp_remote_retrieve_response_code($first),
'second_status' => wp_remote_retrieve_response_code($second)
]
];
}
/**
* 生成测试用 Nonce
*/
private function generate_test_nonce() {
return wp_hash(wp_generate_uuid4() . microtime(), 'nonce');
}
/**
* 保存检测结果
*/
private function save_results($results) {
$history = get_option(self::OPTION_KEY, []);
$history[time()] = $results;
// 保留最近30天记录
if (count($history) > 30) {
$history = array_slice($history, -30, 30, true);
}
update_option(self::OPTION_KEY, $history);
}
/**
* 发送异常通知
*/
private function send_notification($results) {
$failed = array_filter($results, function($item) {
return !$item['pass'];
});
if (!empty($failed)) {
$message = "0-RTT 自检异常:\n";
foreach ($failed as $case => $detail) {
$message .= sprintf("- %s: %s\n",
$case,
json_encode($detail['data'])
);
}
wp_mail(
get_option('admin_email'),
'[警告] 0-RTT 服务异常',
$message
);
}
}
/**
* 注册测试端点
*/
public function register_test_endpoint() {
register_rest_route('zrtt/v1', '/health-check', [
'methods' => ['GET', 'POST'],
'callback' => function($request) {
// 模拟不同响应
if ($request->get_header('x_0rtt_test')) {
$is_replay = get_transient('zrtt_test_nonce');
if ('POST' === $request->get_method()) {
if ($is_replay) {
return new WP_Error(
'replay_detected',
'Security verification failed',
['status' => 425]
);
}
set_transient('zrtt_test_nonce', 1, 60);
return rest_ensure_response(['status' => 'ok']);
}
return rest_ensure_response([
'early_data' => $request->get_header('early_data'),
'timestamp' => time()
]);
}
return rest_ensure_response(['error' => 'invalid_request']);
},
'permission_callback' => '__return_true'
]);
}
/**
* 管理员界面
*/
public static function render_admin_page() {
$history = get_option(self::OPTION_KEY, []);
?>
<div class="wrap">
<h1>0-RTT 健康检查</h1>
<table class="wp-list-table widefat striped">
<thead>
<tr>
<th>检测时间</th>
<th>基础连接</th>
<th>重放防护</th>
<th>Nonce 验证</th>
</tr>
</thead>
<tbody>
<?php foreach ($history as $time => $results): ?>
<tr>
<td><?= date('Y-m-d H:i:s', $time) ?></td>
<td><?= $results['basic']['pass'] ? '✅' : '❌' ?></td>
<td><?= $results['replay']['pass'] ? '✅' : '❌' ?></td>
<td><?= $results['nonce']['pass'] ? '✅' : '❌' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php
}
}
// 初始化
new ZeroRTT_SelfCheck();
// 注册管理菜单
add_action('admin_menu', function() {
add_submenu_page(
'tools.php',
'0-RTT 健康检查',
'0-RTT 状态',
'manage_options',
'zrtt-check',
['ZeroRTT_SelfCheck', 'render_admin_page']
);
});
Leave a Reply
You must be logged in to post a comment.