首页
下载
文档
问答社区
视频
捐赠
源代码
AI 助理
赞助商
CRMEB
Apipost
腾讯云
微擎
禅道
51Talk
商业产品
Swoole AI 智能文档翻译器
Swoole-Compiler PHP 代码加密器
CRMEB 新零售社交电商系统
登录
注册
全部
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
发表新帖
Swoole v4.6 版本新特性之 SNI 支持
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](https://github.com/swoole/swoole-src/issues/4031)),想让 Swoole 的 HTTP Server 支持通过 Hostname 来配置 SSL 信息。 实际上是 Swoole 在 [#3908](https://github.com/swoole/swoole-src/pull/3908) 中已经进行了支持,不过英文网站的文档还没来及更新,所以没有找到相关说明。 下面就来演示 Swoole 如何设置 SNI: 首先先下载证书,这里直接使用 Swoole 测试的证书 ```bash 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 ```php 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](https://wiki.swoole.com/#/server/setting?id=ssl_sni_certs) 选项 `ssl_sni_certs`的参数是一个二维数组,key 为 Hostname,value 是对应的证书配置 ```php $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,以及对应的证书配置,就有了如下代码 ```php 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(); ``` 搞个客户端来测试一下 ```php $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 进行抓包 ```bash tcpdump -i lo0 port 9501 -w sni.pcap ``` 请求成功后,客户端会输出对应的三个 Hostname ```bash $ php swoole.php string(10) "cs.php.net" string(10) "uk.php.net" string(10) "us.php.net" ``` 然后使用 Wireshark 来分析一下抓到的包,通过`ssl.handshake`来过滤出想要的报文  分析报文发现 server_name 的扩展字段只存在于 Client Hello 这个过程中 接着使用`ssl.handshake.extensions_server_name`进行过滤,提取出包含 SNI 协议的 Client Hello 报文  就可以看到 SNI 扩展字段: ```txt 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 <?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(); ``` 
发布于4年前 · 9 次浏览 · 来自
分享
鲁飞
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](https://github.com/swoole/swoole-src/issues/4031)),想让 Swoole 的 HTTP Server 支持通过 Hostname 来配置 SSL 信息。 实际上是 Swoole 在 [#3908](https://github.com/swoole/swoole-src/pull/3908) 中已经进行了支持,不过英文网站的文档还没来及更新,所以没有找到相关说明。 下面就来演示 Swoole 如何设置 SNI: 首先先下载证书,这里直接使用 Swoole 测试的证书 ```bash 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 ```php 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](https://wiki.swoole.com/#/server/setting?id=ssl_sni_certs) 选项 `ssl_sni_certs`的参数是一个二维数组,key 为 Hostname,value 是对应的证书配置 ```php $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,以及对应的证书配置,就有了如下代码 ```php 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(); ``` 搞个客户端来测试一下 ```php $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 进行抓包 ```bash tcpdump -i lo0 port 9501 -w sni.pcap ``` 请求成功后,客户端会输出对应的三个 Hostname ```bash $ php swoole.php string(10) "cs.php.net" string(10) "uk.php.net" string(10) "us.php.net" ``` 然后使用 Wireshark 来分析一下抓到的包,通过`ssl.handshake`来过滤出想要的报文  分析报文发现 server_name 的扩展字段只存在于 Client Hello 这个过程中 接着使用`ssl.handshake.extensions_server_name`进行过滤,提取出包含 SNI 协议的 Client Hello 报文  就可以看到 SNI 扩展字段: ```txt 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 <?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(); ``` 
赞
0
分享
收藏
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
评论
还没有评论!
微信公众号
热门内容
作者其它话题
- thinkphp5.1在使用think-swoole的时候报错unsupported option [host]
- WSL 下服务器响应数据过大无法接收
暂无回复的问答
- CodeGalaxy K3s 轻量集群节点之间如何实现负载均衡
- 关于openssl CURL WARNING swSSL_connect: SSL_connect(fd=69) failed. Error: error:141A318A:SSL routines:tls_process_ske_dhe:dh key too small[1|394]
- 多个模型如何进行事务异常回退?
- websocket开启wss报错
- 协程tcp服务器如何使用多进程?recv()方法接收信息,打印出来的pid一直是同一个。没用使用到多进程啊。