Swoole在v4.6.0版本中支持SNI。本文将演示和解释这一新功能。我们先来了解一下什么是SNI协议?ServerNameIdentification,简称SNI,是一种扩展的TLS计算机网络协议,用于解决一个服务器有多个域名的情况。在此协议下,在握手过程开始时,客户端告诉它要连接的服务器的主机名。这允许服务器在同一个IP地址和TCP端口号上提供多个证书,从而允许多个安全HTTPS网站(或任何其他基于TLS的服务)在同一个IP地址上提供服务,而不需要它们都使用相同的证书。那么如果一台服务器有多个虚拟主机,每个主机有不同的域名,使用不同的证书,应该和哪个主机通信呢?与HTTP协议在服务器上解决多个域名的解决方案类似,HTTP通过请求头中的Host字段来指定要访问的域名。TLS的做法也是添加一个Host,在客户端发送的SSL请求的ClientHello阶段添加,以便服务器切换到正确的域并返回相应的证书。Swoole的GitHub中也有一个Issue(#4031),希望Swoole的HTTPServer支持通过Hostname配置SSL信息。其实在#3908已经支持Swoole了,但是英文网站的文档还没有更新,所以没有找到相关的说明。下面演示一下Swoole如何设置SNI:首先下载证书,这里直接使用Swoole测试过的证书wget-r-np-nd-P./ssl_certshttps://cdn.jsdelivr.net/gh/swoole/swoole-src@4.6.2/tests/include/ssl_certs/下载完成后会存放在当前目录下的ssl_certs目录下,然后创建一个HTTPServeruseSwoole\Http\Request;useSwoole\Http\Response;useSwoole\Http\Server;$http=newServer('0.0.0.0',9501);$http->on('request',function(Request$request,Response$response){$response->end('你好Swoole');});$http->开始();这里需要使用一个新的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和对应的证书配置。使用以下代码使用Swoole\Http\Request;useSwoole\Http\Response;useSwoole\Http\Server;define('SSL_FILE_DIR',__DIR__.'/ssl_certs');$http=newServer('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.SWOOLE_SSL_SSLv2,'ssl_sni_certs'=>['cs.php.net'=>['serverssl_cert_file'=>SSL_FILE.per.'/cni_'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('HelloSwoole');});$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_cre吃(['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。网';$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-ilo0port9501-wsni.pcap请求成功后,客户端会输出三个对应的Hostname$phpswoole.phpstring(10)"cs.php.net"string(10)"uk.php.net"string(10)"us.php.net"然后用Wireshark分析抓包,通过ssl.handshake过滤出想要的包,分析包发现只有server_name的扩展字段存在于ClientHello进程中然后使用ssl.handshake.extensions_server_name进行过滤,提取包含SNI协议的ClientHello报文可以看到SNI扩展字段:Extension:server_name(len=15)Type:server_name(0)Length:15服务器NameIndicationextensionServerNamelistlength:13ServerNameType:host_name(0)ServerNamelength:10ServerName:cs.php.net这里通过SNI指定TLS握手的目标域名为cs.php.net,一个server与多个域名可以正常建立TLS连接。下面是完整的测试代码:add(function(Pool$pool,int$workerId){$http=newServer('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_FILE'DSSL_FILE_Cert.pem.'/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('HelloSwoole');});$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_certif冰'];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();
