Linuxのkernel TLSでnginxのSSL_sendfileを試してみた
はじめに
OpenSSLのSSL_sendfileとパッチを当てたnginxでLinuxのkTLSを試してみた · hnakamur’s blog を書いてから1年半経って状況が変わっていたので再度試してみました。
9日前に SSL: SSL_sendfile() support with kernel TLS. · nginx/nginx@1fc61b7 で Linux の kernel TLS を使って sendfile するコードが nginx に入っていました。
コミットメッセージによると enable-tls オプションを有効にした OpenSSL 3.0 が必要とのことです。
検証環境
$ cat /etc/os-release | grep ^VERSION=
VERSION="20.04.3 LTS (Focal Fossa)"
$ uname -r
5.11.0-38-generic
$ uname -m
x86_64
Docker を使った検証手順を https://github.com/hnakamur/ktls_sendfile_experiment に置きました。 以下の手順でビルドします。
git clone https://github.com/hnakamur/ktls_sendfile_experiment
cd ktls_sendfile_experiment
docker build -t tks_sendfile .
手順の詳細は Dockerfile を参照してください。
OpenSSL はこの記事を書いた時の最新のコミット Remove redundant RAND_get0_private() call · openssl/openssl@a87c324 にデバッグログを追加した Use fprintf for debug log · hnakamur/openssl@4194d73 のコミットを使用しています。
nginx も同様に最新のコミット Core: removed unnecessary restriction in hash initialization. · nginx/nginx@3253b34 にデバッグログを追加した Add debug log for BIO_get_ktls_send · hnakamur/nginx@2499956 のコミットを使用しています。
OpenSSL の ktls_sendfile のテスト実行
以下のコマンドで実行できます。
docker run --rm -it ktls_sendfile test_sslapi.sh
デバッグログ出力の抜粋を以下に示します。
DEBUG_KTLS: after create_ssl_objects2, serverssl=0x563af62a1740, clientssl=0x563af62a3500, clientssl->options=0x120000
DEBUG_KTLS: SSL_set_options(serverssl, SSL_OP_ENABLE_KTLS) OK, serverssl->options=0x120108, SSL_OP_ENABLE_KTLS=0x8, serverssl=0x563af62a1740.
DEBUG_KTLS: Skip ktls for s=0x563af62a3500 because compressed or ktls disabled, s->compress=(nil), s->options=0x120000, disabled=1.
DEBUG_KTLS: s=0x563af62a3500 after s->statem.enc_write_state = ENC_WRITE_STATE_VALID.
DEBUG_KTLS: calling BIO_set_ktls s=0x563af62a1740, which=0x21, SSL3_CC_WRITE=0x2, which & SSL3_CC_WRITE=0x0
DEBUG_KTLS: s=0x563af62a1740 BIO_set_ktls(bio, &crypto_info, which & SSL3_CC_WRITE) returned true, which=0x21, SSL3_CC_WRITE=0x2, which & SSL3_CC_WRITE=0x0
DEBUG_KTLS: s=0x563af62a1740 after s->statem.enc_write_state = ENC_WRITE_STATE_VALID.
DEBUG_KTLS: calling BIO_set_ktls s=0x563af62a1740, which=0x22, SSL3_CC_WRITE=0x2, which & SSL3_CC_WRITE=0x2
DEBUG_KTLS: s=0x563af62a1740 BIO_set_ktls(bio, &crypto_info, which & SSL3_CC_WRITE) returned true, which=0x22, SSL3_CC_WRITE=0x2, which & SSL3_CC_WRITE=0x2
DEBUG_KTLS: s=0x563af62a1740 after s->statem.enc_write_state = ENC_WRITE_STATE_VALID.
DEBUG_KTLS: Skip ktls for s=0x563af62a3500 because compressed or ktls disabled, s->compress=(nil), s->options=0x120000, disabled=1.
DEBUG_KTLS: s=0x563af62a3500 after s->statem.enc_write_state = ENC_WRITE_STATE_VALID.
DEBUG_KTLS: ktls_sendfile ret=16384, s=0x563af62a1740, wfd=5, fd=3, offset=0, size=16384, flags=0
DEBUG_KTLS: ktls_sendfile ret=16384, s=0x563af62a1740, wfd=5, fd=3, offset=16384, size=16384, flags=0
DEBUG_KTLS: ktls_sendfile ret=16384, s=0x563af62a1740, wfd=5, fd=3, offset=32768, size=16384, flags=0
DEBUG_KTLS: ktls_sendfile ret=16384, s=0x563af62a1740, wfd=5, fd=3, offset=49152, size=16384, flags=0
execute_test_ktls_sendfile 関数内で
create_ssl_objects2 関数を呼び出して serverssl
と clientssl
を作成しています。
その後 SSL_set_options(serverssl, SSL_OP_ENABLE_KTLS) を実行して serverssl
で KTLS を有効にしています。
tls1_change_cipher_state 関数内で
if (s->compress || (s->options & SSL_OP_ENABLE_KTLS) == 0)
という条件判定がありますがで clientssl
のほうは KTLS を有効にしていないのでその下の goto skip_ktls;
で
skip_tls: ラベルに飛びます。
serverssl のほうは BIO_set_ktls を呼び出しています。
その後
SSL_sendfile 関数 内で
ret = ktls_sendfile(SSL_get_wfd(s), fd, offset, size, flags); として ktls_sendfile
関数を呼んでいます。
ktls_sendfile
は Linux 用の実装が include/internal/ktls.h#L332-L335、FreeBSD用の実装が include/internal/ktls.h#L188-L198 にあります。
どちらもほぼ OS の sendfile を呼んでいるだけです。 Linux と FreeBSD でインタフェースを合わせるためにラップしているということのようです。 Linux のほうは frags 引数が無視されるとコメントに書いてありました。
OpenSSL の s_server と curl での検証
以下のコマンドで openssl s_server
を起動します。
docker run --rm -it ktls_sendfile run_s_server.sh
openssl s_server -WWW -cert /work/example.com.crt -key /work/example.com.key -accept 443 -ktls -sendfile
のように起動しています。
別の端末を開いて以下のように curl を実行します。
docker exec -it $(docker ps -q) curl -sSkv -o /dev/null https://localhost/index.html
openssl s_server
側の出力は以下のようになり ktls_sendfile
が呼ばれていることがわかります。
Using default temp DH parameters
ACCEPT
FILE:index.html
DEBUG_KTLS: ktls_sendfile ret=615, s=0x56490fd4b870, wfd=4, fd=5, offset=0, size=615, flags=0
KTLS SENDFILE 'index.html' OK
curl の出力には以下の行があり、 TLSv1.3 で接続したことがわかります。
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
nginx と curl での検証
以下のコマンドで nginx を起動します。
docker run --rm -it ktls_sendfile
nginx のコマンドラインは Dockerfile 内の CMD の行 で指定していて、シェルからの実行形式だと
/usr/local/nginx/sbin/nginx -g 'daemon off;'
です。
別の端末を開いて以下のように curl を実行します。
docker exec -it $(docker ps -q) curl -sSkv -o /dev/null https://localhost
nginx の標準エラー出力の抜粋を以下に示します。
nginx と OpenSSL に追加したデバッグログで nginx 側の BIO_get_ktls_send
関数呼び出しで OpenSSL 側では ktls_sendfile
が呼ばれていることが確認できました。
2021/10/30 17:07:06 [notice] 7#0: *1 ngx_ssl_handshake: calling BIO_get_ktls_send() while SSL handshaking, client: 127.0.0.1, server: 0.0.0.0:443
2021/10/30 17:07:06 [notice] 7#0: *1 ngx_ssl_handshake: BIO_get_ktls_send(): 1 while SSL handshaking, client: 127.0.0.1, server: 0.0.0.0:443
DEBUG_KTLS: ktls_sendfile ret=615, s=0x55ef95f596c0, wfd=3, fd=10, offset=0, size=615, flags=0
また curl の出力には以下の行があり TLSv1.3 で接続したことがわかります。
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384