Swoole v4.6 版本新特性之 SNI 支持

本文涉及的产品
.cn 域名,1个 12个月
简介: Swoole 在 v4.6.0 版本中对 SNI 进行了支持,这篇文章就对这个新特性进行一些演示和说明。

先来了解一下什么是 SNI 协议?

Server Name Identification 简称 SNI,是一个扩展的 TLS 计算机联网协议,用来解决一个服务器拥有多个域名的情况。

在该协议下,在握手过程开始时通过客户端告诉它正在连接的服务器的主机名称。这允许服务器在相同的 IP 地址和 TCP 端口号上呈现多个证书,并且因此允许在相同的 IP 地址上提供多个安全 HTTPS 网站(或其他任何基于 TLS 的服务),而不需要所有这些站点使用相同的证书。

那么如果一台服务器有多个虚拟主机,而且每个主机的域名不一样,使用了不一样的证书,该和哪个主机进行通信?

和 HTTP 协议用来解决服务器多域名的方案类似,HTTP 在请求头中使用 Host 字段来指定要访问的域名。

而 TLS 的做法也是添加 Host,在客户端发出 SSL 请求中的 Client Hello 阶段进行添加,这样就可以让服务器能够切换到正确的域并返回相应的证书。

在 Swoole 的 GitHub 中也有一个 Issue (#4031),想让 Swoole 的 HTTP Server 支持通过 Hostname 来配置 SSL 信息。

实际上是 Swoole 在 #3908 中已经进行了支持,不过英文网站的文档还没来及更新,所以没有找到相关说明。

下面就来演示 Swoole 如何设置 SNI:

首先先下载证书,这里直接使用 Swoole 测试的证书

wget -r -np -nd -P ./ssl_certs https://cdn.jsdelivr.net/gh/swoole/swoole-src@4.6.2/tests/include/ssl_certs/

下载完成后会存放在当前目录下的ssl_certs目录中

再来创建一个 HTTP Server

use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;
$http = new Server('0.0.0.0', 9501);
$http->on('request', function (Request $request, Response $response) {
    $response->end('Hello Swoole');
});
$http->start();

这里就需要用到一个新增的 ssl_sni_certs 选项

ssl_sni_certs的参数是一个二维数组,key 为 Hostname,value 是对应的证书配置

$http->set([
   'ssl_sni_certs' => [
       'cs.php.net' => [
           'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_cs_cert.pem',
           'ssl_key_file' => SSL_FILE_DIR . '/sni_server_cs_key.pem'
       ],
       'uk.php.net' => [
           'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_uk_cert.pem',
           'ssl_key_file' => SSL_FILE_DIR . '/sni_server_uk_key.pem'
       ],
       'us.php.net' => [
           'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_us_cert.pem',
           'ssl_key_file' => SSL_FILE_DIR . '/sni_server_us_key.pem',
       ],
   ]
]);

配置一下 Server 的 sock_type 来支持 SSL,以及对应的证书配置,就有了如下代码

use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;
define('SSL_FILE_DIR', __DIR__ . '/ssl_certs');
$http = new Server('127.0.0.1', 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$http->set([
   'log_file' => '/dev/null',
   'ssl_cert_file' => SSL_FILE_DIR . '/server.crt',
   'ssl_key_file' => SSL_FILE_DIR . '/server.key',
   'ssl_protocols' => SWOOLE_SSL_TLSv1_2 | SWOOLE_SSL_TLSv1_3 | SWOOLE_SSL_TLSv1_1 | SWOOLE_SSL_SSLv2,
   'ssl_sni_certs' => [
       'cs.php.net' => [
           'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_cs_cert.pem',
           'ssl_key_file' => SSL_FILE_DIR . '/sni_server_cs_key.pem'
       ],
       'uk.php.net' => [
           'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_uk_cert.pem',
           'ssl_key_file' => SSL_FILE_DIR . '/sni_server_uk_key.pem'
       ],
       'us.php.net' => [
           'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_us_cert.pem',
           'ssl_key_file' => SSL_FILE_DIR . '/sni_server_us_key.pem',
       ],
   ]
]);
$http->on('request', function (Request $request, Response $response) {
    $response->end('Hello Swoole');
});
$http->start();

搞个客户端来测试一下

$flags = STREAM_CLIENT_CONNECT;
$port = 9501;
$ctxArr = [
    'cafile' => SSL_FILE_DIR . '/sni_server_ca.pem',
    'capture_peer_cert' => true,
    'verify_peer' => false,
];
$ctxArr['peer_name'] = 'cs.php.net';
$ctx = stream_context_create(['ssl' => $ctxArr]);
$client = stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx);
$cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
var_dump(openssl_x509_parse($cert)['subject']['CN']);
$ctxArr['peer_name'] = 'uk.php.net';
$ctx = stream_context_create(['ssl' => $ctxArr]);
$client = stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx);
$cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
var_dump(openssl_x509_parse($cert)['subject']['CN']);
$ctxArr['peer_name'] = 'us.php.net';
$ctx = stream_context_create(['ssl' => $ctxArr]);
$client = stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx);
$cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
var_dump(openssl_x509_parse($cert)['subject']['CN']);

在测试的同时,使用 tcpdump 进行抓包


tcpdump -i lo0 port 9501 -w sni.pcap

请求成功后,客户端会输出对应的三个 Hostname


$ php swoole.php
string(10) "cs.php.net"
string(10) "uk.php.net"
string(10) "us.php.net"

然后使用 Wireshark 来分析一下抓到的包,通过ssl.handshake来过滤出想要的报文


7.1.png

分析报文发现 server_name 的扩展字段只存在于 Client Hello 这个过程中

接着使用ssl.handshake.extensions_server_name进行过滤,提取出包含 SNI 协议的 Client Hello 报文


7.2.png

就可以看到 SNI 扩展字段:


Extension: server_name (len=15)
Type: server_name (0)
Length: 15
Server Name Indication extension
Server Name list length: 13
Server Name Type: host_name (0)
Server Name length: 10
Server Name: cs.php.net

这里指定了该 TLS 握手的目标域名为 cs.php.net

通过 SNI,拥有多域名的服务器就可以正常建立 TLS 连接了。

下面是完整的测试代码:

<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;
use Swoole\Process\Manager;
use Swoole\Process\Pool;
define('SSL_FILE_DIR', __DIR__ . '/ssl_certs');
$pm = new Manager();
$pm->add(function (Pool $pool, int $workerId) {
    $http = new Server('127.0.0.1', 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL);
    $http->set([
       'log_file' => '/dev/null',
       'ssl_cert_file' => SSL_FILE_DIR . '/server.crt',
       'ssl_key_file' => SSL_FILE_DIR . '/server.key',
       'ssl_protocols' => SWOOLE_SSL_TLSv1_2 | SWOOLE_SSL_TLSv1_3 | SWOOLE_SSL_TLSv1_1 | SWOOLE_SSL_SSLv2,
       'ssl_sni_certs' => [
           'cs.php.net' => [
               'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_cs_cert.pem',
               'ssl_key_file' => SSL_FILE_DIR . '/sni_server_cs_key.pem'
           ],
           'uk.php.net' => [
               'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_uk_cert.pem',
               'ssl_key_file' => SSL_FILE_DIR . '/sni_server_uk_key.pem'
           ],
           'us.php.net' => [
               'ssl_cert_file' => SSL_FILE_DIR . '/sni_server_us_cert.pem',
               'ssl_key_file' => SSL_FILE_DIR . '/sni_server_us_key.pem',
           ],
       ]
    ]);
    $http->on('request', function (Request $request, Response $response) {
        $response->end('Hello Swoole');
    });
    $http->start();
});
$pm->add(function (Pool $pool, int $workerId) {
    $flags = STREAM_CLIENT_CONNECT;
    $port = 9501;
    $ctxArr = [
        'cafile' => SSL_FILE_DIR . '/sni_server_ca.pem',
        'capture_peer_cert' => true,
        'verify_peer' => false,
    ];
    $ctxArr['peer_name'] = 'cs.php.net';
    $ctx = stream_context_create(['ssl' => $ctxArr]);
    $client = stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx);
    $cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
    var_dump(openssl_x509_parse($cert)['subject']['CN']);
    $ctxArr['peer_name'] = 'uk.php.net';
    $ctx = stream_context_create(['ssl' => $ctxArr]);
    $client = stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx);
    $cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
    var_dump(openssl_x509_parse($cert)['subject']['CN']);
    $ctxArr['peer_name'] = 'us.php.net';
    $ctx = stream_context_create(['ssl' => $ctxArr]);
    $client = stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx);
    $cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
    var_dump(openssl_x509_parse($cert)['subject']['CN']);
    $pool->shutdown();
});
$pm->start();
目录
相关文章
|
安全 API 网络安全
Swoole v4.6.0 版本发布,支持原生 curl 协程客户端
Swoole v4.6.0 版本发布了,同样也是 2021 年的首个版本更新。 作为一个 y 版本发布,此次更新也包含了不兼容的修改以及许多的新功能
754 0
|
8月前
|
网络协议 Java API
JDK 9新特性探秘:HTTP/2支持及其性能优势
本文将深入探讨JDK 9中引入的一项重要新特性——对HTTP/2协议的原生支持。HTTP/2作为下一代互联网传输协议,相较于HTTP/1.1在性能、安全性和效率方面有着显著的提升。JDK 9通过内置HTTP/2客户端API,为Java开发者提供了更加便捷和高效的网络通信手段。本文将详细介绍HTTP/2的特性、JDK 9中HTTP/2的支持方式,以及如何在实际项目中应用这一新特性来提升网络应用的性能。
|
XML JSON 数据格式
XmlRPC协议详解(一款不支持原生异步请求的协议)
XmlRPC是一种基于XML(eXtensible Markup Language)的远程过程调用协议。它使用简单的文本格式进行通信,将请求和响应数据封装在XML中,广泛应用于Web服务和分布式系统中。
327 0
|
网络安全 Python
python接口自动化(十二)--https请求(SSL)(详解)
本来最新的requests库V2.13.0是支持https请求的,但是一般写脚本时候,我们会用抓包工具fiddler,这时候会 报:requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590) 小编环境:
868 0
python接口自动化(十二)--https请求(SSL)(详解)
Swoole v4.5.9版本发布,兼容PHP8!
PHP8 现在已经正式发布了,它引入了一些重大变更,以及许多新特性和性能优化,包括命名参数、联合类型、注解、Constructor Property Promotion、match 表达式、nullsafe 运算符、JIT,以及对类型系统、错误处理和一致性的改进。 Swoole 也在第一时间进行来兼容,可以和 PHP8 一起使用,需要在 PHP8 使用 Swoole 的小伙伴可以直接使用此版本,其他低版本可能编译失败哦。
983 0
|
Go 网络安全 API
Swoole v4.6.3 版本发布,祝大家 2021 春节快乐
v4.6.3 版本主要是一个 Bug 修复版本,没有向下不兼容改动。
120 0
|
网络协议 PHP
Swoole v4.5.1版本发布
v4.5.1,这是一个 BUG 修复版本, 补充了本应在v4.5.0引入的 System 文件函数废弃标记。
143 0
|
关系型数据库 MySQL 测试技术
Swoole v4.5.3版本发布
新增了APL,修复了一些错误
149 0
|
NoSQL 关系型数据库 MySQL
Swoole v4.4.20版本发布
v4.4.20,这是一个 BUG 修复版本, 没有任何向下不兼容改动
148 0
|
域名解析 网络协议 NoSQL
Swoole v4.7 版本预览之支持 c-ares
c-ares 是一个异步 DNS 解析库。 它适用于需要在不阻塞的情况下执行 DNS 查询或需要并行执行多个 DNS 查询的应用程序。
580 0