From 38ac7de61e8b206f8140fd6ec46b3aad0663578d Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 28 May 2021 18:16:23 +0300 Subject: Version bump. --- docs/changes.xml | 29 +++++++++++++++++++++++++++++ version | 4 ++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 3707194e..51a519f0 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -5,6 +5,35 @@ + + + + +NGINX Unit updated to 1.25.0. + + + + + + + + + + + diff --git a/version b/version index 4483727a..b8cd3458 100644 --- a/version +++ b/version @@ -1,5 +1,5 @@ # Copyright (C) NGINX, Inc. -NXT_VERSION=1.24.0 -NXT_VERNUM=12400 +NXT_VERSION=1.25.0 +NXT_VERNUM=12500 -- cgit From fd3558456edc752d43b58a0c0e1b99e5a45edae9 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Wed, 2 Jun 2021 16:14:22 +0300 Subject: Node.js: packaging new loader.js and loader.mjs. The files loader.js and loader.mjs (introduced in f85b85094541 and 3c551b9721df) were added to the packaged files list. --- src/nodejs/unit-http/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nodejs/unit-http/package.json b/src/nodejs/unit-http/package.json index 81f06bd4..f0afe41c 100644 --- a/src/nodejs/unit-http/package.json +++ b/src/nodejs/unit-http/package.json @@ -10,6 +10,8 @@ "unit.cpp", "http.js", "http_server.js", + "loader.js", + "loader.mjs", "nxt_napi.h", "package.json", "socket.js", -- cgit From 1e3f7808b1091d76b80369a93f0361453247f368 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 15 Jun 2021 10:35:15 +0300 Subject: Node.js: improving and test packaging. The patch removes the "files" section from package.json to avoid future issues with missing files. For package testing purposes, 'npm pack' is used instead of plain 'tar' to simulate packaging more accurately. --- auto/modules/nodejs | 7 +++++-- src/nodejs/unit-http/package.json | 22 ---------------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/auto/modules/nodejs b/auto/modules/nodejs index b2309143..7d4f8581 100644 --- a/auto/modules/nodejs +++ b/auto/modules/nodejs @@ -157,6 +157,7 @@ ${NXT_NODE}-copy: ${NXT_NODE_VERSION_FILE} ${NXT_NODE_PACKAGE_FILE} mkdir -p ${NXT_NODE_TMP} cp -rp src/nodejs/unit-http/* ${NXT_NODE_TMP}/ cp -p ${NXT_NODE_VERSION_FILE} ${NXT_NODE_PACKAGE_FILE} ${NXT_NODE_TMP}/ + rm -f ${NXT_NODE_TMP}/binding_pub.gyp ${NXT_NODE}-copy-g: ${NXT_NODE_VERSION_FILE} ${NXT_NODE_PACKAGE_FILE} mkdir -p ${NXT_NODE_TMP_G} @@ -174,10 +175,12 @@ ${NXT_NODE_PACKAGE_FILE}: ${NXT_VERSION_H} src/nodejs/unit-http/package.json src/nodejs/unit-http/package.json > ${NXT_NODE_PACKAGE_FILE} ${NXT_NODE_TARBALL}: ${NXT_NODE}-copy - tar -zcvf ${NXT_NODE_TARBALL} -C ${NXT_NODE_TMP} . + cd ${NXT_NODE_TMP} && npm pack + mv ${NXT_NODE_TMP}/unit-http-\$(NXT_VERSION).tgz ${NXT_NODE_TARBALL} ${NXT_NODE_TARBALL_G}: ${NXT_NODE}-copy-g - tar -zcvf ${NXT_NODE_TARBALL_G} -C ${NXT_NODE_TMP_G} . + cd ${NXT_NODE_TMP_G} && npm pack + mv ${NXT_NODE_TMP_G}/unit-http-\$(NXT_VERSION).tgz ${NXT_NODE_TARBALL_G} install: ${NXT_NODE}-$NXT_NODE_INSTALL diff --git a/src/nodejs/unit-http/package.json b/src/nodejs/unit-http/package.json index f0afe41c..debbd492 100644 --- a/src/nodejs/unit-http/package.json +++ b/src/nodejs/unit-http/package.json @@ -3,28 +3,6 @@ "version": "%%VERSION%%", "description": "HTTP module for NGINX Unit", "main": "http.js", - "files": [ - "unit.h", - "version.h", - "addon.cpp", - "unit.cpp", - "http.js", - "http_server.js", - "loader.js", - "loader.mjs", - "nxt_napi.h", - "package.json", - "socket.js", - "binding.gyp", - "README.md", - "websocket.js", - "websocket_connection.js", - "websocket_frame.js", - "websocket_request.js", - "websocket_router.js", - "websocket_router_request.js", - "websocket_server.js" - ], "scripts": { "clean": "node-gyp clean", "configure": "node-gyp configure", -- cgit From 72420358be128640dd09a0914a8d88b50c152545 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Thu, 24 Jun 2021 04:01:15 +0100 Subject: Tests: chroot test with permissions skipped under root. --- test/test_share_chroot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_share_chroot.py b/test/test_share_chroot.py index 7e53d3f7..02b3657d 100644 --- a/test/test_share_chroot.py +++ b/test/test_share_chroot.py @@ -44,7 +44,10 @@ class TestShareChroot(TestApplicationProto): assert self.get(url='/index.html')['status'] == 403, 'chroot 403 2' assert self.get(url='/file')['status'] == 403, 'chroot 403' - def test_share_chroot_permission(self, temp_dir): + def test_share_chroot_permission(self, is_su, temp_dir): + if is_su: + pytest.skip('does\'t work under root') + os.chmod(temp_dir + '/assets/dir', 0o100) assert 'success' in self.conf( -- cgit From b86891c4ef848a2da05abd1350af5f0b8e4335fa Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 28 Jun 2021 22:05:40 +0100 Subject: Tests: renamed share to static. Also minor style changes. --- test/conftest.py | 2 +- test/test_configuration.py | 3 +- test/test_node_es_modules.py | 3 +- test/test_share_chroot.py | 111 ---------------------------- test/test_share_fallback.py | 148 ------------------------------------- test/test_share_mount.py | 142 ------------------------------------ test/test_share_symlink.py | 96 ------------------------ test/test_share_types.py | 170 ------------------------------------------- test/test_static_chroot.py | 108 +++++++++++++++++++++++++++ test/test_static_fallback.py | 149 +++++++++++++++++++++++++++++++++++++ test/test_static_mount.py | 140 +++++++++++++++++++++++++++++++++++ test/test_static_symlink.py | 94 ++++++++++++++++++++++++ test/test_static_types.py | 169 ++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 665 insertions(+), 670 deletions(-) delete mode 100644 test/test_share_chroot.py delete mode 100644 test/test_share_fallback.py delete mode 100644 test/test_share_mount.py delete mode 100644 test/test_share_symlink.py delete mode 100644 test/test_share_types.py create mode 100644 test/test_static_chroot.py create mode 100644 test/test_static_fallback.py create mode 100644 test/test_static_mount.py create mode 100644 test/test_static_symlink.py create mode 100644 test/test_static_types.py diff --git a/test/conftest.py b/test/conftest.py index 5ea4e49d..db34984f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -22,8 +22,8 @@ from unit.check.node import check_node from unit.check.regex import check_regex from unit.check.tls import check_openssl from unit.http import TestHTTP -from unit.option import option from unit.log import Log +from unit.option import option from unit.utils import public_dir from unit.utils import waitforfiles diff --git a/test/test_configuration.py b/test/test_configuration.py index 880aef6c..c149658c 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,6 +1,7 @@ +import socket + import pytest -import socket from unit.control import TestControl diff --git a/test/test_node_es_modules.py b/test/test_node_es_modules.py index 0945a967..5464d4a6 100644 --- a/test/test_node_es_modules.py +++ b/test/test_node_es_modules.py @@ -1,6 +1,7 @@ +from distutils.version import LooseVersion + import pytest -from distutils.version import LooseVersion from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket diff --git a/test/test_share_chroot.py b/test/test_share_chroot.py deleted file mode 100644 index 02b3657d..00000000 --- a/test/test_share_chroot.py +++ /dev/null @@ -1,111 +0,0 @@ -import os -from pathlib import Path - -import pytest - -from unit.applications.proto import TestApplicationProto - - -class TestShareChroot(TestApplicationProto): - prerequisites = {'features': ['chroot']} - - @pytest.fixture(autouse=True) - def setup_method_fixture(self, temp_dir): - os.makedirs(temp_dir + '/assets/dir') - with open(temp_dir + '/assets/index.html', 'w') as index, open( - temp_dir + '/assets/dir/file', 'w' - ) as file: - index.write('0123456789') - file.write('blah') - - test = Path(__file__) - self.test_path = '/' + test.parent.name + '/' + test.name - - self._load_conf( - { - "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], - } - ) - - def test_share_chroot(self, temp_dir): - assert self.get(url='/dir/file')['status'] == 200, 'default chroot' - assert self.get(url='/index.html')['status'] == 200, 'default chroot 2' - - assert 'success' in self.conf( - { - "share": temp_dir + "/assets", - "chroot": temp_dir + "/assets/dir", - }, - 'routes/0/action', - ), 'configure chroot' - - assert self.get(url='/dir/file')['status'] == 200, 'chroot' - assert self.get(url='/index.html')['status'] == 403, 'chroot 403 2' - assert self.get(url='/file')['status'] == 403, 'chroot 403' - - def test_share_chroot_permission(self, is_su, temp_dir): - if is_su: - pytest.skip('does\'t work under root') - - os.chmod(temp_dir + '/assets/dir', 0o100) - - assert 'success' in self.conf( - { - "share": temp_dir + "/assets", - "chroot": temp_dir + "/assets/dir", - }, - 'routes/0/action', - ), 'configure chroot' - - assert self.get(url='/dir/file')['status'] == 200, 'chroot' - - def test_share_chroot_empty(self, temp_dir): - assert 'success' in self.conf( - {"share": temp_dir + "/assets", "chroot": ""}, 'routes/0/action', - ), 'configure chroot empty absolute' - - assert ( - self.get(url='/dir/file')['status'] == 200 - ), 'chroot empty absolute' - - assert 'success' in self.conf( - {"share": ".", "chroot": ""}, 'routes/0/action', - ), 'configure chroot empty relative' - - assert ( - self.get(url=self.test_path)['status'] == 200 - ), 'chroot empty relative' - - def test_share_chroot_relative(self, is_su, temp_dir): - if is_su: - pytest.skip('does\'t work under root') - - assert 'success' in self.conf( - {"share": temp_dir + "/assets", "chroot": "."}, 'routes/0/action', - ), 'configure relative chroot' - - assert self.get(url='/dir/file')['status'] == 403, 'relative chroot' - - assert 'success' in self.conf( - {"share": "."}, 'routes/0/action', - ), 'configure relative share' - - assert self.get(url=self.test_path)['status'] == 200, 'relative share' - - assert 'success' in self.conf( - {"share": ".", "chroot": "."}, 'routes/0/action', - ), 'configure relative' - - assert self.get(url=self.test_path)['status'] == 200, 'relative' - - def test_share_chroot_invalid(self, temp_dir): - assert 'error' in self.conf( - {"share": temp_dir, "chroot": True}, 'routes/0/action', - ), 'configure chroot error' - assert 'error' in self.conf( - {"share": temp_dir, "symlinks": "True"}, 'routes/0/action', - ), 'configure symlink error' - assert 'error' in self.conf( - {"share": temp_dir, "mount": "True"}, 'routes/0/action', - ), 'configure mount error' diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py deleted file mode 100644 index 0b1c270e..00000000 --- a/test/test_share_fallback.py +++ /dev/null @@ -1,148 +0,0 @@ -import os - -import pytest - -from unit.applications.proto import TestApplicationProto -from unit.option import option - - -class TestStatic(TestApplicationProto): - prerequisites = {} - - def setup_method(self): - os.makedirs(option.temp_dir + '/assets/dir') - with open(option.temp_dir + '/assets/index.html', 'w') as index: - index.write('0123456789') - - os.makedirs(option.temp_dir + '/assets/403') - os.chmod(option.temp_dir + '/assets/403', 0o000) - - self._load_conf( - { - "listeners": { - "*:7080": {"pass": "routes"}, - "*:7081": {"pass": "routes"}, - }, - "routes": [{"action": {"share": option.temp_dir + "/assets"}}], - "applications": {}, - } - ) - - def teardown_method(self): - try: - os.chmod(option.temp_dir + '/assets/403', 0o777) - except FileNotFoundError: - pass - - def action_update(self, conf): - assert 'success' in self.conf(conf, 'routes/0/action') - - def test_fallback(self): - self.action_update({"share": "/blah"}) - assert self.get()['status'] == 404, 'bad path no fallback' - - self.action_update({"share": "/blah", "fallback": {"return": 200}}) - - resp = self.get() - assert resp['status'] == 200, 'bad path fallback status' - assert resp['body'] == '', 'bad path fallback' - - def test_fallback_valid_path(self, temp_dir): - self.action_update( - {"share": temp_dir + "/assets", "fallback": {"return": 200}} - ) - resp = self.get() - assert resp['status'] == 200, 'fallback status' - assert resp['body'] == '0123456789', 'fallback' - - resp = self.get(url='/403/') - assert resp['status'] == 200, 'fallback status 403' - assert resp['body'] == '', 'fallback 403' - - resp = self.post() - assert resp['status'] == 200, 'fallback status 405' - assert resp['body'] == '', 'fallback 405' - - assert self.get(url='/dir')['status'] == 301, 'fallback status 301' - - def test_fallback_nested(self): - self.action_update( - { - "share": "/blah", - "fallback": { - "share": "/blah/blah", - "fallback": {"return": 200}, - }, - } - ) - - resp = self.get() - assert resp['status'] == 200, 'fallback nested status' - assert resp['body'] == '', 'fallback nested' - - def test_fallback_share(self, temp_dir): - self.action_update( - {"share": "/blah", "fallback": {"share": temp_dir + "/assets"},} - ) - - resp = self.get() - assert resp['status'] == 200, 'fallback share status' - assert resp['body'] == '0123456789', 'fallback share' - - resp = self.head() - assert resp['status'] == 200, 'fallback share status HEAD' - assert resp['body'] == '', 'fallback share HEAD' - - assert ( - self.get(url='/dir')['status'] == 301 - ), 'fallback share status 301' - - def test_fallback_proxy(self): - assert 'success' in self.conf( - [ - { - "match": {"destination": "*:7081"}, - "action": {"return": 200}, - }, - { - "action": { - "share": "/blah", - "fallback": {"proxy": "http://127.0.0.1:7081"}, - } - }, - ], - 'routes', - ), 'configure fallback proxy route' - - resp = self.get() - assert resp['status'] == 200, 'fallback proxy status' - assert resp['body'] == '', 'fallback proxy' - - @pytest.mark.skip('not yet') - def test_fallback_proxy_loop(self, skip_alert): - skip_alert( - r'open.*/blah/index.html.*failed', - r'accept.*failed', - r'socket.*failed', - r'new connections are not accepted', - ) - - self.action_update( - {"share": "/blah", "fallback": {"proxy": "http://127.0.0.1:7080"}} - ) - self.get(no_recv=True) - - assert 'success' in self.conf_delete('listeners/*:7081') - self.get(read_timeout=1) - - def test_fallback_invalid(self): - def check_error(conf): - assert 'error' in self.conf(conf, 'routes/0/action') - - check_error({"share": "/blah", "fallback": {}}) - check_error({"share": "/blah", "fallback": ""}) - check_error({"return": 200, "fallback": {"share": "/blah"}}) - check_error( - {"proxy": "http://127.0.0.1:7081", "fallback": {"share": "/blah"}} - ) - check_error({"fallback": {"share": "/blah"}}) diff --git a/test/test_share_mount.py b/test/test_share_mount.py deleted file mode 100644 index f22fbe75..00000000 --- a/test/test_share_mount.py +++ /dev/null @@ -1,142 +0,0 @@ -import os -import subprocess - -import pytest - -from unit.applications.proto import TestApplicationProto - - -class TestShareMount(TestApplicationProto): - prerequisites = {'features': ['chroot']} - - @pytest.fixture(autouse=True) - def setup_method_fixture(self, is_su, temp_dir): - if not is_su: - pytest.skip('requires root') - - os.makedirs(temp_dir + '/assets/dir/mount') - os.makedirs(temp_dir + '/assets/dir/dir') - os.makedirs(temp_dir + '/assets/mount') - with open(temp_dir + '/assets/index.html', 'w') as index, open( - temp_dir + '/assets/dir/dir/file', 'w' - ) as file, open(temp_dir + '/assets/mount/index.html', 'w') as mount: - index.write('index') - file.write('file') - mount.write('mount') - - try: - process = subprocess.Popen( - [ - "mount", - "--bind", - temp_dir + "/assets/mount", - temp_dir + "/assets/dir/mount", - ], - stderr=subprocess.STDOUT, - ) - - process.communicate() - - except KeyboardInterrupt: - raise - - except: - pytest.fail('Can\'t run mount process.') - - self._load_conf( - { - "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets/dir"}}], - } - ) - - yield - - try: - process = subprocess.Popen( - ["umount", "--lazy", temp_dir + "/assets/dir/mount"], - stderr=subprocess.STDOUT, - ) - - process.communicate() - - except KeyboardInterrupt: - raise - - except: - pytest.fail('Can\'t run umount process.') - - def test_share_mount(self, temp_dir, skip_alert): - skip_alert(r'opening.*failed') - - resp = self.get(url='/mount/') - assert resp['status'] == 200 - assert resp['body'] == 'mount' - - assert 'success' in self.conf( - {"share": temp_dir + "/assets/dir", "traverse_mounts": False}, - 'routes/0/action', - ), 'configure mount disable' - - assert self.get(url='/mount/')['status'] == 403 - - assert 'success' in self.conf( - {"share": temp_dir + "/assets/dir", "traverse_mounts": True}, - 'routes/0/action', - ), 'configure mount enable' - - resp = self.get(url='/mount/') - assert resp['status'] == 200 - assert resp['body'] == 'mount' - - def test_share_mount_two_blocks(self, temp_dir, skip_alert): - skip_alert(r'opening.*failed') - - os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') - - assert 'success' in self.conf( - [ - { - "match": {"method": "HEAD"}, - "action": { - "share": temp_dir + "/assets/dir", - "traverse_mounts": False, - }, - }, - { - "match": {"method": "GET"}, - "action": { - "share": temp_dir + "/assets/dir", - "traverse_mounts": True, - }, - }, - ], - 'routes', - ), 'configure two options' - - assert self.get(url='/mount/')['status'] == 200, 'block enabled' - assert self.head(url='/mount/')['status'] == 403, 'block disabled' - - def test_share_mount_chroot(self, temp_dir, skip_alert): - skip_alert(r'opening.*failed') - - assert 'success' in self.conf( - { - "share": temp_dir + "/assets/dir", - "chroot": temp_dir + "/assets", - }, - 'routes/0/action', - ), 'configure chroot mount default' - - assert self.get(url='/mount/')['status'] == 200, 'chroot' - - assert 'success' in self.conf( - { - "share": temp_dir + "/assets/dir", - "chroot": temp_dir + "/assets", - "traverse_mounts": False, - }, - 'routes/0/action', - ), 'configure chroot mount disable' - - assert self.get(url='/mount/')['status'] == 403, 'chroot mount' diff --git a/test/test_share_symlink.py b/test/test_share_symlink.py deleted file mode 100644 index 3970b605..00000000 --- a/test/test_share_symlink.py +++ /dev/null @@ -1,96 +0,0 @@ -import os - -import pytest - -from unit.applications.proto import TestApplicationProto - - -class TestShareSymlink(TestApplicationProto): - prerequisites = {'features': ['chroot']} - - @pytest.fixture(autouse=True) - def setup_method_fixture(self, temp_dir): - os.makedirs(temp_dir + '/assets/dir/dir') - with open(temp_dir + '/assets/index.html', 'w') as index, open( - temp_dir + '/assets/dir/file', 'w' - ) as file: - index.write('0123456789') - file.write('blah') - - self._load_conf( - { - "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], - } - ) - - def test_share_symlink(self, temp_dir, skip_alert): - skip_alert(r'opening.*failed') - - os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') - - assert self.get(url='/dir')['status'] == 301, 'dir' - assert self.get(url='/dir/file')['status'] == 200, 'file' - assert self.get(url='/link')['status'] == 301, 'symlink dir' - assert self.get(url='/link/file')['status'] == 200, 'symlink file' - - assert 'success' in self.conf( - {"share": temp_dir + "/assets", "follow_symlinks": False}, - 'routes/0/action', - ), 'configure symlink disable' - - assert self.get(url='/link/file')['status'] == 403, 'symlink disabled' - - assert 'success' in self.conf( - {"share": temp_dir + "/assets", "follow_symlinks": True}, - 'routes/0/action', - ), 'configure symlink enable' - - assert self.get(url='/link/file')['status'] == 200, 'symlink enabled' - - def test_share_symlink_two_blocks(self, temp_dir, skip_alert): - skip_alert(r'opening.*failed') - - os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') - - assert 'success' in self.conf( - [ - { - "match": {"method": "HEAD"}, - "action": { - "share": temp_dir + "/assets", - "follow_symlinks": False, - }, - }, - { - "match": {"method": "GET"}, - "action": { - "share": temp_dir + "/assets", - "follow_symlinks": True, - }, - }, - ], - 'routes', - ), 'configure two options' - - assert self.get(url='/link/file')['status'] == 200, 'block enabled' - assert self.head(url='/link/file')['status'] == 403, 'block disabled' - - def test_share_symlink_chroot(self, temp_dir, skip_alert): - skip_alert(r'opening.*failed') - - os.symlink( - temp_dir + '/assets/dir/file', temp_dir + '/assets/dir/dir/link' - ) - - assert self.get(url='/dir/dir/link')['status'] == 200, 'default chroot' - - assert 'success' in self.conf( - { - "share": temp_dir + "/assets", - "chroot": temp_dir + "/assets/dir/dir", - }, - 'routes/0/action', - ), 'configure chroot' - - assert self.get(url='/dir/dir/link')['status'] == 404, 'chroot' diff --git a/test/test_share_types.py b/test/test_share_types.py deleted file mode 100644 index b5ed97a0..00000000 --- a/test/test_share_types.py +++ /dev/null @@ -1,170 +0,0 @@ -import os -from pathlib import Path - -import pytest -from unit.applications.proto import TestApplicationProto -from unit.option import option - - -class TestShareTypes(TestApplicationProto): - prerequisites = {} - - @pytest.fixture(autouse=True) - def setup_method_fixture(self, temp_dir): - Path(temp_dir + '/assets').mkdir() - for ext in ['.xml', '.mp4', '.php', '', '.txt', '.html', '.png']: - Path(temp_dir + '/assets/file' + ext).write_text(ext) - - Path(temp_dir + '/assets/index.html').write_text('index') - - self._load_conf( - { - "listeners": { - "*:7080": {"pass": "routes"}, - "*:7081": {"pass": "routes"}, - }, - "routes": [{"action": {"share": temp_dir + "/assets"}}], - "applications": {}, - } - ) - - def action_update(self, conf): - assert 'success' in self.conf(conf, 'routes/0/action') - - def check_body(self, http_url, body): - resp = self.get(url=http_url) - assert resp['status'] == 200, 'status' - assert resp['body'] == body, 'body' - - def test_share_types_basic(self, temp_dir): - self.action_update({"share": temp_dir + "/assets"}) - self.check_body('/index.html', 'index') - self.check_body('/file.xml', '.xml') - - self.action_update( - {"share": temp_dir + "/assets", "types": "application/xml"} - ) - self.check_body('/file.xml', '.xml') - - self.action_update( - {"share": temp_dir + "/assets", "types": ["application/xml"]} - ) - self.check_body('/file.xml', '.xml') - - self.action_update({"share": temp_dir + "/assets", "types": [""]}) - assert self.get(url='/file.xml')['status'] == 403, 'no mtype' - - def test_share_types_wildcard(self, temp_dir): - self.action_update( - {"share": temp_dir + "/assets", "types": ["application/*"]} - ) - self.check_body('/file.xml', '.xml') - assert self.get(url='/file.mp4')['status'] == 403, 'app * mtype mp4' - - self.action_update( - {"share": temp_dir + "/assets", "types": ["video/*"]} - ) - assert self.get(url='/file.xml')['status'] == 403, 'video * mtype xml' - self.check_body('/file.mp4', '.mp4') - - def test_share_types_negation(self, temp_dir): - self.action_update( - {"share": temp_dir + "/assets", "types": ["!application/xml"]} - ) - assert self.get(url='/file.xml')['status'] == 403, 'forbidden negation' - self.check_body('/file.mp4', '.mp4') - - # sorting negation - self.action_update( - { - "share": temp_dir + "/assets", - "types": ["!video/*", "image/png", "!image/jpg"], - } - ) - assert self.get(url='/file.mp4')['status'] == 403, 'negation sort mp4' - self.check_body('/file.png', '.png') - assert self.get(url='/file.jpg')['status'] == 403, 'negation sort jpg' - - def test_share_types_regex(self, temp_dir): - self.action_update( - {"share": temp_dir + "/assets", "types": ["~text/(html|plain)"]} - ) - assert self.get(url='/file.php')['status'] == 403, 'regex fail' - self.check_body('/file.html', '.html') - self.check_body('/file.txt', '.txt') - - def test_share_types_case(self, temp_dir): - self.action_update( - {"share": temp_dir + "/assets", "types": ["!APpliCaTiOn/xMl"]} - ) - self.check_body('/file.mp4', '.mp4') - assert ( - self.get(url='/file.xml')['status'] == 403 - ), 'mixed case xml negation' - - self.action_update( - {"share": temp_dir + "/assets", "types": ["vIdEo/mp4"]} - ) - assert self.get(url='/file.mp4')['status'] == 200, 'mixed case' - assert ( - self.get(url='/file.xml')['status'] == 403 - ), 'mixed case video negation' - - self.action_update( - {"share": temp_dir + "/assets", "types": ["vIdEo/*"]} - ) - self.check_body('/file.mp4', '.mp4') - assert ( - self.get(url='/file.xml')['status'] == 403 - ), 'mixed case video * negation' - - def test_share_types_fallback(self, temp_dir): - assert 'success' in self.conf( - [ - { - "match": {"destination": "*:7081"}, - "action": {"return": 200}, - }, - { - "action": { - "share": temp_dir + "/assets", - "types": ["!application/x-httpd-php"], - "fallback": {"proxy": "http://127.0.0.1:7081"}, - } - }, - ], - 'routes', - ), 'configure fallback proxy route' - - self.check_body('/file.php', '') - self.check_body('/file.mp4', '.mp4') - - def test_share_types_index(self, temp_dir): - self.action_update( - {"share": temp_dir + "/assets", "types": "application/xml"} - ) - self.check_body('/', 'index') - self.check_body('/file.xml', '.xml') - assert self.get(url='/file.mp4')['status'] == 403, 'forbidden mtype' - - def test_share_types_custom_mime(self, temp_dir): - self._load_conf( - { - "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], - "applications": {}, - "settings": { - "http": { - "static": {"mime_types": {"test/mime-type": ["file"]}} - } - }, - } - ) - - self.action_update({"share": temp_dir + "/assets", "types": [""]}) - assert self.get(url='/file')['status'] == 403, 'forbidden custom mime' - - self.action_update( - {"share": temp_dir + "/assets", "types": ["test/mime-type"]} - ) - self.check_body('/file', '') diff --git a/test/test_static_chroot.py b/test/test_static_chroot.py new file mode 100644 index 00000000..f9bc93a8 --- /dev/null +++ b/test/test_static_chroot.py @@ -0,0 +1,108 @@ +import os +from pathlib import Path + +import pytest + +from unit.applications.proto import TestApplicationProto + + +class TestStaticChroot(TestApplicationProto): + prerequisites = {'features': ['chroot']} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + os.makedirs(temp_dir + '/assets/dir') + Path(temp_dir + '/assets/index.html').write_text('0123456789') + Path(temp_dir + '/assets/dir/file').write_text('blah') + + test = Path(__file__) + self.test_path = '/' + test.parent.name + '/' + test.name + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + } + ) + + def test_static_chroot(self, temp_dir): + assert self.get(url='/dir/file')['status'] == 200, 'default chroot' + assert self.get(url='/index.html')['status'] == 200, 'default chroot 2' + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets", + "chroot": temp_dir + "/assets/dir", + }, + 'routes/0/action', + ), 'configure chroot' + + assert self.get(url='/dir/file')['status'] == 200, 'chroot' + assert self.get(url='/index.html')['status'] == 403, 'chroot 403 2' + assert self.get(url='/file')['status'] == 403, 'chroot 403' + + def test_static_chroot_permission(self, is_su, temp_dir): + if is_su: + pytest.skip('does\'t work under root') + + os.chmod(temp_dir + '/assets/dir', 0o100) + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets", + "chroot": temp_dir + "/assets/dir", + }, + 'routes/0/action', + ), 'configure chroot' + + assert self.get(url='/dir/file')['status'] == 200, 'chroot' + + def test_static_chroot_empty(self, temp_dir): + assert 'success' in self.conf( + {"share": temp_dir + "/assets", "chroot": ""}, 'routes/0/action', + ), 'configure chroot empty absolute' + + assert ( + self.get(url='/dir/file')['status'] == 200 + ), 'chroot empty absolute' + + assert 'success' in self.conf( + {"share": ".", "chroot": ""}, 'routes/0/action', + ), 'configure chroot empty relative' + + assert ( + self.get(url=self.test_path)['status'] == 200 + ), 'chroot empty relative' + + def test_static_chroot_relative(self, is_su, temp_dir): + if is_su: + pytest.skip('does\'t work under root') + + assert 'success' in self.conf( + {"share": temp_dir + "/assets", "chroot": "."}, 'routes/0/action', + ), 'configure relative chroot' + + assert self.get(url='/dir/file')['status'] == 403, 'relative chroot' + + assert 'success' in self.conf( + {"share": "."}, 'routes/0/action', + ), 'configure relative share' + + assert self.get(url=self.test_path)['status'] == 200, 'relative share' + + assert 'success' in self.conf( + {"share": ".", "chroot": "."}, 'routes/0/action', + ), 'configure relative' + + assert self.get(url=self.test_path)['status'] == 200, 'relative' + + def test_static_chroot_invalid(self, temp_dir): + assert 'error' in self.conf( + {"share": temp_dir, "chroot": True}, 'routes/0/action', + ), 'configure chroot error' + assert 'error' in self.conf( + {"share": temp_dir, "symlinks": "True"}, 'routes/0/action', + ), 'configure symlink error' + assert 'error' in self.conf( + {"share": temp_dir, "mount": "True"}, 'routes/0/action', + ), 'configure mount error' diff --git a/test/test_static_fallback.py b/test/test_static_fallback.py new file mode 100644 index 00000000..dc9056b9 --- /dev/null +++ b/test/test_static_fallback.py @@ -0,0 +1,149 @@ +import os +from pathlib import Path + +import pytest + +from unit.applications.proto import TestApplicationProto + + +class TestStaticFallback(TestApplicationProto): + prerequisites = {} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + os.makedirs(temp_dir + '/assets/dir') + Path(temp_dir + '/assets/index.html').write_text('0123456789') + + os.makedirs(temp_dir + '/assets/403') + os.chmod(temp_dir + '/assets/403', 0o000) + + self._load_conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": {"pass": "routes"}, + }, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + "applications": {}, + } + ) + + yield + + try: + os.chmod(temp_dir + '/assets/403', 0o777) + except FileNotFoundError: + pass + + def action_update(self, conf): + assert 'success' in self.conf(conf, 'routes/0/action') + + def test_static_fallback(self): + self.action_update({"share": "/blah"}) + assert self.get()['status'] == 404, 'bad path no fallback' + + self.action_update({"share": "/blah", "fallback": {"return": 200}}) + + resp = self.get() + assert resp['status'] == 200, 'bad path fallback status' + assert resp['body'] == '', 'bad path fallback' + + def test_static_fallback_valid_path(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "fallback": {"return": 200}} + ) + resp = self.get() + assert resp['status'] == 200, 'fallback status' + assert resp['body'] == '0123456789', 'fallback' + + resp = self.get(url='/403/') + assert resp['status'] == 200, 'fallback status 403' + assert resp['body'] == '', 'fallback 403' + + resp = self.post() + assert resp['status'] == 200, 'fallback status 405' + assert resp['body'] == '', 'fallback 405' + + assert self.get(url='/dir')['status'] == 301, 'fallback status 301' + + def test_static_fallback_nested(self): + self.action_update( + { + "share": "/blah", + "fallback": { + "share": "/blah/blah", + "fallback": {"return": 200}, + }, + } + ) + + resp = self.get() + assert resp['status'] == 200, 'fallback nested status' + assert resp['body'] == '', 'fallback nested' + + def test_static_fallback_share(self, temp_dir): + self.action_update( + {"share": "/blah", "fallback": {"share": temp_dir + "/assets"},} + ) + + resp = self.get() + assert resp['status'] == 200, 'fallback share status' + assert resp['body'] == '0123456789', 'fallback share' + + resp = self.head() + assert resp['status'] == 200, 'fallback share status HEAD' + assert resp['body'] == '', 'fallback share HEAD' + + assert ( + self.get(url='/dir')['status'] == 301 + ), 'fallback share status 301' + + def test_static_fallback_proxy(self): + assert 'success' in self.conf( + [ + { + "match": {"destination": "*:7081"}, + "action": {"return": 200}, + }, + { + "action": { + "share": "/blah", + "fallback": {"proxy": "http://127.0.0.1:7081"}, + } + }, + ], + 'routes', + ), 'configure fallback proxy route' + + resp = self.get() + assert resp['status'] == 200, 'fallback proxy status' + assert resp['body'] == '', 'fallback proxy' + + @pytest.mark.skip('not yet') + def test_static_fallback_proxy_loop(self, skip_alert): + skip_alert( + r'open.*/blah/index.html.*failed', + r'accept.*failed', + r'socket.*failed', + r'new connections are not accepted', + ) + + self.action_update( + {"share": "/blah", "fallback": {"proxy": "http://127.0.0.1:7080"}} + ) + self.get(no_recv=True) + + assert 'success' in self.conf_delete('listeners/*:7081') + self.get(read_timeout=1) + + def test_static_fallback_invalid(self): + def check_error(conf): + assert 'error' in self.conf(conf, 'routes/0/action') + + check_error({"share": "/blah", "fallback": {}}) + check_error({"share": "/blah", "fallback": ""}) + check_error({"return": 200, "fallback": {"share": "/blah"}}) + check_error( + {"proxy": "http://127.0.0.1:7081", "fallback": {"share": "/blah"}} + ) + check_error({"fallback": {"share": "/blah"}}) diff --git a/test/test_static_mount.py b/test/test_static_mount.py new file mode 100644 index 00000000..570f6439 --- /dev/null +++ b/test/test_static_mount.py @@ -0,0 +1,140 @@ +import os +import subprocess +from pathlib import Path + +import pytest + +from unit.applications.proto import TestApplicationProto + + +class TestStaticMount(TestApplicationProto): + prerequisites = {'features': ['chroot']} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, is_su, temp_dir): + if not is_su: + pytest.skip('requires root') + + os.makedirs(temp_dir + '/assets/dir/mount') + os.makedirs(temp_dir + '/assets/dir/dir') + os.makedirs(temp_dir + '/assets/mount') + Path(temp_dir + '/assets/index.html').write_text('index') + Path(temp_dir + '/assets/dir/dir/file').write_text('file') + Path(temp_dir + '/assets/mount/index.html').write_text('mount') + + try: + process = subprocess.Popen( + [ + "mount", + "--bind", + temp_dir + "/assets/mount", + temp_dir + "/assets/dir/mount", + ], + stderr=subprocess.STDOUT, + ) + + process.communicate() + + except KeyboardInterrupt: + raise + + except: + pytest.fail('Can\'t run mount process.') + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets/dir"}}], + } + ) + + yield + + try: + process = subprocess.Popen( + ["umount", "--lazy", temp_dir + "/assets/dir/mount"], + stderr=subprocess.STDOUT, + ) + + process.communicate() + + except KeyboardInterrupt: + raise + + except: + pytest.fail('Can\'t run umount process.') + + def test_static_mount(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + resp = self.get(url='/mount/') + assert resp['status'] == 200 + assert resp['body'] == 'mount' + + assert 'success' in self.conf( + {"share": temp_dir + "/assets/dir", "traverse_mounts": False}, + 'routes/0/action', + ), 'configure mount disable' + + assert self.get(url='/mount/')['status'] == 403 + + assert 'success' in self.conf( + {"share": temp_dir + "/assets/dir", "traverse_mounts": True}, + 'routes/0/action', + ), 'configure mount enable' + + resp = self.get(url='/mount/') + assert resp['status'] == 200 + assert resp['body'] == 'mount' + + def test_static_mount_two_blocks(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') + + assert 'success' in self.conf( + [ + { + "match": {"method": "HEAD"}, + "action": { + "share": temp_dir + "/assets/dir", + "traverse_mounts": False, + }, + }, + { + "match": {"method": "GET"}, + "action": { + "share": temp_dir + "/assets/dir", + "traverse_mounts": True, + }, + }, + ], + 'routes', + ), 'configure two options' + + assert self.get(url='/mount/')['status'] == 200, 'block enabled' + assert self.head(url='/mount/')['status'] == 403, 'block disabled' + + def test_static_mount_chroot(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets/dir", + "chroot": temp_dir + "/assets", + }, + 'routes/0/action', + ), 'configure chroot mount default' + + assert self.get(url='/mount/')['status'] == 200, 'chroot' + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets/dir", + "chroot": temp_dir + "/assets", + "traverse_mounts": False, + }, + 'routes/0/action', + ), 'configure chroot mount disable' + + assert self.get(url='/mount/')['status'] == 403, 'chroot mount' diff --git a/test/test_static_symlink.py b/test/test_static_symlink.py new file mode 100644 index 00000000..35eb402a --- /dev/null +++ b/test/test_static_symlink.py @@ -0,0 +1,94 @@ +import os +from pathlib import Path + +import pytest + +from unit.applications.proto import TestApplicationProto + + +class TestStaticSymlink(TestApplicationProto): + prerequisites = {'features': ['chroot']} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + os.makedirs(temp_dir + '/assets/dir/dir') + Path(temp_dir + '/assets/index.html').write_text('0123456789') + Path(temp_dir + '/assets/dir/file').write_text('blah') + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + } + ) + + def test_static_symlink(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') + + assert self.get(url='/dir')['status'] == 301, 'dir' + assert self.get(url='/dir/file')['status'] == 200, 'file' + assert self.get(url='/link')['status'] == 301, 'symlink dir' + assert self.get(url='/link/file')['status'] == 200, 'symlink file' + + assert 'success' in self.conf( + {"share": temp_dir + "/assets", "follow_symlinks": False}, + 'routes/0/action', + ), 'configure symlink disable' + + assert self.get(url='/link/file')['status'] == 403, 'symlink disabled' + + assert 'success' in self.conf( + {"share": temp_dir + "/assets", "follow_symlinks": True}, + 'routes/0/action', + ), 'configure symlink enable' + + assert self.get(url='/link/file')['status'] == 200, 'symlink enabled' + + def test_static_symlink_two_blocks(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') + + assert 'success' in self.conf( + [ + { + "match": {"method": "HEAD"}, + "action": { + "share": temp_dir + "/assets", + "follow_symlinks": False, + }, + }, + { + "match": {"method": "GET"}, + "action": { + "share": temp_dir + "/assets", + "follow_symlinks": True, + }, + }, + ], + 'routes', + ), 'configure two options' + + assert self.get(url='/link/file')['status'] == 200, 'block enabled' + assert self.head(url='/link/file')['status'] == 403, 'block disabled' + + def test_static_symlink_chroot(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + os.symlink( + temp_dir + '/assets/dir/file', temp_dir + '/assets/dir/dir/link' + ) + + assert self.get(url='/dir/dir/link')['status'] == 200, 'default chroot' + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets", + "chroot": temp_dir + "/assets/dir/dir", + }, + 'routes/0/action', + ), 'configure chroot' + + assert self.get(url='/dir/dir/link')['status'] == 404, 'chroot' diff --git a/test/test_static_types.py b/test/test_static_types.py new file mode 100644 index 00000000..20defddf --- /dev/null +++ b/test/test_static_types.py @@ -0,0 +1,169 @@ +from pathlib import Path + +import pytest + +from unit.applications.proto import TestApplicationProto + + +class TestStaticTypes(TestApplicationProto): + prerequisites = {} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + Path(temp_dir + '/assets').mkdir() + for ext in ['.xml', '.mp4', '.php', '', '.txt', '.html', '.png']: + Path(temp_dir + '/assets/file' + ext).write_text(ext) + + Path(temp_dir + '/assets/index.html').write_text('index') + + self._load_conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": {"pass": "routes"}, + }, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + "applications": {}, + } + ) + + def action_update(self, conf): + assert 'success' in self.conf(conf, 'routes/0/action') + + def check_body(self, http_url, body): + resp = self.get(url=http_url) + assert resp['status'] == 200, 'status' + assert resp['body'] == body, 'body' + + def test_static_types_basic(self, temp_dir): + self.action_update({"share": temp_dir + "/assets"}) + self.check_body('/index.html', 'index') + self.check_body('/file.xml', '.xml') + + self.action_update( + {"share": temp_dir + "/assets", "types": "application/xml"} + ) + self.check_body('/file.xml', '.xml') + + self.action_update( + {"share": temp_dir + "/assets", "types": ["application/xml"]} + ) + self.check_body('/file.xml', '.xml') + + self.action_update({"share": temp_dir + "/assets", "types": [""]}) + assert self.get(url='/file.xml')['status'] == 403, 'no mtype' + + def test_static_types_wildcard(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": ["application/*"]} + ) + self.check_body('/file.xml', '.xml') + assert self.get(url='/file.mp4')['status'] == 403, 'app * mtype mp4' + + self.action_update( + {"share": temp_dir + "/assets", "types": ["video/*"]} + ) + assert self.get(url='/file.xml')['status'] == 403, 'video * mtype xml' + self.check_body('/file.mp4', '.mp4') + + def test_static_types_negation(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": ["!application/xml"]} + ) + assert self.get(url='/file.xml')['status'] == 403, 'forbidden negation' + self.check_body('/file.mp4', '.mp4') + + # sorting negation + self.action_update( + { + "share": temp_dir + "/assets", + "types": ["!video/*", "image/png", "!image/jpg"], + } + ) + assert self.get(url='/file.mp4')['status'] == 403, 'negation sort mp4' + self.check_body('/file.png', '.png') + assert self.get(url='/file.jpg')['status'] == 403, 'negation sort jpg' + + def test_static_types_regex(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": ["~text/(html|plain)"]} + ) + assert self.get(url='/file.php')['status'] == 403, 'regex fail' + self.check_body('/file.html', '.html') + self.check_body('/file.txt', '.txt') + + def test_static_types_case(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": ["!APpliCaTiOn/xMl"]} + ) + self.check_body('/file.mp4', '.mp4') + assert ( + self.get(url='/file.xml')['status'] == 403 + ), 'mixed case xml negation' + + self.action_update( + {"share": temp_dir + "/assets", "types": ["vIdEo/mp4"]} + ) + assert self.get(url='/file.mp4')['status'] == 200, 'mixed case' + assert ( + self.get(url='/file.xml')['status'] == 403 + ), 'mixed case video negation' + + self.action_update( + {"share": temp_dir + "/assets", "types": ["vIdEo/*"]} + ) + self.check_body('/file.mp4', '.mp4') + assert ( + self.get(url='/file.xml')['status'] == 403 + ), 'mixed case video * negation' + + def test_static_types_fallback(self, temp_dir): + assert 'success' in self.conf( + [ + { + "match": {"destination": "*:7081"}, + "action": {"return": 200}, + }, + { + "action": { + "share": temp_dir + "/assets", + "types": ["!application/x-httpd-php"], + "fallback": {"proxy": "http://127.0.0.1:7081"}, + } + }, + ], + 'routes', + ), 'configure fallback proxy route' + + self.check_body('/file.php', '') + self.check_body('/file.mp4', '.mp4') + + def test_static_types_index(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": "application/xml"} + ) + self.check_body('/', 'index') + self.check_body('/file.xml', '.xml') + assert self.get(url='/file.mp4')['status'] == 403, 'forbidden mtype' + + def test_static_types_custom_mime(self, temp_dir): + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + "applications": {}, + "settings": { + "http": { + "static": {"mime_types": {"test/mime-type": ["file"]}} + } + }, + } + ) + + self.action_update({"share": temp_dir + "/assets", "types": [""]}) + assert self.get(url='/file')['status'] == 403, 'forbidden custom mime' + + self.action_update( + {"share": temp_dir + "/assets", "types": ["test/mime-type"]} + ) + self.check_body('/file', '') -- cgit From c16123e7493118dad698ccac7e56bb475bac7def Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Mon, 24 May 2021 16:15:42 +0800 Subject: Router: split nxt_http_return_conf_t from nxt_http_action_t. No functional changes. --- src/nxt_http.h | 20 ++++++-- src/nxt_http_return.c | 83 +++++++++++++++++++++++++++----- src/nxt_http_route.c | 130 +++++++++++++++++--------------------------------- 3 files changed, 132 insertions(+), 101 deletions(-) diff --git a/src/nxt_http.h b/src/nxt_http.h index f82d837e..18cf8680 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -201,15 +201,29 @@ typedef struct nxt_http_route_s nxt_http_route_t; typedef struct nxt_http_route_rule_s nxt_http_route_rule_t; +typedef struct { + nxt_conf_value_t *pass; + nxt_conf_value_t *ret; + nxt_str_t location; + nxt_conf_value_t *proxy; + nxt_conf_value_t *share; + nxt_str_t chroot; + nxt_conf_value_t *follow_symlinks; + nxt_conf_value_t *traverse_mounts; + nxt_conf_value_t *types; + nxt_conf_value_t *fallback; +} nxt_http_action_conf_t; + + struct nxt_http_action_s { nxt_http_action_t *(*handler)(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action); union { + void *conf; nxt_http_route_t *route; nxt_upstream_t *upstream; uint32_t upstream_number; - nxt_http_status_t return_code; nxt_var_t *var; struct { @@ -319,8 +333,8 @@ nxt_int_t nxt_upstreams_joint_create(nxt_router_temp_conf_t *tmcf, void nxt_http_request_action(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action); -nxt_http_action_t *nxt_http_return_handler(nxt_task_t *task, - nxt_http_request_t *r, nxt_http_action_t *action); +nxt_int_t nxt_http_return_init(nxt_mp_t *mp, nxt_http_action_t *action, + nxt_http_action_conf_t *acf); nxt_http_action_t *nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action); diff --git a/src/nxt_http_return.c b/src/nxt_http_return.c index c466cc25..18fd490d 100644 --- a/src/nxt_http_return.c +++ b/src/nxt_http_return.c @@ -7,29 +7,90 @@ #include +typedef struct { + nxt_http_status_t status; + nxt_str_t location; +} nxt_http_return_conf_t; + + +static nxt_http_action_t *nxt_http_return(nxt_task_t *task, + nxt_http_request_t *r, nxt_http_action_t *action); + + static const nxt_http_request_state_t nxt_http_return_send_state; +nxt_int_t +nxt_http_return_init(nxt_mp_t *mp, nxt_http_action_t *action, + nxt_http_action_conf_t *acf) +{ + nxt_str_t *loc; + nxt_uint_t encode; + nxt_http_return_conf_t *conf; + + conf = nxt_mp_zget(mp, sizeof(nxt_http_return_conf_t)); + if (nxt_slow_path(conf == NULL)) { + return NXT_ERROR; + } + + action->handler = nxt_http_return; + action->u.conf = conf; + + conf->status = nxt_conf_get_number(acf->ret); + + if (acf->location.length > 0) { + if (nxt_is_complex_uri_encoded(acf->location.start, + acf->location.length)) + { + loc = nxt_str_dup(mp, &conf->location, &acf->location); + if (nxt_slow_path(loc == NULL)) { + return NXT_ERROR; + } + + } else { + loc = &conf->location; + + encode = nxt_encode_complex_uri(NULL, acf->location.start, + acf->location.length); + loc->length = acf->location.length + encode * 2; + + loc->start = nxt_mp_nget(mp, loc->length); + if (nxt_slow_path(loc->start == NULL)) { + return NXT_ERROR; + } + + nxt_encode_complex_uri(loc->start, acf->location.start, + acf->location.length); + } + } + + return NXT_OK; +} + + nxt_http_action_t * -nxt_http_return_handler(nxt_task_t *task, nxt_http_request_t *r, +nxt_http_return(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action) { - nxt_http_field_t *field; - nxt_http_status_t status; + nxt_http_field_t *field; + nxt_http_return_conf_t *conf; + + conf = action->u.conf; - status = action->u.return_code; + nxt_debug(task, "http return: %d (loc: \"%V\")", + conf->status, &conf->location); - if (status >= NXT_HTTP_BAD_REQUEST - && status <= NXT_HTTP_SERVER_ERROR_MAX) + if (conf->status >= NXT_HTTP_BAD_REQUEST + && conf->status <= NXT_HTTP_SERVER_ERROR_MAX) { - nxt_http_request_error(task, r, status); + nxt_http_request_error(task, r, conf->status); return NULL; } - r->status = status; + r->status = conf->status; r->resp.content_length_n = 0; - if (action->name.length > 0) { + if (conf->location.length > 0) { field = nxt_list_zero_add(r->resp.fields); if (nxt_slow_path(field == NULL)) { nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); @@ -38,8 +99,8 @@ nxt_http_return_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_field_name_set(field, "Location"); - field->value = action->name.start; - field->value_length = action->name.length; + field->value = conf->location.start; + field->value_length = conf->location.length; } r->state = &nxt_http_return_send_state; diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 15b85544..46c9b536 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -46,20 +46,6 @@ typedef enum { } nxt_http_route_encoding_t; -typedef struct { - nxt_conf_value_t *pass; - nxt_conf_value_t *ret; - nxt_str_t location; - nxt_conf_value_t *proxy; - nxt_conf_value_t *share; - nxt_str_t chroot; - nxt_conf_value_t *follow_symlinks; - nxt_conf_value_t *traverse_mounts; - nxt_conf_value_t *types; - nxt_conf_value_t *fallback; -} nxt_http_route_action_conf_t; - - typedef struct { nxt_conf_value_t *host; nxt_conf_value_t *uri; @@ -199,7 +185,7 @@ static nxt_http_route_t *nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); static nxt_http_route_match_t *nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); -static nxt_int_t nxt_http_route_action_create(nxt_task_t *task, +static nxt_int_t nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, nxt_http_action_t *action); static nxt_http_route_table_t *nxt_http_route_table_create(nxt_task_t *task, @@ -476,7 +462,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, return NULL; } - ret = nxt_http_route_action_create(task, tmcf, action_conf, &match->action); + ret = nxt_http_action_init(task, tmcf, action_conf, &match->action); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } @@ -617,77 +603,76 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { { nxt_string("pass"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, pass) + offsetof(nxt_http_action_conf_t, pass) }, { nxt_string("return"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, ret) + offsetof(nxt_http_action_conf_t, ret) }, { nxt_string("location"), NXT_CONF_MAP_STR, - offsetof(nxt_http_route_action_conf_t, location) + offsetof(nxt_http_action_conf_t, location) }, { nxt_string("proxy"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, proxy) + offsetof(nxt_http_action_conf_t, proxy) }, { nxt_string("share"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, share) + offsetof(nxt_http_action_conf_t, share) }, { nxt_string("chroot"), NXT_CONF_MAP_STR, - offsetof(nxt_http_route_action_conf_t, chroot) + offsetof(nxt_http_action_conf_t, chroot) }, { nxt_string("follow_symlinks"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, follow_symlinks) + offsetof(nxt_http_action_conf_t, follow_symlinks) }, { nxt_string("traverse_mounts"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, traverse_mounts) + offsetof(nxt_http_action_conf_t, traverse_mounts) }, { nxt_string("types"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, types) + offsetof(nxt_http_action_conf_t, types) }, { nxt_string("fallback"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, fallback) + offsetof(nxt_http_action_conf_t, fallback) }, }; static nxt_int_t -nxt_http_route_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, +nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, nxt_http_action_t *action) { #if (NXT_HAVE_OPENAT2) - u_char *p; - uint8_t slash; - nxt_str_t *chroot; + u_char *p; + uint8_t slash; + nxt_str_t *chroot; #endif - nxt_mp_t *mp; - nxt_int_t ret; - nxt_str_t name, *string; - nxt_uint_t encode; - nxt_conf_value_t *conf; - nxt_http_route_rule_t *rule; - nxt_http_route_action_conf_t accf; + nxt_mp_t *mp; + nxt_int_t ret; + nxt_str_t name, *string; + nxt_conf_value_t *conf; + nxt_http_route_rule_t *rule; + nxt_http_action_conf_t acf; - nxt_memzero(&accf, sizeof(accf)); + nxt_memzero(&acf, sizeof(acf)); ret = nxt_conf_map_object(tmcf->mem_pool, cv, nxt_http_route_action_conf, - nxt_nitems(nxt_http_route_action_conf), &accf); + nxt_nitems(nxt_http_route_action_conf), &acf); if (ret != NXT_OK) { return ret; } @@ -696,47 +681,18 @@ nxt_http_route_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, mp = tmcf->router_conf->mem_pool; - if (accf.ret != NULL) { - action->handler = nxt_http_return_handler; - action->u.return_code = nxt_conf_get_number(accf.ret); - - if (accf.location.length > 0) { - if (nxt_is_complex_uri_encoded(accf.location.start, - accf.location.length)) - { - string = nxt_str_dup(mp, &action->name, &accf.location); - if (nxt_slow_path(string == NULL)) { - return NXT_ERROR; - } - - } else { - string = &action->name; - - encode = nxt_encode_complex_uri(NULL, accf.location.start, - accf.location.length); - string->length = accf.location.length + encode * 2; - - string->start = nxt_mp_nget(mp, string->length); - if (nxt_slow_path(string->start == NULL)) { - return NXT_ERROR; - } - - nxt_encode_complex_uri(string->start, accf.location.start, - accf.location.length); - } - } - - return NXT_OK; + if (acf.ret != NULL) { + return nxt_http_return_init(mp, action, &acf); } - if (accf.share != NULL) { - conf = accf.share; + if (acf.share != NULL) { + conf = acf.share; - } else if (accf.proxy != NULL) { - conf = accf.proxy; + } else if (acf.proxy != NULL) { + conf = acf.proxy; } else { - conf = accf.pass; + conf = acf.pass; } nxt_conf_get_string(conf, &name); @@ -746,11 +702,11 @@ nxt_http_route_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, return NXT_ERROR; } - if (accf.share != NULL) { + if (acf.share != NULL) { action->handler = nxt_http_static_handler; #if (NXT_HAVE_OPENAT2) - string = &accf.chroot; + string = &acf.chroot; chroot = &action->u.share.chroot; if (string->length > 0) { @@ -774,21 +730,21 @@ nxt_http_route_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, *p = '\0'; } - if (accf.follow_symlinks != NULL - && !nxt_conf_get_boolean(accf.follow_symlinks)) + if (acf.follow_symlinks != NULL + && !nxt_conf_get_boolean(acf.follow_symlinks)) { action->u.share.resolve |= RESOLVE_NO_SYMLINKS; } - if (accf.traverse_mounts != NULL - && !nxt_conf_get_boolean(accf.traverse_mounts)) + if (acf.traverse_mounts != NULL + && !nxt_conf_get_boolean(acf.traverse_mounts)) { action->u.share.resolve |= RESOLVE_NO_XDEV; } #endif - if (accf.types != NULL) { - rule = nxt_http_route_rule_create(task, mp, accf.types, 0, + if (acf.types != NULL) { + rule = nxt_http_route_rule_create(task, mp, acf.types, 0, NXT_HTTP_ROUTE_PATTERN_LOWCASE, NXT_HTTP_ROUTE_ENCODING_NONE); if (nxt_slow_path(rule == NULL)) { @@ -798,21 +754,21 @@ nxt_http_route_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, action->u.share.types = rule; } - if (accf.fallback != NULL) { + if (acf.fallback != NULL) { action->u.share.fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t)); if (nxt_slow_path(action->u.share.fallback == NULL)) { return NXT_ERROR; } - return nxt_http_route_action_create(task, tmcf, accf.fallback, - action->u.share.fallback); + return nxt_http_action_init(task, tmcf, acf.fallback, + action->u.share.fallback); } return NXT_OK; } - if (accf.proxy != NULL) { + if (acf.proxy != NULL) { return nxt_http_proxy_create(mp, action); } -- cgit From cfba69781a18407d5c2020c4e3f3d4fc175a6127 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 1 Jul 2021 13:56:40 +0300 Subject: Fixing multiple TLS-enabled listeners initialization. Because of the incorrect 'last' field assignment, multiple listeners with a TLS certificate did not initialize properly, which caused a router crash while establishing a connection. Test with multiple TLS listeners added. The issue was introduced in the c548e46fe516 commit. This closes #561 issue on GitHub. --- docs/changes.xml | 7 +++++++ src/nxt_router.c | 11 +++++------ test/test_tls.py | 13 +++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 51a519f0..ec8d4981 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -44,6 +44,13 @@ Initial release of Java 17 module for NGINX Unit. + + +the router process could crash on TLS connection open when multiple listeners +with TLS certificate configured; the bug had appeared in 1.23.0. + + + diff --git a/src/nxt_router.c b/src/nxt_router.c index 015ae226..26b846b0 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -124,7 +124,7 @@ static void nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *value, nxt_socket_conf_t *skcf, - nxt_conf_value_t * conf_cmds); + nxt_conf_value_t * conf_cmds, nxt_bool_t last); #endif static void nxt_router_app_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_app_t *app); @@ -956,8 +956,6 @@ nxt_router_conf_apply(nxt_task_t *task, void *obj, void *data) tls = nxt_queue_link_data(qlk, nxt_router_tlssock_t, link); - tls->last = nxt_queue_is_empty(&tmcf->tls); - nxt_cert_store_get(task, &tls->name, tmcf->mem_pool, nxt_router_tls_rpc_handler, tls); return; @@ -1752,7 +1750,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_assert(value != NULL); ret = nxt_router_conf_tls_insert(tmcf, value, skcf, - conf_cmds); + conf_cmds, i == 0); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -1761,7 +1759,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } else { /* NXT_CONF_STRING */ ret = nxt_router_conf_tls_insert(tmcf, certificate, skcf, - conf_cmds); + conf_cmds, 1); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -1856,7 +1854,7 @@ fail: static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *value, nxt_socket_conf_t *skcf, - nxt_conf_value_t *conf_cmds) + nxt_conf_value_t *conf_cmds, nxt_bool_t last) { nxt_router_tlssock_t *tls; @@ -1868,6 +1866,7 @@ nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, tls->socket_conf = skcf; tls->conf_cmds = conf_cmds; tls->temp_conf = tmcf; + tls->last = last; nxt_conf_get_string(value, &tls->name); nxt_queue_insert_tail(&tmcf->tls, &tls->link); diff --git a/test/test_tls.py b/test/test_tls.py index 0cfeaded..546f0f89 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -665,3 +665,16 @@ basicConstraints = critical,CA:TRUE""" ) assert res['status'] == 200, 'status ok' assert res['body'] == filename + data + + def test_tls_multi_listener(self): + self.load('empty') + + self.certificate() + + self.add_tls() + self.add_tls(port=7081) + + assert self.get_ssl()['status'] == 200, 'listener #1' + + assert self.get_ssl(port=7081)['status'] == 200, 'listener #2' + -- cgit From 830729a6c5a59615d611575c22516b0fb9dcab3d Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 1 Jul 2021 11:16:43 +0000 Subject: Ruby: improved logging of exceptions without backtraces. If an exception was raised with a backtrace of zero length, the nxt_ruby_exception_log() routine would return without logging the exception class and message. This commit fixes the issue. --- src/ruby/nxt_ruby.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index ca14af5b..10935528 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -1069,14 +1069,18 @@ nxt_ruby_exception_log(nxt_unit_request_info_t *req, uint32_t level, return; } + eclass = rb_class_name(rb_class_of(err)); + + msg = rb_funcall(err, rb_intern("message"), 0); ary = rb_funcall(err, rb_intern("backtrace"), 0); - if (nxt_slow_path(RARRAY_LEN(ary) == 0)) { + + if (RARRAY_LEN(ary) == 0) { + nxt_unit_req_log(req, level, "Ruby: %s (%s)", RSTRING_PTR(msg), + RSTRING_PTR(eclass)); + return; } - eclass = rb_class_name(rb_class_of(err)); - msg = rb_funcall(err, rb_intern("message"), 0); - nxt_unit_req_log(req, level, "Ruby: %s: %s (%s)", RSTRING_PTR(RARRAY_PTR(ary)[0]), RSTRING_PTR(msg), RSTRING_PTR(eclass)); -- cgit From 210c8bbd81a8836bffee221215effa00018868cd Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 1 Jul 2021 16:22:08 +0300 Subject: Tests: fixing racing condition in respawn tests. A race may occur between the router process restart and the main process sending a notification to the running controller. For example, a test script detects the new process and starts performing a smoke test, but the controller has not yet received the 'remove PID' notification, so the connection to the router is broken and any attempt to update the configuration will cause an error. The solution is to perform several attempts to reconfigure Unit with a short delay between failures. --- test/test_respawn.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/test_respawn.py b/test/test_respawn.py index edbfa2a8..5a5d6126 100644 --- a/test/test_respawn.py +++ b/test/test_respawn.py @@ -44,11 +44,16 @@ class TestRespawn(TestApplicationPython): return re.findall(str(ppid) + r'.*' + name, ps_output) def smoke_test(self, unit_pid): - for _ in range(5): - assert 'success' in self.conf( - '1', 'applications/' + self.app_name + '/processes' - ) - assert self.get()['status'] == 200 + for _ in range(10): + r = self.conf('1', 'applications/' + self.app_name + '/processes') + + if 'success' in r: + break + + time.sleep(0.1) + + assert 'success' in r + assert self.get()['status'] == 200 # Check if the only one router, controller, # and application processes running. -- cgit From 2ac9c627aa6aa736f3df9f426e741642694ac911 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 1 Jul 2021 16:23:51 +0300 Subject: Fixing memory and descriptor leakage in case of port send failure. In rare cases, when the destination process had finished running but no notification of this was received yet, send could fail with an error, and the send message structure with file descriptors could leak. The leakage was periodically reproduced by respawn tests on FreeBSD 12. --- docs/changes.xml | 21 ++++++++++++++------- src/nxt_port_socket.c | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index ec8d4981..2af2fc90 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,20 @@ NGINX Unit updated to 1.25.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +the router process could crash on TLS connection open when multiple listeners +with TLS certificate configured; the bug had appeared in 1.23.0. + + + + + +a descriptor and memory leak occurred in the router process when an app +process stopped or crashed. + + + @@ -44,13 +58,6 @@ Initial release of Java 17 module for NGINX Unit. - - -the router process could crash on TLS connection open when multiple listeners -with TLS certificate configured; the bug had appeared in 1.23.0. - - - diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index 3cf2e79a..843728a4 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -524,6 +524,24 @@ next_fragment: } else { if (nxt_slow_path(n == NXT_ERROR)) { + if (msg->link.next == NULL) { + if (msg->close_fd) { + if (msg->fd[0] != -1) { + nxt_fd_close(msg->fd[0]); + + msg->fd[0] = -1; + } + + if (msg->fd[1] != -1) { + nxt_fd_close(msg->fd[1]); + + msg->fd[1] = -1; + } + } + + nxt_port_release_send_msg(msg); + } + goto fail; } -- cgit From 54bf3e19122ca89ef79abe9cbdd2a039c7d6db3b Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 1 Jul 2021 16:23:56 +0300 Subject: Deduplicating code for closing fds in nxt_port_send_msg_t. --- src/nxt_port_socket.c | 64 ++++++++++++++++++++------------------------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index 843728a4..ba1b7081 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -21,6 +21,7 @@ static nxt_int_t nxt_port_msg_chk_insert(nxt_task_t *task, nxt_port_t *port, static nxt_port_send_msg_t *nxt_port_msg_alloc(nxt_port_send_msg_t *m); static void nxt_port_write_handler(nxt_task_t *task, void *obj, void *data); static nxt_port_send_msg_t *nxt_port_msg_first(nxt_port_t *port); +nxt_inline void nxt_port_msg_close_fd(nxt_port_send_msg_t *msg); static nxt_buf_t *nxt_port_buf_completion(nxt_task_t *task, nxt_work_queue_t *wq, nxt_buf_t *b, size_t sent, nxt_bool_t mmap_mode); static nxt_port_send_msg_t *nxt_port_msg_insert_tail(nxt_port_t *port, @@ -449,19 +450,7 @@ next_fragment: goto fail; } - if (msg->close_fd) { - if (msg->fd[0] != -1) { - nxt_fd_close(msg->fd[0]); - - msg->fd[0] = -1; - } - - if (msg->fd[1] != -1) { - nxt_fd_close(msg->fd[1]); - - msg->fd[1] = -1; - } - } + nxt_port_msg_close_fd(msg); msg->buf = nxt_port_buf_completion(task, wq, msg->buf, plain_size, m == NXT_PORT_METHOD_MMAP); @@ -525,19 +514,7 @@ next_fragment: } else { if (nxt_slow_path(n == NXT_ERROR)) { if (msg->link.next == NULL) { - if (msg->close_fd) { - if (msg->fd[0] != -1) { - nxt_fd_close(msg->fd[0]); - - msg->fd[0] = -1; - } - - if (msg->fd[1] != -1) { - nxt_fd_close(msg->fd[1]); - - msg->fd[1] = -1; - } - } + nxt_port_msg_close_fd(msg); nxt_port_release_send_msg(msg); } @@ -609,6 +586,27 @@ nxt_port_msg_first(nxt_port_t *port) } +nxt_inline void +nxt_port_msg_close_fd(nxt_port_send_msg_t *msg) +{ + if (!msg->close_fd) { + return; + } + + if (msg->fd[0] != -1) { + nxt_fd_close(msg->fd[0]); + + msg->fd[0] = -1; + } + + if (msg->fd[1] != -1) { + nxt_fd_close(msg->fd[1]); + + msg->fd[1] = -1; + } +} + + static nxt_buf_t * nxt_port_buf_completion(nxt_task_t *task, nxt_work_queue_t *wq, nxt_buf_t *b, size_t sent, nxt_bool_t mmap_mode) @@ -1333,19 +1331,7 @@ nxt_port_error_handler(nxt_task_t *task, void *obj, void *data) nxt_queue_each(msg, &port->messages, nxt_port_send_msg_t, link) { - if (msg->close_fd) { - if (msg->fd[0] != -1) { - nxt_fd_close(msg->fd[0]); - - msg->fd[0] = -1; - } - - if (msg->fd[1] != -1) { - nxt_fd_close(msg->fd[1]); - - msg->fd[1] = -1; - } - } + nxt_port_msg_close_fd(msg); for (b = msg->buf; b != NULL; b = next) { next = b->next; -- cgit From 7d2bc04e391f9216fb4e0464cb43c9c438f7e034 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Fri, 2 Jul 2021 10:55:13 +0000 Subject: Fixing crash during IPv6 text address generation. When the textual representation of an IPv6 nxt_sockaddr_t was being generated, a crash would occur if the address had a full IPv6 form: f607:7403:1e4b:6c66:33b2:843f:2517:da27 This was caused by a variable that tracks the location of a collapsed group ("::") that was not set to a sane default. When the address was generated, a group would be inserted when it was not necessary, thus causing an overflow. This closes #481 issue on GitHub. --- docs/changes.xml | 7 +++++++ src/nxt_sockaddr.c | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 2af2fc90..1d77aea7 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -45,6 +45,13 @@ process stopped or crashed. + + +the controller or router process could crash if the configuration contained +a full-form IPv6 in a listener address. + + + diff --git a/src/nxt_sockaddr.c b/src/nxt_sockaddr.c index af696a6b..47ee165f 100644 --- a/src/nxt_sockaddr.c +++ b/src/nxt_sockaddr.c @@ -525,9 +525,9 @@ nxt_inet6_ntop(u_char *addr, u_char *buf, u_char *end) return buf; } - zero_start = 8; + zero_start = 16; zero_groups = 0; - last_zero_start = 8; + last_zero_start = 16; last_zero_groups = 0; for (i = 0; i < 16; i += 2) { -- cgit From 655e321075c0beebe14eba83deeac1ba4c9e0b29 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Fri, 2 Jul 2021 12:57:55 +0000 Subject: Ruby: process and thread lifecycle hooks. This feature allows one to specify blocks of code that are called when certain lifecycle events occur. A user configures a "hooks" property on the app configuration that points to a script. This script will be evaluated on boot and should contain blocks of code that will be called on specific events. An example of configuration: { "type": "ruby", "processes": 2, "threads": 2, "user": "vagrant", "group": "vagrant", "script": "config.ru", "hooks": "hooks.rb", "working_directory": "/home/vagrant/unit/rbhooks", "environment": { "GEM_HOME": "/home/vagrant/.ruby" } } An example of a valid "hooks.rb" file follows: File.write("./hooks.#{Process.pid}", "hooks evaluated") on_worker_boot do File.write("./worker_boot.#{Process.pid}", "worker booted") end on_thread_boot do File.write("./thread_boot.#{Process.pid}.#{Thread.current.object_id}", "thread booted") end on_thread_shutdown do File.write("./thread_shutdown.#{Process.pid}.#{Thread.current.object_id}", "thread shutdown") end on_worker_shutdown do File.write("./worker_shutdown.#{Process.pid}", "worker shutdown") end This closes issue #535 on GitHub. --- docs/changes.xml | 6 ++ src/nxt_application.h | 1 + src/nxt_conf_validation.c | 3 + src/nxt_main_process.c | 5 ++ src/ruby/nxt_ruby.c | 143 +++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 157 insertions(+), 1 deletion(-) diff --git a/docs/changes.xml b/docs/changes.xml index 1d77aea7..5265d529 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,12 @@ NGINX Unit updated to 1.25.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +process and thread lifecycle hooks in Ruby. + + + the router process could crash on TLS connection open when multiple listeners diff --git a/src/nxt_application.h b/src/nxt_application.h index 45e7fa48..6fbdc4be 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -74,6 +74,7 @@ typedef struct { typedef struct { nxt_str_t script; uint32_t threads; + nxt_str_t hooks; } nxt_ruby_app_conf_t; diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 06ae2847..a16c955c 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -732,6 +732,9 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_ruby_members[] = { .name = nxt_string("threads"), .type = NXT_CONF_VLDT_INTEGER, .validator = nxt_conf_vldt_threads, + }, { + .name = nxt_string("hooks"), + .type = NXT_CONF_VLDT_STRING }, NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members) diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 00f336f6..10bd2518 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -271,6 +271,11 @@ static nxt_conf_map_t nxt_ruby_app_conf[] = { NXT_CONF_MAP_INT32, offsetof(nxt_common_app_conf_t, u.ruby.threads), }, + { + nxt_string("hooks"), + NXT_CONF_MAP_STR, + offsetof(nxt_common_app_conf_t, u.ruby.hooks), + } }; diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index 10935528..522869b5 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -29,6 +29,11 @@ typedef struct { static nxt_int_t nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data); static VALUE nxt_ruby_init_basic(VALUE arg); + +static VALUE nxt_ruby_hook_procs_load(VALUE path); +static VALUE nxt_ruby_hook_register(VALUE arg); +static VALUE nxt_ruby_hook_call(VALUE name); + static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init); static VALUE nxt_ruby_require_rubygems(VALUE arg); @@ -78,6 +83,7 @@ static uint32_t compat[] = { NXT_VERNUM, NXT_DEBUG, }; +static VALUE nxt_ruby_hook_procs; static VALUE nxt_ruby_rackup; static VALUE nxt_ruby_call; @@ -115,6 +121,10 @@ static VALUE nxt_rb_server_addr_str; static VALUE nxt_rb_server_name_str; static VALUE nxt_rb_server_port_str; static VALUE nxt_rb_server_protocol_str; +static VALUE nxt_rb_on_worker_boot; +static VALUE nxt_rb_on_worker_shutdown; +static VALUE nxt_rb_on_thread_boot; +static VALUE nxt_rb_on_thread_shutdown; static nxt_ruby_string_t nxt_rb_strings[] = { { nxt_string("80"), &nxt_rb_80_str }, @@ -132,6 +142,10 @@ static nxt_ruby_string_t nxt_rb_strings[] = { { nxt_string("SERVER_NAME"), &nxt_rb_server_name_str }, { nxt_string("SERVER_PORT"), &nxt_rb_server_port_str }, { nxt_string("SERVER_PROTOCOL"), &nxt_rb_server_protocol_str }, + { nxt_string("on_worker_boot"), &nxt_rb_on_worker_boot }, + { nxt_string("on_worker_shutdown"), &nxt_rb_on_worker_shutdown }, + { nxt_string("on_thread_boot"), &nxt_rb_on_thread_boot }, + { nxt_string("on_thread_shutdown"), &nxt_rb_on_thread_shutdown }, { nxt_null_string, NULL }, }; @@ -183,11 +197,70 @@ nxt_ruby_done_strings(void) } +static VALUE +nxt_ruby_hook_procs_load(VALUE path) +{ + VALUE module, file, file_obj; + + module = rb_define_module("Unit"); + + nxt_ruby_hook_procs = rb_hash_new(); + + rb_gc_register_address(&nxt_ruby_hook_procs); + + rb_define_module_function(module, "on_worker_boot", + &nxt_ruby_hook_register, 0); + rb_define_module_function(module, "on_worker_shutdown", + &nxt_ruby_hook_register, 0); + rb_define_module_function(module, "on_thread_boot", + &nxt_ruby_hook_register, 0); + rb_define_module_function(module, "on_thread_shutdown", + &nxt_ruby_hook_register, 0); + + file = rb_const_get(rb_cObject, rb_intern("File")); + file_obj = rb_funcall(file, rb_intern("read"), 1, path); + + return rb_funcall(module, rb_intern("module_eval"), 3, file_obj, path, + INT2NUM(1)); +} + + +static VALUE +nxt_ruby_hook_register(VALUE arg) +{ + VALUE kernel, callee, callee_str; + + rb_need_block(); + + kernel = rb_const_get(rb_cObject, rb_intern("Kernel")); + callee = rb_funcall(kernel, rb_intern("__callee__"), 0); + callee_str = rb_funcall(callee, rb_intern("to_s"), 0); + + rb_hash_aset(nxt_ruby_hook_procs, callee_str, rb_block_proc()); + + return Qnil; +} + + +static VALUE +nxt_ruby_hook_call(VALUE name) +{ + VALUE proc; + + proc = rb_hash_lookup(nxt_ruby_hook_procs, name); + if (proc == Qnil) { + return Qnil; + } + + return rb_funcall(proc, rb_intern("call"), 0); +} + + static nxt_int_t nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data) { int state, rc; - VALUE res; + VALUE res, path; nxt_ruby_ctx_t ruby_ctx; nxt_unit_ctx_t *unit_ctx; nxt_unit_init_t ruby_unit_init; @@ -231,6 +304,29 @@ nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data) } nxt_ruby_call = Qnil; + nxt_ruby_hook_procs = Qnil; + + if (c->hooks.start != NULL) { + path = rb_str_new((const char *) c->hooks.start, + (long) c->hooks.length); + + rb_protect(nxt_ruby_hook_procs_load, path, &state); + rb_str_free(path); + if (nxt_slow_path(state != 0)) { + nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, + "Failed to setup hooks"); + return NXT_ERROR; + } + } + + if (nxt_ruby_hook_procs != Qnil) { + rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_boot, &state); + if (nxt_slow_path(state != 0)) { + nxt_ruby_exception_log(NULL, NXT_LOG_ERR, + "Failed to call on_worker_boot()"); + return NXT_ERROR; + } + } nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init); if (nxt_slow_path(nxt_ruby_rackup == Qnil)) { @@ -274,11 +370,35 @@ nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data) goto fail; } + if (nxt_ruby_hook_procs != Qnil) { + rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state); + if (nxt_slow_path(state != 0)) { + nxt_ruby_exception_log(NULL, NXT_LOG_ERR, + "Failed to call on_thread_boot()"); + } + } + rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_unit_run, unit_ctx, nxt_ruby_ubf, unit_ctx); + if (nxt_ruby_hook_procs != Qnil) { + rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state); + if (nxt_slow_path(state != 0)) { + nxt_ruby_exception_log(NULL, NXT_LOG_ERR, + "Failed to call on_thread_shutdown()"); + } + } + nxt_ruby_join_threads(unit_ctx, c); + if (nxt_ruby_hook_procs != Qnil) { + rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_shutdown, &state); + if (nxt_slow_path(state != 0)) { + nxt_ruby_exception_log(NULL, NXT_LOG_ERR, + "Failed to call on_worker_shutdown()"); + } + } + nxt_unit_done(unit_ctx); nxt_ruby_ctx_done(&ruby_ctx); @@ -1120,6 +1240,10 @@ nxt_ruby_atexit(void) rb_gc_unregister_address(&nxt_ruby_call); } + if (nxt_ruby_hook_procs != Qnil) { + rb_gc_unregister_address(&nxt_ruby_hook_procs); + } + nxt_ruby_done_strings(); ruby_cleanup(0); @@ -1182,6 +1306,7 @@ nxt_ruby_thread_create_gvl(void *rctx) static VALUE nxt_ruby_thread_func(VALUE arg) { + int state; nxt_unit_ctx_t *ctx; nxt_ruby_ctx_t *rctx; @@ -1194,9 +1319,25 @@ nxt_ruby_thread_func(VALUE arg) goto fail; } + if (nxt_ruby_hook_procs != Qnil) { + rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state); + if (nxt_slow_path(state != 0)) { + nxt_ruby_exception_log(NULL, NXT_LOG_ERR, + "Failed to call on_thread_boot()"); + } + } + (void) rb_thread_call_without_gvl(nxt_ruby_unit_run, ctx, nxt_ruby_ubf, ctx); + if (nxt_ruby_hook_procs != Qnil) { + rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state); + if (nxt_slow_path(state != 0)) { + nxt_ruby_exception_log(NULL, NXT_LOG_ERR, + "Failed to call on_thread_shutdown()"); + } + } + nxt_unit_done(ctx); fail: -- cgit From 6c14d5d7b1921bd78f2d1b7458eae7d97eee0fcd Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Fri, 2 Jul 2021 13:00:04 +0000 Subject: Tests: run Ruby applications inside temporary directory. --- test/test_ruby_isolation.py | 7 ------- test/unit/applications/lang/ruby.py | 16 +++++++++++++++- test/unit/check/isolation.py | 7 +++++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py index 8443d857..f414d610 100644 --- a/test/test_ruby_isolation.py +++ b/test/test_ruby_isolation.py @@ -35,13 +35,6 @@ class TestRubyIsolation(TestApplicationRuby): 'pid': True, } - os.mkdir(option.temp_dir + '/ruby') - - shutil.copytree( - option.test_dir + '/ruby/status_int', - option.temp_dir + '/ruby/status_int', - ) - self.load('status_int', isolation=isolation) assert 'success' in self.conf( diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py index 02644584..d95d62b4 100644 --- a/test/unit/applications/lang/ruby.py +++ b/test/unit/applications/lang/ruby.py @@ -1,12 +1,26 @@ +import os +import shutil + from unit.applications.proto import TestApplicationProto from unit.option import option +from unit.utils import public_dir class TestApplicationRuby(TestApplicationProto): application_type = "ruby" + def prepare_env(self, script): + shutil.copytree( + option.test_dir + '/ruby/' + script, + option.temp_dir + '/ruby/' + script + ) + + public_dir(option.temp_dir + '/ruby/' + script) + def load(self, script, name='config.ru', **kwargs): - script_path = option.test_dir + '/ruby/' + script + self.prepare_env(script) + + script_path = option.temp_dir + '/ruby/' + script self._load_conf( { diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py index 7c83ae35..43c8842f 100644 --- a/test/unit/check/isolation.py +++ b/test/unit/check/isolation.py @@ -3,6 +3,7 @@ import os from unit.applications.lang.go import TestApplicationGo from unit.applications.lang.java import TestApplicationJava +from unit.applications.lang.ruby import TestApplicationRuby from unit.applications.lang.node import TestApplicationNode from unit.applications.proto import TestApplicationProto from unit.http import TestHTTP @@ -65,14 +66,16 @@ def check_isolation(): } elif 'ruby' in available['modules']: + TestApplicationRuby().prepare_env('empty') + conf = { "listeners": {"*:7080": {"pass": "applications/empty"}}, "applications": { "empty": { "type": "ruby", "processes": {"spare": 0}, - "working_directory": option.test_dir + "/ruby/empty", - "script": option.test_dir + "/ruby/empty/config.ru", + "working_directory": option.temp_dir + "/ruby/empty", + "script": option.temp_dir + "/ruby/empty/config.ru", "isolation": {"namespaces": {"credential": True}}, } }, -- cgit From 8c83652c2a0ad7386e27a9ea595c996d3dce018c Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Fri, 2 Jul 2021 13:00:57 +0000 Subject: Tests: Ruby hooks. --- test/ruby/hooks/config.ru | 7 +++ test/ruby/hooks/eval.rb | 3 ++ test/ruby/hooks/multiple.rb | 9 ++++ test/ruby/hooks/on_thread_boot.rb | 5 ++ test/ruby/hooks/on_thread_shutdown.rb | 5 ++ test/ruby/hooks/on_worker_boot.rb | 5 ++ test/ruby/hooks/on_worker_shutdown.rb | 5 ++ test/test_ruby_hooks.py | 98 +++++++++++++++++++++++++++++++++++ test/unit/applications/lang/ruby.py | 24 +++++---- test/unit/applications/proto.py | 16 +++--- test/unit/utils.py | 28 +++++++--- 11 files changed, 183 insertions(+), 22 deletions(-) create mode 100644 test/ruby/hooks/config.ru create mode 100644 test/ruby/hooks/eval.rb create mode 100644 test/ruby/hooks/multiple.rb create mode 100644 test/ruby/hooks/on_thread_boot.rb create mode 100644 test/ruby/hooks/on_thread_shutdown.rb create mode 100644 test/ruby/hooks/on_worker_boot.rb create mode 100644 test/ruby/hooks/on_worker_shutdown.rb create mode 100644 test/test_ruby_hooks.py diff --git a/test/ruby/hooks/config.ru b/test/ruby/hooks/config.ru new file mode 100644 index 00000000..f3069558 --- /dev/null +++ b/test/ruby/hooks/config.ru @@ -0,0 +1,7 @@ +app = Proc.new do |env| + ['200', { + 'Content-Length' => '0' + }, ['']] +end + +run app diff --git a/test/ruby/hooks/eval.rb b/test/ruby/hooks/eval.rb new file mode 100644 index 00000000..ce7329c1 --- /dev/null +++ b/test/ruby/hooks/eval.rb @@ -0,0 +1,3 @@ +require 'securerandom' + +File.write("./cookie_eval.#{SecureRandom.hex}", "evaluated") diff --git a/test/ruby/hooks/multiple.rb b/test/ruby/hooks/multiple.rb new file mode 100644 index 00000000..295b46a9 --- /dev/null +++ b/test/ruby/hooks/multiple.rb @@ -0,0 +1,9 @@ +require 'securerandom' + +on_worker_boot do + File.write("./cookie_worker_boot.#{SecureRandom.hex}", "worker booted") +end + +on_thread_boot do + File.write("./cookie_thread_boot.#{SecureRandom.hex}", "thread booted") +end diff --git a/test/ruby/hooks/on_thread_boot.rb b/test/ruby/hooks/on_thread_boot.rb new file mode 100644 index 00000000..1e05e248 --- /dev/null +++ b/test/ruby/hooks/on_thread_boot.rb @@ -0,0 +1,5 @@ +require 'securerandom' + +on_thread_boot do + File.write("./cookie_thread_boot.#{SecureRandom.hex}", "booted") +end diff --git a/test/ruby/hooks/on_thread_shutdown.rb b/test/ruby/hooks/on_thread_shutdown.rb new file mode 100644 index 00000000..e65c1b42 --- /dev/null +++ b/test/ruby/hooks/on_thread_shutdown.rb @@ -0,0 +1,5 @@ +require 'securerandom' + +on_thread_shutdown do + File.write("./cookie_thread_shutdown.#{SecureRandom.hex}", "shutdown") +end diff --git a/test/ruby/hooks/on_worker_boot.rb b/test/ruby/hooks/on_worker_boot.rb new file mode 100644 index 00000000..b6529f60 --- /dev/null +++ b/test/ruby/hooks/on_worker_boot.rb @@ -0,0 +1,5 @@ +require 'securerandom' + +on_worker_boot do + File.write("./cookie_worker_boot.#{SecureRandom.hex}", "booted") +end diff --git a/test/ruby/hooks/on_worker_shutdown.rb b/test/ruby/hooks/on_worker_shutdown.rb new file mode 100644 index 00000000..9ffaad93 --- /dev/null +++ b/test/ruby/hooks/on_worker_shutdown.rb @@ -0,0 +1,5 @@ +require 'securerandom' + +on_worker_shutdown do + File.write("./cookie_worker_shutdown.#{SecureRandom.hex}", "shutdown") +end diff --git a/test/test_ruby_hooks.py b/test/test_ruby_hooks.py new file mode 100644 index 00000000..af8ce337 --- /dev/null +++ b/test/test_ruby_hooks.py @@ -0,0 +1,98 @@ +import os +import time +from pathlib import Path + +import pytest + +from conftest import unit_stop +from unit.applications.lang.ruby import TestApplicationRuby +from unit.option import option +from unit.utils import waitforglob + + +class TestRubyHooks(TestApplicationRuby): + prerequisites = {'modules': {'ruby': 'all'}} + + def _wait_cookie(self, pattern, count): + return waitforglob( + option.temp_dir + '/ruby/hooks/cookie_' + pattern, count + ) + + def test_ruby_hooks_eval(self): + processes = 2 + + self.load('hooks', processes=processes, hooks='eval.rb') + + hooked = self._wait_cookie('eval.*', processes) + + assert hooked, 'hooks evaluated' + + def test_ruby_hooks_on_worker_boot(self): + processes = 2 + + self.load('hooks', processes=processes, hooks='on_worker_boot.rb') + + hooked = self._wait_cookie('worker_boot.*', processes) + + assert hooked, 'on_worker_boot called' + + def test_ruby_hooks_on_worker_shutdown(self): + processes = 2 + + self.load('hooks', processes=processes, hooks='on_worker_shutdown.rb') + + assert self.get()['status'] == 200, 'app response' + + self.load('empty') + + hooked = self._wait_cookie('worker_shutdown.*', processes) + + assert hooked, 'on_worker_shutdown called' + + def test_ruby_hooks_on_thread_boot(self): + processes = 1 + threads = 2 + + self.load( + 'hooks', + processes=processes, + threads=threads, + hooks='on_thread_boot.rb', + ) + + hooked = self._wait_cookie('thread_boot.*', processes * threads) + + assert hooked, 'on_thread_boot called' + + def test_ruby_hooks_on_thread_shutdown(self): + processes = 1 + threads = 2 + + self.load( + 'hooks', + processes=processes, + threads=threads, + hooks='on_thread_shutdown.rb', + ) + + assert self.get()['status'] == 200, 'app response' + + self.load('empty') + + hooked = self._wait_cookie('thread_shutdown.*', processes * threads) + + assert hooked, 'on_thread_shutdown called' + + def test_ruby_hooks_multiple(self): + processes = 1 + threads = 1 + + self.load( + 'hooks', processes=processes, threads=threads, hooks='multiple.rb', + ) + + hooked = self._wait_cookie('worker_boot.*', processes) + assert hooked, 'on_worker_boot called' + + hooked = self._wait_cookie('thread_boot.*', threads) + assert hooked, 'on_thread_boot called' diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py index d95d62b4..61d50558 100644 --- a/test/unit/applications/lang/ruby.py +++ b/test/unit/applications/lang/ruby.py @@ -12,7 +12,7 @@ class TestApplicationRuby(TestApplicationProto): def prepare_env(self, script): shutil.copytree( option.test_dir + '/ruby/' + script, - option.temp_dir + '/ruby/' + script + option.temp_dir + '/ruby/' + script, ) public_dir(option.temp_dir + '/ruby/' + script) @@ -22,17 +22,23 @@ class TestApplicationRuby(TestApplicationProto): script_path = option.temp_dir + '/ruby/' + script + app = { + "type": self.get_application_type(), + "processes": {"spare": 0}, + "working_directory": script_path, + "script": script_path + '/' + name, + } + + for key in [ + 'hooks', + ]: + if key in kwargs: + app[key] = kwargs[key] + self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + script}}, - "applications": { - script: { - "type": self.get_application_type(), - "processes": {"spare": 0}, - "working_directory": script_path, - "script": script_path + '/' + name, - } - }, + "applications": {script: app}, }, **kwargs ) diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index 92754c03..e30d21ff 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -47,13 +47,15 @@ class TestApplicationProto(TestControl): if 'applications' in conf: for app in conf['applications'].keys(): app_conf = conf['applications'][app] - if 'user' in kwargs: - app_conf['user'] = kwargs['user'] - if 'group' in kwargs: - app_conf['group'] = kwargs['group'] - - if 'isolation' in kwargs: - app_conf['isolation'] = kwargs['isolation'] + for key in [ + 'user', + 'group', + 'isolation', + 'processes', + 'threads', + ]: + if key in kwargs: + app_conf[key] = kwargs[key] assert 'success' in self.conf(conf), 'load application configuration' diff --git a/test/unit/utils.py b/test/unit/utils.py index a627e9f5..43aaa81b 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -1,3 +1,4 @@ +import glob import os import socket import subprocess @@ -16,8 +17,8 @@ def public_dir(path): os.chmod(os.path.join(root, f), 0o777) -def waitforfiles(*files): - for i in range(50): +def waitforfiles(*files, timeout=50): + for i in range(timeout): wait = False for f in files: @@ -33,6 +34,21 @@ def waitforfiles(*files): return False +def waitforglob(pattern, count=1, timeout=50): + for i in range(timeout): + n = 0 + + for f in glob.glob(pattern): + n += 1 + + if n == count: + return True + + time.sleep(0.1) + + return False + + def waitforsocket(port): for i in range(50): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: @@ -72,8 +88,8 @@ def sysctl(): return out -def waitformount(template, wait=50): - for i in range(wait): +def waitformount(template, timeout=50): + for i in range(timeout): if findmnt().find(template) != -1: return True @@ -82,8 +98,8 @@ def waitformount(template, wait=50): return False -def waitforunmount(template, wait=50): - for i in range(wait): +def waitforunmount(template, timeout=50): + for i in range(timeout): if findmnt().find(template) == -1: return True -- cgit From bc849920754b7b84a34ea46de4d42fa33b507c93 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Sat, 3 Jul 2021 19:15:04 +0100 Subject: Tests: address configuration tests reworked. --- test/test_configuration.py | 79 ++++++++++++---------------------------------- 1 file changed, 21 insertions(+), 58 deletions(-) diff --git a/test/test_configuration.py b/test/test_configuration.py index c149658c..8655968f 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -8,6 +8,15 @@ from unit.control import TestControl class TestConfiguration(TestControl): prerequisites = {'modules': {'python': 'any'}} + def try_addr(self, addr): + return self.conf( + { + "listeners": {addr: {"pass": "routes"}}, + "routes": [{"action": {"return": 200}}], + "applications": {}, + } + ) + def test_json_empty(self): assert 'error' in self.conf(''), 'empty' @@ -218,50 +227,20 @@ class TestConfiguration(TestControl): {"*:7080": {"pass": "applications/app"}}, 'listeners' ), 'listeners no app' - def test_listeners_wildcard(self): - assert 'success' in self.conf( - { - "listeners": {"*:7080": {"pass": "applications/app"}}, - "applications": { - "app": { - "type": "python", - "processes": {"spare": 0}, - "path": "/app", - "module": "wsgi", - } - }, - } - ), 'listeners wildcard' + def test_listeners_addr(self): + assert 'success' in self.try_addr("*:7080"), 'wildcard' + assert 'success' in self.try_addr("127.0.0.1:7081"), 'explicit' + assert 'success' in self.try_addr("[::1]:7082"), 'explicit ipv6' - def test_listeners_explicit(self): - assert 'success' in self.conf( - { - "listeners": {"127.0.0.1:7080": {"pass": "applications/app"}}, - "applications": { - "app": { - "type": "python", - "processes": {"spare": 0}, - "path": "/app", - "module": "wsgi", - } - }, - } - ), 'explicit' + def test_listeners_addr_error(self): + assert 'error' in self.try_addr("127.0.0.1"), 'no port' - def test_listeners_explicit_ipv6(self): - assert 'success' in self.conf( - { - "listeners": {"[::1]:7080": {"pass": "applications/app"}}, - "applications": { - "app": { - "type": "python", - "processes": {"spare": 0}, - "path": "/app", - "module": "wsgi", - } - }, - } - ), 'explicit ipv6' + def test_listeners_addr_error_2(self, skip_alert): + skip_alert(r'bind.*failed', r'failed to apply new conf') + + assert 'error' in self.try_addr( + "[f607:7403:1e4b:6c66:33b2:843f:2517:da27]:7080" + ) def test_listeners_port_release(self): for i in range(10): @@ -290,22 +269,6 @@ class TestConfiguration(TestControl): assert 'success' in resp, 'port release' - @pytest.mark.skip('not yet, unsafe') - def test_listeners_no_port(self): - assert 'error' in self.conf( - { - "listeners": {"127.0.0.1": {"pass": "applications/app"}}, - "applications": { - "app": { - "type": "python", - "processes": {"spare": 0}, - "path": "/app", - "module": "wsgi", - } - }, - } - ), 'no port' - def test_json_application_name_large(self): name = "X" * 1024 * 1024 -- cgit From 4f94df6a71d60e3930d3d5f1dd1301ba43778618 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Tue, 6 Jul 2021 12:22:10 +0100 Subject: Tests: print_log_on_assert() decorator introduced. --- test/conftest.py | 155 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 82 insertions(+), 73 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index db34984f..87471287 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -74,7 +74,7 @@ def pytest_addoption(parser): unit_instance = {} _processes = [] -_fds_check = { +_fds_info = { 'main': {'fds': 0, 'skip': False}, 'router': {'name': 'unit: router', 'pid': -1, 'fds': 0, 'skip': False}, 'controller': { @@ -115,6 +115,17 @@ def pytest_configure(config): fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0) +def print_log_on_assert(func): + def inner_function(*args, **kwargs): + try: + func(*args, **kwargs) + except AssertionError as e: + _print_log(kwargs.get('log', None)) + raise e + + return inner_function + + def pytest_generate_tests(metafunc): cls = metafunc.cls if ( @@ -275,9 +286,9 @@ def run(request): ] option.skip_sanitizer = False - _fds_check['main']['skip'] = False - _fds_check['router']['skip'] = False - _fds_check['controller']['skip'] = False + _fds_info['main']['skip'] = False + _fds_info['router']['skip'] = False + _fds_info['controller']['skip'] = False yield @@ -299,7 +310,7 @@ def run(request): # clean temp_dir before the next test if not option.restart: - _clear_conf(unit['temp_dir'] + '/control.unit.sock', log) + _clear_conf(unit['temp_dir'] + '/control.unit.sock', log=log) for item in os.listdir(unit['temp_dir']): if item not in [ @@ -319,51 +330,9 @@ def run(request): else: shutil.rmtree(path) - # check descriptors (wait for some time before check) - - def waitforfds(diff): - for i in range(600): - fds_diff = diff() - - if fds_diff <= option.fds_threshold: - break - - time.sleep(0.1) + # check descriptors - return fds_diff - - ps = _fds_check['main'] - if not ps['skip']: - fds_diff = waitforfds( - lambda: _count_fds(unit_instance['pid']) - ps['fds'] - ) - ps['fds'] += fds_diff - - assert ( - fds_diff <= option.fds_threshold - ), 'descriptors leak main process' - - else: - ps['fds'] = _count_fds(unit_instance['pid']) - - for name in ['controller', 'router']: - ps = _fds_check[name] - ps_pid = ps['pid'] - ps['pid'] = pid_by_name(ps['name']) - - if not ps['skip']: - fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds']) - ps['fds'] += fds_diff - - if not option.restart: - assert ps['pid'] == ps_pid, 'same pid %s' % name - - assert fds_diff <= option.fds_threshold, ( - 'descriptors leak %s' % name - ) - - else: - ps['fds'] = _count_fds(ps['pid']) + _check_fds(log=log) # print unit.log in case of error @@ -440,13 +409,13 @@ def unit_run(): _clear_conf(unit_instance['temp_dir'] + '/control.unit.sock') - _fds_check['main']['fds'] = _count_fds(unit_instance['pid']) + _fds_info['main']['fds'] = _count_fds(unit_instance['pid']) - router = _fds_check['router'] + router = _fds_info['router'] router['pid'] = pid_by_name(router['name']) router['fds'] = _count_fds(router['pid']) - controller = _fds_check['controller'] + controller = _fds_info['controller'] controller['pid'] = pid_by_name(controller['name']) controller['fds'] = _count_fds(controller['pid']) @@ -481,7 +450,8 @@ def unit_stop(): return 'Could not terminate unit' -def _check_alerts(log=None): +@print_log_on_assert +def _check_alerts(*, log=None): if log is None: with Log.open(encoding='utf-8') as f: log = f.read() @@ -499,22 +469,18 @@ def _check_alerts(log=None): for skip in option.skip_alerts: alerts = [al for al in alerts if re.search(skip, al) is None] - if alerts: - _print_log(log) - assert not alerts, 'alert(s)' + assert not alerts, 'alert(s)' if not option.skip_sanitizer: sanitizer_errors = re.findall('.+Sanitizer.+', log) - if sanitizer_errors: - _print_log(log) - assert not sanitizer_errors, 'sanitizer error(s)' + assert not sanitizer_errors, 'sanitizer error(s)' if found: print('skipped.') -def _print_log(data=None): +def _print_log(log): path = Log.get_path() print('Path to unit.log:\n' + path + '\n') @@ -523,19 +489,15 @@ def _print_log(data=None): os.set_blocking(sys.stdout.fileno(), True) sys.stdout.flush() - if data is None: + if log is None: with open(path, 'r', encoding='utf-8', errors='ignore') as f: shutil.copyfileobj(f, sys.stdout) else: - sys.stdout.write(data) - + sys.stdout.write(log) -def _clear_conf(sock, log=None): - def check_success(resp): - if 'success' not in resp: - _print_log(log) - assert 'success' in resp +@print_log_on_assert +def _clear_conf(sock, *, log=None): resp = http.put( url='/config', sock_type='unix', @@ -543,7 +505,7 @@ def _clear_conf(sock, log=None): body=json.dumps({"listeners": {}, "applications": {}}), )['body'] - check_success(resp) + assert 'success' in resp, 'clear conf' if 'openssl' not in option.available['modules']: return @@ -561,7 +523,54 @@ def _clear_conf(sock, log=None): url='/certificates/' + cert, sock_type='unix', addr=sock, )['body'] - check_success(resp) + assert 'success' in resp, 'remove certificate' + + +@print_log_on_assert +def _check_fds(*, log=None): + def waitforfds(diff): + for i in range(600): + fds_diff = diff() + + if fds_diff <= option.fds_threshold: + break + + time.sleep(0.1) + + return fds_diff + + ps = _fds_info['main'] + if not ps['skip']: + fds_diff = waitforfds( + lambda: _count_fds(unit_instance['pid']) - ps['fds'] + ) + ps['fds'] += fds_diff + + assert ( + fds_diff <= option.fds_threshold + ), 'descriptors leak main process' + + else: + ps['fds'] = _count_fds(unit_instance['pid']) + + for name in ['controller', 'router']: + ps = _fds_info[name] + ps_pid = ps['pid'] + ps['pid'] = pid_by_name(ps['name']) + + if not ps['skip']: + fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds']) + ps['fds'] += fds_diff + + if not option.restart: + assert ps['pid'] == ps_pid, 'same pid %s' % name + + assert fds_diff <= option.fds_threshold, ( + 'descriptors leak %s' % name + ) + + else: + ps['fds'] = _count_fds(ps['pid']) def _count_fds(pid): @@ -639,9 +648,9 @@ def skip_alert(): @pytest.fixture() def skip_fds_check(): def _skip(main=False, router=False, controller=False): - _fds_check['main']['skip'] = main - _fds_check['router']['skip'] = router - _fds_check['controller']['skip'] = controller + _fds_info['main']['skip'] = main + _fds_info['router']['skip'] = router + _fds_info['controller']['skip'] = controller return _skip -- cgit From daa051e7e7266325ef38a606b3aee4377a73f0d0 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Mon, 19 Jul 2021 16:23:13 +0300 Subject: Router: fixing assertion on app thread port handle. A new application thread port message can be processed in the router after the application is removed from the router. Assertion for this case is replaced by a condition to store the new thread port until receiving the stop notification from the application process. --- docs/changes.xml | 7 +++++++ src/nxt_router.c | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 5265d529..dd8fb731 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -44,6 +44,13 @@ with TLS certificate configured; the bug had appeared in 1.23.0. + + +the router process could crash on rapid mutithreaded application +reconfiguration. + + + a descriptor and memory leak occurred in the router process when an app diff --git a/src/nxt_router.c b/src/nxt_router.c index 26b846b0..409f88a1 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -680,18 +680,20 @@ nxt_router_new_port_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) nxt_assert(main_app_port != NULL); app = main_app_port->app; - nxt_assert(app != NULL); - nxt_thread_mutex_lock(&app->mutex); + if (nxt_fast_path(app != NULL)) { + nxt_thread_mutex_lock(&app->mutex); - /* TODO here should be find-and-add code because there can be - port waiters in port_hash */ - nxt_port_hash_add(&app->port_hash, port); - app->port_hash_count++; + /* TODO here should be find-and-add code because there can be + port waiters in port_hash */ + nxt_port_hash_add(&app->port_hash, port); + app->port_hash_count++; - nxt_thread_mutex_unlock(&app->mutex); + nxt_thread_mutex_unlock(&app->mutex); + + port->app = app; + } - port->app = app; port->main_app_port = main_app_port; nxt_port_socket_write(task, port, NXT_PORT_MSG_PORT_ACK, -1, 0, 0, NULL); -- cgit From 567545213d95e608b54ce92bfc33fac4327a9f93 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 20 Jul 2021 10:37:50 +0300 Subject: Python: fixing ASGI receive() issues. The receive() call never blocks for a GET request and always returns the same empty body message. The Starlette framework creates a separate task when receive() is called in a loop until an 'http.disconnect' message is received. The 'http.disconnect' message was previously issued after the response header had been sent. However, the correct behavior is to respond with 'http.disconnect' after sending the response is complete. This closes #564 issue on GitHub. --- docs/changes.xml | 7 ++++ src/python/nxt_python_asgi_http.c | 87 ++++++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index dd8fb731..68db823d 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -65,6 +65,13 @@ a full-form IPv6 in a listener address. + + +compatibility issues with some Python ASGI apps, notably based on Starlette +framework. + + + diff --git a/src/python/nxt_python_asgi_http.c b/src/python/nxt_python_asgi_http.c index d88c4b00..3074c09f 100644 --- a/src/python/nxt_python_asgi_http.c +++ b/src/python/nxt_python_asgi_http.c @@ -23,10 +23,11 @@ typedef struct { PyObject *send_future; uint64_t content_length; uint64_t bytes_sent; - int complete; - int closed; PyObject *send_body; Py_ssize_t send_body_off; + uint8_t complete; + uint8_t closed; + uint8_t empty_body_received; } nxt_py_asgi_http_t; @@ -37,6 +38,7 @@ static PyObject *nxt_py_asgi_http_response_start(nxt_py_asgi_http_t *http, PyObject *dict); static PyObject *nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict); +static void nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http); static PyObject *nxt_py_asgi_http_done(PyObject *self, PyObject *future); @@ -94,10 +96,11 @@ nxt_py_asgi_http_create(nxt_unit_request_info_t *req) http->send_future = NULL; http->content_length = -1; http->bytes_sent = 0; - http->complete = 0; - http->closed = 0; http->send_body = NULL; http->send_body_off = 0; + http->complete = 0; + http->closed = 0; + http->empty_body_received = 0; } return (PyObject *) http; @@ -117,7 +120,7 @@ nxt_py_asgi_http_receive(PyObject *self, PyObject *none) nxt_unit_req_debug(req, "asgi_http_receive"); - if (nxt_slow_path(http->closed || nxt_unit_response_is_sent(req))) { + if (nxt_slow_path(http->closed || http->complete )) { msg = nxt_py_asgi_new_msg(req, nxt_py_http_disconnect_str); } else { @@ -171,6 +174,14 @@ nxt_py_asgi_http_read_msg(nxt_py_asgi_http_t *http) size = nxt_py_asgi_http_body_buf_size; } + if (size == 0) { + if (http->empty_body_received) { + Py_RETURN_NONE; + } + + http->empty_body_received = 1; + } + if (size > 0) { body = PyBytes_FromStringAndSize(NULL, size); if (nxt_slow_path(body == NULL)) { @@ -442,6 +453,8 @@ nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict) if (more_body == NULL || more_body == Py_False) { http->complete = 1; + + nxt_py_asgi_http_emit_disconnect(http); } Py_INCREF(http); @@ -449,6 +462,41 @@ nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict) } +static void +nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http) +{ + PyObject *msg, *future, *res; + + if (http->receive_future == NULL) { + return; + } + + msg = nxt_py_asgi_new_msg(http->req, nxt_py_http_disconnect_str); + if (nxt_slow_path(msg == NULL)) { + return; + } + + if (msg == Py_None) { + Py_DECREF(msg); + return; + } + + future = http->receive_future; + http->receive_future = NULL; + + res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg, NULL); + if (nxt_slow_path(res == NULL)) { + nxt_unit_req_alert(http->req, "'set_result' call failed"); + nxt_python_print_exception(); + } + + Py_XDECREF(res); + Py_DECREF(future); + + Py_DECREF(msg); +} + + void nxt_py_asgi_http_data_handler(nxt_unit_request_info_t *req) { @@ -573,7 +621,6 @@ fail: void nxt_py_asgi_http_close_handler(nxt_unit_request_info_t *req) { - PyObject *msg, *future, *res; nxt_py_asgi_http_t *http; http = req->data; @@ -582,33 +629,7 @@ nxt_py_asgi_http_close_handler(nxt_unit_request_info_t *req) http->closed = 1; - if (http->receive_future == NULL) { - return; - } - - msg = nxt_py_asgi_new_msg(req, nxt_py_http_disconnect_str); - if (nxt_slow_path(msg == NULL)) { - return; - } - - if (msg == Py_None) { - Py_DECREF(msg); - return; - } - - future = http->receive_future; - http->receive_future = NULL; - - res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg, NULL); - if (nxt_slow_path(res == NULL)) { - nxt_unit_req_alert(req, "'set_result' call failed"); - nxt_python_print_exception(); - } - - Py_XDECREF(res); - Py_DECREF(future); - - Py_DECREF(msg); + nxt_py_asgi_http_emit_disconnect(http); } -- cgit From dfbdc1c11a201e46d61f4bc61cfbe5741fc4fd70 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 20 Jul 2021 10:37:53 +0300 Subject: Python: fixing exceptions in Future.set_result for ASGI implementation. An ASGI application can cancel the Future object returned by the receive() call. In this case, Unit's ASGI implementation should not call set_result() because the Future is already handled. In particular, the Starlette framework was noted to cancel the received Future. This patch adds a done() check for the Future before attempting a set_result(). This is related to #564 issue on GitHub. --- src/python/nxt_python_asgi_http.c | 55 +++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/python/nxt_python_asgi_http.c b/src/python/nxt_python_asgi_http.c index 3074c09f..c4a77d53 100644 --- a/src/python/nxt_python_asgi_http.c +++ b/src/python/nxt_python_asgi_http.c @@ -39,6 +39,8 @@ static PyObject *nxt_py_asgi_http_response_start(nxt_py_asgi_http_t *http, static PyObject *nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict); static void nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http); +static void nxt_py_asgi_http_set_result(nxt_py_asgi_http_t *http, + PyObject *future, PyObject *msg); static PyObject *nxt_py_asgi_http_done(PyObject *self, PyObject *future); @@ -465,7 +467,7 @@ nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict) static void nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http) { - PyObject *msg, *future, *res; + PyObject *msg, *future; if (http->receive_future == NULL) { return; @@ -484,23 +486,45 @@ nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http) future = http->receive_future; http->receive_future = NULL; - res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg, NULL); + nxt_py_asgi_http_set_result(http, future, msg); + + Py_DECREF(msg); +} + + +static void +nxt_py_asgi_http_set_result(nxt_py_asgi_http_t *http, PyObject *future, + PyObject *msg) +{ + PyObject *res; + + res = PyObject_CallMethodObjArgs(future, nxt_py_done_str, NULL); if (nxt_slow_path(res == NULL)) { - nxt_unit_req_alert(http->req, "'set_result' call failed"); + nxt_unit_req_alert(http->req, "'done' call failed"); nxt_python_print_exception(); } + if (nxt_fast_path(res == Py_False)) { + res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg, + NULL); + if (nxt_slow_path(res == NULL)) { + nxt_unit_req_alert(http->req, "'set_result' call failed"); + nxt_python_print_exception(); + } + + } else { + res = NULL; + } + Py_XDECREF(res); Py_DECREF(future); - - Py_DECREF(msg); } void nxt_py_asgi_http_data_handler(nxt_unit_request_info_t *req) { - PyObject *msg, *future, *res; + PyObject *msg, *future; nxt_py_asgi_http_t *http; http = req->data; @@ -524,14 +548,7 @@ nxt_py_asgi_http_data_handler(nxt_unit_request_info_t *req) future = http->receive_future; http->receive_future = NULL; - res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg, NULL); - if (nxt_slow_path(res == NULL)) { - nxt_unit_req_alert(req, "'set_result' call failed"); - nxt_python_print_exception(); - } - - Py_XDECREF(res); - Py_DECREF(future); + nxt_py_asgi_http_set_result(http, future, msg); Py_DECREF(msg); } @@ -575,15 +592,7 @@ nxt_py_asgi_http_drain(nxt_queue_link_t *lnk) future = http->send_future; http->send_future = NULL; - res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, Py_None, - NULL); - if (nxt_slow_path(res == NULL)) { - nxt_unit_req_alert(http->req, "'set_result' call failed"); - nxt_python_print_exception(); - } - - Py_XDECREF(res); - Py_DECREF(future); + nxt_py_asgi_http_set_result(http, future, Py_None); return NXT_UNIT_OK; -- cgit From f27fbd9b4d2bdaddf1e7001d0d0bc5586ba04cd4 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 20 Jul 2021 10:37:54 +0300 Subject: Python: using default event_loop for main thread for ASGI. Unit's ASGI implementation creates a new event loop to run an application for each thread since 542b5b8c0647. This may cause unexpected exceptions or strange bugs if asyncio synchronisation primitives are initialised before the application starts (e.g. globally). Although the approach with a new event loop for the main thread is consistent and helps to prepare the application to run in multiple threads, it can be a source of pain for people who just want to run single-threaded ASGI applications in Unit. This is related to #560 issue on GitHub. --- src/python/nxt_python.c | 4 ++-- src/python/nxt_python.h | 2 +- src/python/nxt_python_asgi.c | 28 +++++++++++++++++----------- src/python/nxt_python_wsgi.c | 4 ++-- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c index 588a147a..bdce68b2 100644 --- a/src/python/nxt_python.c +++ b/src/python/nxt_python.c @@ -264,7 +264,7 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) goto fail; } - rc = nxt_py_proto.ctx_data_alloc(&python_init.ctx_data); + rc = nxt_py_proto.ctx_data_alloc(&python_init.ctx_data, 1); if (nxt_slow_path(rc != NXT_UNIT_OK)) { goto fail; } @@ -504,7 +504,7 @@ nxt_python_init_threads(nxt_python_app_conf_t *c) for (i = 0; i < c->threads - 1; i++) { ti = &nxt_py_threads[i]; - res = nxt_py_proto.ctx_data_alloc(&ti->ctx_data); + res = nxt_py_proto.ctx_data_alloc(&ti->ctx_data, 0); if (nxt_slow_path(res != NXT_UNIT_OK)) { return NXT_UNIT_ERROR; } diff --git a/src/python/nxt_python.h b/src/python/nxt_python.h index a5c1d9a6..e4eac9dc 100644 --- a/src/python/nxt_python.h +++ b/src/python/nxt_python.h @@ -60,7 +60,7 @@ typedef struct { typedef struct { - int (*ctx_data_alloc)(void **pdata); + int (*ctx_data_alloc)(void **pdata, int main); void (*ctx_data_free)(void *data); int (*startup)(void *data); int (*run)(nxt_unit_ctx_t *ctx); diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c index 1d220678..26003805 100644 --- a/src/python/nxt_python_asgi.c +++ b/src/python/nxt_python_asgi.c @@ -17,7 +17,7 @@ static PyObject *nxt_python_asgi_get_func(PyObject *obj); -static int nxt_python_asgi_ctx_data_alloc(void **pdata); +static int nxt_python_asgi_ctx_data_alloc(void **pdata, int main); static void nxt_python_asgi_ctx_data_free(void *data); static int nxt_python_asgi_startup(void *data); static int nxt_python_asgi_run(nxt_unit_ctx_t *ctx); @@ -194,10 +194,11 @@ nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto) static int -nxt_python_asgi_ctx_data_alloc(void **pdata) +nxt_python_asgi_ctx_data_alloc(void **pdata, int main) { uint32_t i; - PyObject *asyncio, *loop, *new_event_loop, *obj; + PyObject *asyncio, *loop, *event_loop, *obj; + const char *event_loop_func; nxt_py_asgi_ctx_data_t *ctx_data; ctx_data = nxt_unit_malloc(NULL, sizeof(nxt_py_asgi_ctx_data_t)); @@ -232,23 +233,28 @@ nxt_python_asgi_ctx_data_alloc(void **pdata) goto fail; } - new_event_loop = PyDict_GetItemString(PyModule_GetDict(asyncio), - "new_event_loop"); - if (nxt_slow_path(new_event_loop == NULL)) { + event_loop_func = main ? "get_event_loop" : "new_event_loop"; + + event_loop = PyDict_GetItemString(PyModule_GetDict(asyncio), + event_loop_func); + if (nxt_slow_path(event_loop == NULL)) { nxt_unit_alert(NULL, - "Python failed to get 'new_event_loop' from module 'asyncio'"); + "Python failed to get '%s' from module 'asyncio'", + event_loop_func); goto fail; } - if (nxt_slow_path(PyCallable_Check(new_event_loop) == 0)) { + if (nxt_slow_path(PyCallable_Check(event_loop) == 0)) { nxt_unit_alert(NULL, - "'asyncio.new_event_loop' is not a callable object"); + "'asyncio.%s' is not a callable object", + event_loop_func); goto fail; } - loop = PyObject_CallObject(new_event_loop, NULL); + loop = PyObject_CallObject(event_loop, NULL); if (nxt_slow_path(loop == NULL)) { - nxt_unit_alert(NULL, "Python failed to call 'asyncio.new_event_loop'"); + nxt_unit_alert(NULL, "Python failed to call 'asyncio.%s'", + event_loop_func); goto fail; } diff --git a/src/python/nxt_python_wsgi.c b/src/python/nxt_python_wsgi.c index b80d10fa..87dcfaa2 100644 --- a/src/python/nxt_python_wsgi.c +++ b/src/python/nxt_python_wsgi.c @@ -51,7 +51,7 @@ typedef struct { } nxt_python_ctx_t; -static int nxt_python_wsgi_ctx_data_alloc(void **pdata); +static int nxt_python_wsgi_ctx_data_alloc(void **pdata, int main); static void nxt_python_wsgi_ctx_data_free(void *data); static int nxt_python_wsgi_run(nxt_unit_ctx_t *ctx); static void nxt_python_wsgi_done(void); @@ -210,7 +210,7 @@ fail: static int -nxt_python_wsgi_ctx_data_alloc(void **pdata) +nxt_python_wsgi_ctx_data_alloc(void **pdata, int main) { nxt_python_ctx_t *pctx; -- cgit From 1f2ba4dca8c67442e19367ac7f1f96dbff6457ff Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Wed, 21 Jul 2021 14:53:33 +0000 Subject: Tests: use mutex with multitthreaded Ruby hooks. This commit fixes a rare crash that can occur when File.write is called by many threads. --- test/ruby/hooks/multiple.rb | 6 +++++- test/ruby/hooks/on_thread_boot.rb | 6 +++++- test/ruby/hooks/on_thread_shutdown.rb | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/test/ruby/hooks/multiple.rb b/test/ruby/hooks/multiple.rb index 295b46a9..b1b659a5 100644 --- a/test/ruby/hooks/multiple.rb +++ b/test/ruby/hooks/multiple.rb @@ -1,9 +1,13 @@ require 'securerandom' +@mutex = Mutex.new + on_worker_boot do File.write("./cookie_worker_boot.#{SecureRandom.hex}", "worker booted") end on_thread_boot do - File.write("./cookie_thread_boot.#{SecureRandom.hex}", "thread booted") + @mutex.synchronize do + File.write("./cookie_thread_boot.#{SecureRandom.hex}", "thread booted") + end end diff --git a/test/ruby/hooks/on_thread_boot.rb b/test/ruby/hooks/on_thread_boot.rb index 1e05e248..4f88424e 100644 --- a/test/ruby/hooks/on_thread_boot.rb +++ b/test/ruby/hooks/on_thread_boot.rb @@ -1,5 +1,9 @@ require 'securerandom' +@mutex = Mutex.new + on_thread_boot do - File.write("./cookie_thread_boot.#{SecureRandom.hex}", "booted") + @mutex.synchronize do + File.write("./cookie_thread_boot.#{SecureRandom.hex}", "booted") + end end diff --git a/test/ruby/hooks/on_thread_shutdown.rb b/test/ruby/hooks/on_thread_shutdown.rb index e65c1b42..d953b8b7 100644 --- a/test/ruby/hooks/on_thread_shutdown.rb +++ b/test/ruby/hooks/on_thread_shutdown.rb @@ -1,5 +1,9 @@ require 'securerandom' +@mutex = Mutex.new + on_thread_shutdown do - File.write("./cookie_thread_shutdown.#{SecureRandom.hex}", "shutdown") + @mutex.synchronize do + File.write("./cookie_thread_shutdown.#{SecureRandom.hex}", "shutdown") + end end -- cgit From c37ff7ed0ed06b0e928efdb217a8999ff3ff7f50 Mon Sep 17 00:00:00 2001 From: Andrey Suvorov Date: Wed, 21 Jul 2021 15:22:52 -0700 Subject: Enabling configure TLS sessions. To support TLS sessions, Unit uses the OpenSSL built-in session cache; the cache_size option defines the number sessions to store. To disable the feather, the option must be zero. --- docs/changes.xml | 6 +++++ src/nxt_conf_validation.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++ src/nxt_openssl.c | 36 +++++++++++++++++++++++------ src/nxt_router.c | 48 ++++++++++++++++++++++++++++---------- src/nxt_tls.h | 15 +++++++++--- 5 files changed, 142 insertions(+), 22 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 68db823d..2aa9bb65 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,12 @@ NGINX Unit updated to 1.25.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +TLS sessions cache. + + + process and thread lifecycle hooks in Ruby. diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index a16c955c..fd57a983 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -95,6 +95,10 @@ static nxt_int_t nxt_conf_vldt_object_conf_commands(nxt_conf_validation_t *vldt, #endif static nxt_int_t nxt_conf_vldt_certificate_element(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_tls_cache_size(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); #endif static nxt_int_t nxt_conf_vldt_action(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); @@ -206,6 +210,7 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_websocket_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_static_members[]; #if (NXT_TLS) static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[]; +static nxt_conf_vldt_object_t nxt_conf_vldt_session_members[]; #endif static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[]; @@ -378,11 +383,65 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = { .validator = nxt_conf_vldt_unsupported, .u.string = "conf_commands", #endif + }, { + .name = nxt_string("session"), + .type = NXT_CONF_VLDT_OBJECT, + .validator = nxt_conf_vldt_object, + .u.members = nxt_conf_vldt_session_members, }, NXT_CONF_VLDT_END }; + +static nxt_conf_vldt_object_t nxt_conf_vldt_session_members[] = { + { + .name = nxt_string("cache_size"), + .type = NXT_CONF_VLDT_INTEGER, + .validator = nxt_conf_vldt_tls_cache_size, + }, { + .name = nxt_string("timeout"), + .type = NXT_CONF_VLDT_INTEGER, + .validator = nxt_conf_vldt_tls_timeout, + }, + + NXT_CONF_VLDT_END +}; + + +static nxt_int_t +nxt_conf_vldt_tls_cache_size(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + int64_t cache_size; + + cache_size = nxt_conf_get_number(value); + + if (cache_size < 0) { + return nxt_conf_vldt_error(vldt, "The \"cache_size\" number must not " + "be negative."); + } + + return NXT_OK; +} + + +static nxt_int_t +nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + int64_t timeout; + + timeout = nxt_conf_get_number(value); + + if (timeout <= 0) { + return nxt_conf_vldt_error(vldt, "The \"timeout\" number must be " + "greater than zero."); + } + + return NXT_OK; +} + #endif diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index 2fd5d1bf..3b5d4fda 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -42,15 +42,16 @@ static void nxt_openssl_lock(int mode, int type, const char *file, int line); static unsigned long nxt_openssl_thread_id(void); static void nxt_openssl_locks_free(void); #endif -static nxt_int_t nxt_openssl_server_init(nxt_task_t *task, - nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_conf_value_t *conf_cmds, - nxt_bool_t last); +static nxt_int_t nxt_openssl_server_init(nxt_task_t *task, nxt_mp_t *mp, + nxt_tls_init_t *tls_init, nxt_bool_t last); static nxt_int_t nxt_openssl_chain_file(nxt_task_t *task, SSL_CTX *ctx, nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_bool_t single); #if (NXT_HAVE_OPENSSL_CONF_CMD) static nxt_int_t nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx, nxt_conf_value_t *value, nxt_mp_t *mp); #endif +static void nxt_ssl_session_cache(SSL_CTX *ctx, size_t cache_size, + time_t timeout); static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf, nxt_mp_t *mp); static nxt_int_t nxt_openssl_bundle_hash_test(nxt_lvlhsh_query_t *lhq, @@ -265,11 +266,12 @@ nxt_openssl_locks_free(void) static nxt_int_t -nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, - nxt_mp_t *mp, nxt_conf_value_t *conf_cmds, nxt_bool_t last) +nxt_openssl_server_init(nxt_task_t *task, nxt_mp_t *mp, + nxt_tls_init_t *tls_init, nxt_bool_t last) { SSL_CTX *ctx; const char *ciphers, *ca_certificate; + nxt_tls_conf_t *conf; STACK_OF(X509_NAME) *list; nxt_tls_bundle_conf_t *bundle; @@ -279,6 +281,8 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, return NXT_ERROR; } + conf = tls_init->conf; + bundle = conf->bundle; nxt_assert(bundle != NULL); @@ -337,13 +341,15 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, } #if (NXT_HAVE_OPENSSL_CONF_CMD) - if (conf_cmds != NULL - && nxt_ssl_conf_commands(task, ctx, conf_cmds, mp) != NXT_OK) + if (tls_init->conf_cmds != NULL + && nxt_ssl_conf_commands(task, ctx, tls_init->conf_cmds, mp) != NXT_OK) { goto fail; } #endif + nxt_ssl_session_cache(ctx, tls_init->cache_size, tls_init->timeout); + SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); if (conf->ca_certificate != NULL) { @@ -582,6 +588,22 @@ fail: #endif +static void +nxt_ssl_session_cache(SSL_CTX *ctx, size_t cache_size, time_t timeout) +{ + if (cache_size == 0) { + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + return; + } + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER); + + SSL_CTX_sess_set_cache_size(ctx, cache_size); + + SSL_CTX_set_timeout(ctx, (long) timeout); +} + + static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf, nxt_mp_t *mp) diff --git a/src/nxt_router.c b/src/nxt_router.c index 409f88a1..1156edb8 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -44,10 +44,10 @@ typedef struct { nxt_str_t name; nxt_socket_conf_t *socket_conf; nxt_router_temp_conf_t *temp_conf; - nxt_conf_value_t *conf_cmds; + nxt_tls_init_t *tls_init; nxt_bool_t last; - nxt_queue_link_t link; /* for nxt_socket_conf_t.tls */ + nxt_queue_link_t link; /* for nxt_socket_conf_t.tls */ } nxt_router_tlssock_t; #endif @@ -123,8 +123,8 @@ static void nxt_router_listen_socket_error(nxt_task_t *task, static void nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, - nxt_conf_value_t *value, nxt_socket_conf_t *skcf, - nxt_conf_value_t * conf_cmds, nxt_bool_t last); + nxt_conf_value_t *value, nxt_socket_conf_t *skcf, nxt_tls_init_t *tls_init, + nxt_bool_t last); #endif static void nxt_router_app_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_app_t *app); @@ -1341,7 +1341,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_router_t *router; nxt_app_joint_t *app_joint; #if (NXT_TLS) - nxt_conf_value_t *certificate, *conf_cmds; + nxt_tls_init_t *tls_init; + nxt_conf_value_t *certificate; #endif nxt_conf_value_t *conf, *http, *value, *websocket; nxt_conf_value_t *applications, *application; @@ -1363,6 +1364,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, #if (NXT_TLS) static nxt_str_t certificate_path = nxt_string("/tls/certificate"); static nxt_str_t conf_commands_path = nxt_string("/tls/conf_commands"); + static nxt_str_t conf_cache_path = nxt_string("/tls/session/cache_size"); + static nxt_str_t conf_timeout_path = nxt_string("/tls/session/timeout"); #endif static nxt_str_t static_path = nxt_string("/settings/http/static"); static nxt_str_t websocket_path = nxt_string("/settings/http/websocket"); @@ -1741,7 +1744,26 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, certificate = nxt_conf_get_path(listener, &certificate_path); if (certificate != NULL) { - conf_cmds = nxt_conf_get_path(listener, &conf_commands_path); + tls_init = nxt_mp_get(tmcf->mem_pool, sizeof(nxt_tls_init_t)); + if (nxt_slow_path(tls_init == NULL)) { + return NXT_ERROR; + } + + tls_init->cache_size = 0; + tls_init->timeout = 300; + + value = nxt_conf_get_path(listener, &conf_cache_path); + if (value != NULL) { + tls_init->cache_size = nxt_conf_get_number(value); + } + + value = nxt_conf_get_path(listener, &conf_timeout_path); + if (value != NULL) { + tls_init->timeout = nxt_conf_get_number(value); + } + + tls_init->conf_cmds = nxt_conf_get_path(listener, + &conf_commands_path); if (nxt_conf_type(certificate) == NXT_CONF_ARRAY) { n = nxt_conf_array_elements_count(certificate); @@ -1752,7 +1774,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_assert(value != NULL); ret = nxt_router_conf_tls_insert(tmcf, value, skcf, - conf_cmds, i == 0); + tls_init, i == 0); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -1761,7 +1783,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } else { /* NXT_CONF_STRING */ ret = nxt_router_conf_tls_insert(tmcf, certificate, skcf, - conf_cmds, 1); + tls_init, 1); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -1856,7 +1878,7 @@ fail: static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *value, nxt_socket_conf_t *skcf, - nxt_conf_value_t *conf_cmds, nxt_bool_t last) + nxt_tls_init_t *tls_init, nxt_bool_t last) { nxt_router_tlssock_t *tls; @@ -1865,8 +1887,8 @@ nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, return NXT_ERROR; } + tls->tls_init = tls_init; tls->socket_conf = skcf; - tls->conf_cmds = conf_cmds; tls->temp_conf = tmcf; tls->last = last; nxt_conf_get_string(value, &tls->name); @@ -2467,6 +2489,8 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, tlscf = tls->socket_conf->tls; } + tls->tls_init->conf = tlscf; + bundle = nxt_mp_get(mp, sizeof(nxt_tls_bundle_conf_t)); if (nxt_slow_path(bundle == NULL)) { goto fail; @@ -2480,8 +2504,8 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, bundle->next = tlscf->bundle; tlscf->bundle = bundle; - ret = task->thread->runtime->tls->server_init(task, tlscf, mp, - tls->conf_cmds, tls->last); + ret = task->thread->runtime->tls->server_init(task, mp, tls->tls_init, + tls->last); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } diff --git a/src/nxt_tls.h b/src/nxt_tls.h index 63c49ee4..a92f1c21 100644 --- a/src/nxt_tls.h +++ b/src/nxt_tls.h @@ -28,14 +28,14 @@ typedef struct nxt_tls_conf_s nxt_tls_conf_t; typedef struct nxt_tls_bundle_conf_s nxt_tls_bundle_conf_t; +typedef struct nxt_tls_init_s nxt_tls_init_t; typedef struct { nxt_int_t (*library_init)(nxt_task_t *task); void (*library_free)(nxt_task_t *task); - nxt_int_t (*server_init)(nxt_task_t *task, - nxt_tls_conf_t *conf, nxt_mp_t *mp, - nxt_conf_value_t *conf_cmds, + nxt_int_t (*server_init)(nxt_task_t *task, nxt_mp_t *mp, + nxt_tls_init_t *tls_init, nxt_bool_t last); void (*server_free)(nxt_task_t *task, nxt_tls_conf_t *conf); @@ -78,6 +78,15 @@ struct nxt_tls_conf_s { }; +struct nxt_tls_init_s { + size_t cache_size; + nxt_time_t timeout; + nxt_conf_value_t *conf_cmds; + + nxt_tls_conf_t *conf; +}; + + #if (NXT_HAVE_OPENSSL) extern const nxt_tls_lib_t nxt_openssl_lib; -- cgit From f965e358b6ca878ead629dffb2f0df57230995ea Mon Sep 17 00:00:00 2001 From: Andrey Suvorov Date: Thu, 22 Jul 2021 11:23:48 -0700 Subject: Changing SNI callback return code if a client sends no SNI. When a client sends no SNI is a common situation. But currently the server processes it as an error and returns SSL_TLSEXT_ERR_ALERT_FATAL causing termination of a current TLS session. The problem occurs if configuration has more than one certificate bundle in a listener. This fix changes the return code to SSL_TLSEXT_ERR_OK and the log level of a message. --- docs/changes.xml | 8 ++++++++ src/nxt_openssl.c | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 2aa9bb65..634bf9cd 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -43,6 +43,14 @@ process and thread lifecycle hooks in Ruby. + + +TLS connection was rejected for configuration with more than one +certificate bundle in a listener if a client did not use SNI. + + + + the router process could crash on TLS connection open when multiple listeners diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index 3b5d4fda..297e11cf 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -804,15 +804,15 @@ nxt_openssl_servername(SSL *s, int *ad, void *arg) } servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); - if (nxt_slow_path(servername == NULL)) { - nxt_log(c->socket.task, NXT_LOG_ALERT, "SSL_get_servername() returned " - "NULL in server name callback"); - return SSL_TLSEXT_ERR_ALERT_FATAL; + + if (servername == NULL) { + nxt_debug(c->socket.task, "SSL_get_servername(): NULL"); + goto done; } str.length = nxt_strlen(servername); if (str.length == 0) { - nxt_debug(c->socket.task, "client sent zero-length server name"); + nxt_debug(c->socket.task, "SSL_get_servername(): \"\" is empty"); goto done; } -- cgit From 960ffc99677b8451a3cc4ea7bc053b3c123e4e1b Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 23 Jul 2021 15:37:03 +0100 Subject: Tests: added SNI test without hostname in request. --- test/test_tls_sni.py | 20 ++++++++++++++++++++ test/unit/http.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/test/test_tls_sni.py b/test/test_tls_sni.py index 2e5424e2..eba6140a 100644 --- a/test/test_tls_sni.py +++ b/test/test_tls_sni.py @@ -168,6 +168,26 @@ basicConstraints = critical,CA:TRUE""" self.check_cert('alt2.example.com', bundles['example.com']['subj']) self.check_cert('blah', bundles['default']['subj']) + def test_tls_sni_no_hostname(self): + bundles = { + "localhost.com": {"subj": "localhost.com", "alt_names": []}, + "example.com": { + "subj": "example.com", + "alt_names": ["example.com"], + }, + } + self.config_bundles(bundles) + self.add_tls(["localhost.com", "example.com"]) + + resp, sock = self.get_ssl( + headers={'Content-Length': '0', 'Connection': 'close'}, start=True, + ) + assert resp['status'] == 200 + assert ( + sock.getpeercert()['subject'][0][0][1] + == bundles['localhost.com']['subj'] + ) + def test_tls_sni_upper_case(self): bundles = { "localhost.com": {"subj": "LOCALHOST.COM", "alt_names": []}, diff --git a/test/unit/http.py b/test/unit/http.py index 797b7681..dcfcd232 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -45,7 +45,7 @@ class TestHTTP: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) if 'wrapper' in kwargs: - server_hostname = headers.get('Host', 'localhost') + server_hostname = headers.get('Host', None) sock = kwargs['wrapper'](sock, server_hostname=server_hostname) connect_args = addr if sock_type == 'unix' else (addr, port) -- cgit From a3df6efc8d5994899a985080b574d9043ecd80f3 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Fri, 23 Jul 2021 09:14:43 +0800 Subject: Router: split nxt_http_static_conf_t from nxt_http_action_t. No functional changes. --- src/nxt_conf_validation.c | 14 +-- src/nxt_http.h | 27 +++--- src/nxt_http_route.c | 101 ++++----------------- src/nxt_http_static.c | 219 +++++++++++++++++++++++++++++++++------------- src/nxt_router.c | 10 +-- 5 files changed, 198 insertions(+), 173 deletions(-) diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index fd57a983..0ec2e811 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -1277,7 +1277,7 @@ static nxt_int_t nxt_conf_vldt_mtypes_extension(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) { - nxt_str_t ext, *dup_type; + nxt_str_t exten, *dup_type; nxt_conf_vldt_mtypes_ctx_t *ctx; ctx = vldt->ctx; @@ -1287,24 +1287,24 @@ nxt_conf_vldt_mtypes_extension(nxt_conf_validation_t *vldt, "contain only strings.", ctx->type); } - nxt_conf_get_string(value, &ext); + nxt_conf_get_string(value, &exten); - if (ext.length == 0) { + if (exten.length == 0) { return nxt_conf_vldt_error(vldt, "An empty file extension for " "the \"%V\" MIME type.", ctx->type); } - dup_type = nxt_http_static_mtypes_hash_find(&ctx->hash, &ext); + dup_type = nxt_http_static_mtype_get(&ctx->hash, &exten); if (dup_type->length != 0) { return nxt_conf_vldt_error(vldt, "The \"%V\" file extension has been " "declared for \"%V\" and \"%V\" " "MIME types at the same time.", - &ext, dup_type, ctx->type); + &exten, dup_type, ctx->type); } - return nxt_http_static_mtypes_hash_add(ctx->pool, &ctx->hash, - &ext, ctx->type); + return nxt_http_static_mtypes_hash_add(ctx->pool, &ctx->hash, &exten, + ctx->type); } diff --git a/src/nxt_http.h b/src/nxt_http.h index 18cf8680..f0276afc 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -230,16 +230,10 @@ struct nxt_http_action_s { nxt_app_t *application; nxt_int_t target; } app; - - struct { - nxt_str_t chroot; - nxt_uint_t resolve; - nxt_http_route_rule_t *types; - nxt_http_action_t *fallback; - } share; } u; nxt_str_t name; + nxt_http_action_t *fallback; }; @@ -322,27 +316,30 @@ nxt_int_t nxt_http_pass_segments(nxt_mp_t *mp, nxt_str_t *pass, nxt_str_t *segments, nxt_uint_t n); nxt_http_action_t *nxt_http_pass_application(nxt_task_t *task, nxt_router_conf_t *rtcf, nxt_str_t *name); +nxt_http_route_rule_t *nxt_http_route_types_rule_create(nxt_task_t *task, + nxt_mp_t *mp, nxt_conf_value_t *types); nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule, u_char *start, size_t length); +nxt_int_t nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv, nxt_http_action_t *action); +void nxt_http_request_action(nxt_task_t *task, nxt_http_request_t *r, + nxt_http_action_t *action); + nxt_int_t nxt_upstreams_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *conf); nxt_int_t nxt_upstreams_joint_create(nxt_router_temp_conf_t *tmcf, nxt_upstream_t ***upstream_joint); -void nxt_http_request_action(nxt_task_t *task, nxt_http_request_t *r, - nxt_http_action_t *action); - nxt_int_t nxt_http_return_init(nxt_mp_t *mp, nxt_http_action_t *action, nxt_http_action_conf_t *acf); -nxt_http_action_t *nxt_http_static_handler(nxt_task_t *task, - nxt_http_request_t *r, nxt_http_action_t *action); +nxt_int_t nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_http_action_t *action, nxt_http_action_conf_t *acf); nxt_int_t nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash); nxt_int_t nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash, - nxt_str_t *extension, nxt_str_t *type); -nxt_str_t *nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, - nxt_str_t *extension); + nxt_str_t *exten, nxt_str_t *type); +nxt_str_t *nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, nxt_str_t *exten); nxt_http_action_t *nxt_http_application_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action); diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 46c9b536..3cf78ec8 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -185,9 +185,6 @@ static nxt_http_route_t *nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); static nxt_http_route_match_t *nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); -static nxt_int_t nxt_http_action_init(nxt_task_t *task, - nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, - nxt_http_action_t *action); static nxt_http_route_table_t *nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *table_cv, nxt_http_route_object_t object, nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding); @@ -653,20 +650,14 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { }; -static nxt_int_t +nxt_int_t nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, nxt_http_action_t *action) { -#if (NXT_HAVE_OPENAT2) - u_char *p; - uint8_t slash; - nxt_str_t *chroot; -#endif nxt_mp_t *mp; nxt_int_t ret; nxt_str_t name, *string; nxt_conf_value_t *conf; - nxt_http_route_rule_t *rule; nxt_http_action_conf_t acf; nxt_memzero(&acf, sizeof(acf)); @@ -686,9 +677,10 @@ nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } if (acf.share != NULL) { - conf = acf.share; + return nxt_http_static_init(task, tmcf, action, &acf); + } - } else if (acf.proxy != NULL) { + if (acf.proxy != NULL) { conf = acf.proxy; } else { @@ -702,72 +694,6 @@ nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, return NXT_ERROR; } - if (acf.share != NULL) { - action->handler = nxt_http_static_handler; - -#if (NXT_HAVE_OPENAT2) - string = &acf.chroot; - chroot = &action->u.share.chroot; - - if (string->length > 0) { - action->u.share.resolve |= RESOLVE_IN_ROOT; - - slash = (string->start[string->length - 1] != '/'); - - chroot->length = string->length + (slash ? 1 : 0); - - chroot->start = nxt_mp_alloc(mp, chroot->length + 1); - if (nxt_slow_path(chroot->start == NULL)) { - return NXT_ERROR; - } - - p = nxt_cpymem(chroot->start, string->start, string->length); - - if (slash) { - *p++ = '/'; - } - - *p = '\0'; - } - - if (acf.follow_symlinks != NULL - && !nxt_conf_get_boolean(acf.follow_symlinks)) - { - action->u.share.resolve |= RESOLVE_NO_SYMLINKS; - } - - if (acf.traverse_mounts != NULL - && !nxt_conf_get_boolean(acf.traverse_mounts)) - { - action->u.share.resolve |= RESOLVE_NO_XDEV; - } -#endif - - if (acf.types != NULL) { - rule = nxt_http_route_rule_create(task, mp, acf.types, 0, - NXT_HTTP_ROUTE_PATTERN_LOWCASE, - NXT_HTTP_ROUTE_ENCODING_NONE); - if (nxt_slow_path(rule == NULL)) { - return NXT_ERROR; - } - - action->u.share.types = rule; - } - - if (acf.fallback != NULL) { - action->u.share.fallback = nxt_mp_alloc(mp, - sizeof(nxt_http_action_t)); - if (nxt_slow_path(action->u.share.fallback == NULL)) { - return NXT_ERROR; - } - - return nxt_http_action_init(task, tmcf, acf.fallback, - action->u.share.fallback); - } - - return NXT_OK; - } - if (acf.proxy != NULL) { return nxt_http_proxy_create(mp, action); } @@ -1075,6 +1001,16 @@ nxt_http_route_addr_rule_create(nxt_task_t *task, nxt_mp_t *mp, } +nxt_http_route_rule_t * +nxt_http_route_types_rule_create(nxt_task_t *task, nxt_mp_t *mp, + nxt_conf_value_t *types) +{ + return nxt_http_route_rule_create(task, mp, types, 0, + NXT_HTTP_ROUTE_PATTERN_LOWCASE, + NXT_HTTP_ROUTE_ENCODING_NONE); +} + + static int nxt_http_pattern_compare(const void *one, const void *two) { @@ -1447,15 +1383,12 @@ static nxt_int_t nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_action_t *action) { - nxt_var_t *var; nxt_int_t ret; + nxt_var_t *var; if (action->handler != NULL) { - if (action->handler == nxt_http_static_handler - && action->u.share.fallback != NULL) - { - return nxt_http_action_resolve(task, tmcf, - action->u.share.fallback); + if (action->fallback != NULL) { + return nxt_http_action_resolve(task, tmcf, action->fallback); } return NXT_OK; diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index c8b73fac..9b79a666 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -7,12 +7,22 @@ #include +typedef struct { + nxt_str_t share; + nxt_str_t chroot; + nxt_uint_t resolve; + nxt_http_route_rule_t *types; +} nxt_http_static_conf_t; + + #define NXT_HTTP_STATIC_BUF_COUNT 2 #define NXT_HTTP_STATIC_BUF_SIZE (128 * 1024) +static nxt_http_action_t *nxt_http_static(nxt_task_t *task, + nxt_http_request_t *r, nxt_http_action_t *action); static void nxt_http_static_extract_extension(nxt_str_t *path, - nxt_str_t *extension); + nxt_str_t *exten); static void nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data); static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj, @@ -27,30 +37,122 @@ static void nxt_http_static_mtypes_hash_free(void *data, void *p); static const nxt_http_request_state_t nxt_http_static_send_state; -nxt_http_action_t * -nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, +nxt_int_t +nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_http_action_t *action, nxt_http_action_conf_t *acf) +{ + nxt_mp_t *mp; + nxt_str_t *str, value; + nxt_http_static_conf_t *conf; + + mp = tmcf->router_conf->mem_pool; + + conf = nxt_mp_zget(mp, sizeof(nxt_http_static_conf_t)); + if (nxt_slow_path(conf == NULL)) { + return NXT_ERROR; + } + + action->handler = nxt_http_static; + action->u.conf = conf; + + nxt_conf_get_string(acf->share, &value); + + str = nxt_str_dup(mp, &conf->share, &value); + if (nxt_slow_path(str == NULL)) { + return NXT_ERROR; + } + +#if (NXT_HAVE_OPENAT2) + if (acf->chroot.length > 0) { + u_char *p; + nxt_str_t slash; + + if (acf->chroot.start[acf->chroot.length - 1] != '/') { + nxt_str_set(&slash, "/"); + + } else { + nxt_str_set(&slash, ""); + } + + value.length = acf->chroot.length + slash.length; + + value.start = nxt_mp_alloc(mp, value.length + 1); + if (nxt_slow_path(value.start == NULL)) { + return NXT_ERROR; + } + + p = value.start; + p = nxt_cpymem(p, acf->chroot.start, acf->chroot.length); + p = nxt_cpymem(p, slash.start, slash.length); + *p = '\0'; + + conf->chroot = value; + conf->resolve |= RESOLVE_IN_ROOT; + } + + if (acf->follow_symlinks != NULL + && !nxt_conf_get_boolean(acf->follow_symlinks)) + { + conf->resolve |= RESOLVE_NO_SYMLINKS; + } + + if (acf->traverse_mounts != NULL + && !nxt_conf_get_boolean(acf->traverse_mounts)) + { + conf->resolve |= RESOLVE_NO_XDEV; + } +#endif + + if (acf->types != NULL) { + conf->types = nxt_http_route_types_rule_create(task, mp, acf->types); + if (nxt_slow_path(conf->types == NULL)) { + return NXT_ERROR; + } + } + + if (acf->fallback != NULL) { + action->fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t)); + if (nxt_slow_path(action->fallback == NULL)) { + return NXT_ERROR; + } + + return nxt_http_action_init(task, tmcf, acf->fallback, + action->fallback); + } + + return NXT_OK; +} + + +static nxt_http_action_t * +nxt_http_static(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action) { - size_t length, encode; - u_char *p, *fname; - struct tm tm; - nxt_buf_t *fb; - nxt_int_t ret; - nxt_str_t index, extension, *mtype, *chroot; - nxt_uint_t level; - nxt_bool_t need_body; - nxt_file_t *f, file; - nxt_file_info_t fi; - nxt_http_field_t *field; - nxt_http_status_t status; - nxt_router_conf_t *rtcf; - nxt_work_handler_t body_handler; + size_t length, encode; + u_char *p, *fname; + struct tm tm; + nxt_buf_t *fb; + nxt_int_t ret; + nxt_str_t index, exten, *mtype, *chroot; + nxt_uint_t level; + nxt_bool_t need_body; + nxt_file_t *f, file; + nxt_file_info_t fi; + nxt_http_field_t *field; + nxt_http_status_t status; + nxt_router_conf_t *rtcf; + nxt_work_handler_t body_handler; + nxt_http_static_conf_t *conf; + + conf = action->u.conf; + + nxt_debug(task, "http static: \"%V\"", &conf->share); if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) { if (!nxt_str_eq(r->method, "HEAD", 4)) { - if (action->u.share.fallback != NULL) { - return action->u.share.fallback; + if (action->fallback != NULL) { + return action->fallback; } nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED); @@ -66,11 +168,11 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, if (r->path->start[r->path->length - 1] == '/') { /* TODO: dynamic index setting. */ nxt_str_set(&index, "index.html"); - nxt_str_set(&extension, ".html"); + nxt_str_set(&exten, ".html"); } else { nxt_str_set(&index, ""); - nxt_str_null(&extension); + nxt_str_null(&exten); } f = NULL; @@ -79,20 +181,19 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, mtype = NULL; - if (action->u.share.types != NULL && extension.start == NULL) { - nxt_http_static_extract_extension(r->path, &extension); - mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, - &extension); + if (conf->types != NULL && exten.start == NULL) { + nxt_http_static_extract_extension(r->path, &exten); + mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten); - ret = nxt_http_route_test_rule(r, action->u.share.types, - mtype->start, mtype->length); + ret = nxt_http_route_test_rule(r, conf->types, mtype->start, + mtype->length); if (nxt_slow_path(ret == NXT_ERROR)) { goto fail; } if (ret == 0) { - if (action->u.share.fallback != NULL) { - return action->u.share.fallback; + if (action->fallback != NULL) { + return action->fallback; } nxt_http_request_error(task, r, NXT_HTTP_FORBIDDEN); @@ -100,7 +201,7 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, } } - length = action->name.length + r->path->length + index.length; + length = conf->share.length + r->path->length + index.length; fname = nxt_mp_nget(r->mem_pool, length + 1); if (nxt_slow_path(fname == NULL)) { @@ -108,7 +209,7 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, } p = fname; - p = nxt_cpymem(p, action->name.start, action->name.length); + p = nxt_cpymem(p, conf->share.start, conf->share.length); p = nxt_cpymem(p, r->path->start, r->path->length); p = nxt_cpymem(p, index.start, index.length); *p = '\0'; @@ -117,11 +218,10 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, file.name = fname; - chroot = &action->u.share.chroot; + chroot = &conf->chroot; #if (NXT_HAVE_OPENAT2) - - if (action->u.share.resolve != 0) { + if (conf->resolve != 0) { if (chroot->length > 0) { file.name = chroot->start; @@ -156,8 +256,7 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, file.name = fname; ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY, - NXT_FILE_OPEN, 0, af.fd, - action->u.share.resolve); + NXT_FILE_OPEN, 0, af.fd, conf->resolve); if (af.fd != AT_FDCWD) { nxt_file_close(task, &af); @@ -169,9 +268,7 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, } #else - ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); - #endif if (nxt_slow_path(ret != NXT_OK)) { @@ -211,8 +308,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, break; } - if (level == NXT_LOG_ERR && action->u.share.fallback != NULL) { - return action->u.share.fallback; + if (level == NXT_LOG_ERR && action->fallback != NULL) { + return action->fallback; } if (status != NXT_HTTP_NOT_FOUND) { @@ -283,13 +380,12 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_file_size(&fi)) - p; - if (extension.start == NULL) { - nxt_http_static_extract_extension(r->path, &extension); + if (exten.start == NULL) { + nxt_http_static_extract_extension(r->path, &exten); } if (mtype == NULL) { - mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, - &extension); + mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten); } if (mtype->length != 0) { @@ -328,8 +424,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_file_close(task, f); if (nxt_slow_path(!nxt_is_dir(&fi))) { - if (action->u.share.fallback != NULL) { - return action->u.share.fallback; + if (action->fallback != NULL) { + return action->fallback; } nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file", @@ -401,7 +497,7 @@ fail: static void -nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *extension) +nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *exten) { u_char ch, *p, *end; @@ -419,8 +515,8 @@ nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *extension) p++; /* Fall through. */ case '.': - extension->length = end - p; - extension->start = p; + exten->length = end - p; + exten->start = p; return; } } @@ -571,13 +667,13 @@ clean: nxt_int_t nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash) { - nxt_str_t *type, extension; + nxt_str_t *type, exten; nxt_int_t ret; nxt_uint_t i; static const struct { nxt_str_t type; - const char *extension; + const char *exten; } default_types[] = { { nxt_string("text/html"), ".html" }, @@ -644,10 +740,10 @@ nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash) for (i = 0; i < nxt_nitems(default_types); i++) { type = (nxt_str_t *) &default_types[i].type; - extension.start = (u_char *) default_types[i].extension; - extension.length = nxt_strlen(extension.start); + exten.start = (u_char *) default_types[i].exten; + exten.length = nxt_strlen(exten.start); - ret = nxt_http_static_mtypes_hash_add(mp, hash, &extension, type); + ret = nxt_http_static_mtypes_hash_add(mp, hash, &exten, type); if (nxt_slow_path(ret != NXT_OK)) { return NXT_ERROR; } @@ -668,14 +764,14 @@ static const nxt_lvlhsh_proto_t nxt_http_static_mtypes_hash_proto typedef struct { - nxt_str_t extension; + nxt_str_t exten; nxt_str_t *type; } nxt_http_static_mtype_t; nxt_int_t nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash, - nxt_str_t *extension, nxt_str_t *type) + nxt_str_t *exten, nxt_str_t *type) { nxt_lvlhsh_query_t lhq; nxt_http_static_mtype_t *mtype; @@ -685,10 +781,10 @@ nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash, return NXT_ERROR; } - mtype->extension = *extension; + mtype->exten = *exten; mtype->type = type; - lhq.key = *extension; + lhq.key = *exten; lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length); lhq.replace = 1; lhq.value = mtype; @@ -700,14 +796,14 @@ nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash, nxt_str_t * -nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension) +nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, nxt_str_t *exten) { nxt_lvlhsh_query_t lhq; nxt_http_static_mtype_t *mtype; static nxt_str_t empty = nxt_string(""); - lhq.key = *extension; + lhq.key = *exten; lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length); lhq.proto = &nxt_http_static_mtypes_hash_proto; @@ -727,8 +823,7 @@ nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data) mtype = data; - return nxt_strcasestr_eq(&lhq->key, &mtype->extension) ? NXT_OK - : NXT_DECLINED; + return nxt_strcasestr_eq(&lhq->key, &mtype->exten) ? NXT_OK : NXT_DECLINED; } diff --git a/src/nxt_router.c b/src/nxt_router.c index 1156edb8..96d62575 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -1907,7 +1907,7 @@ nxt_router_conf_process_static(nxt_task_t *task, nxt_router_conf_t *rtcf, { uint32_t next, i; nxt_mp_t *mp; - nxt_str_t *type, extension, str; + nxt_str_t *type, exten, str; nxt_int_t ret; nxt_uint_t exts; nxt_conf_value_t *mtypes_conf, *ext_conf, *value; @@ -1945,12 +1945,12 @@ nxt_router_conf_process_static(nxt_task_t *task, nxt_router_conf_t *rtcf, if (nxt_conf_type(ext_conf) == NXT_CONF_STRING) { nxt_conf_get_string(ext_conf, &str); - if (nxt_slow_path(nxt_str_dup(mp, &extension, &str) == NULL)) { + if (nxt_slow_path(nxt_str_dup(mp, &exten, &str) == NULL)) { return NXT_ERROR; } ret = nxt_http_static_mtypes_hash_add(mp, &rtcf->mtypes_hash, - &extension, type); + &exten, type); if (nxt_slow_path(ret != NXT_OK)) { return NXT_ERROR; } @@ -1965,12 +1965,12 @@ nxt_router_conf_process_static(nxt_task_t *task, nxt_router_conf_t *rtcf, nxt_conf_get_string(value, &str); - if (nxt_slow_path(nxt_str_dup(mp, &extension, &str) == NULL)) { + if (nxt_slow_path(nxt_str_dup(mp, &exten, &str) == NULL)) { return NXT_ERROR; } ret = nxt_http_static_mtypes_hash_add(mp, &rtcf->mtypes_hash, - &extension, type); + &exten, type); if (nxt_slow_path(ret != NXT_OK)) { return NXT_ERROR; } -- cgit From b47f1ac7ea3d80981485fd4ae82cd1c7fca3c337 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Mon, 26 Jul 2021 15:00:46 +0800 Subject: Router: renamed nxt_http_proxy_create() as nxt_http_proxy_init(). No functional changes. --- src/nxt_http.h | 4 ++-- src/nxt_http_proxy.c | 19 +++++++++++++------ src/nxt_http_route.c | 12 ++---------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/nxt_http.h b/src/nxt_http.h index f0276afc..c08b3ba7 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -348,8 +348,8 @@ nxt_int_t nxt_upstream_find(nxt_upstreams_t *upstreams, nxt_str_t *name, nxt_http_action_t *nxt_upstream_proxy_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_upstream_t *upstream); - -nxt_int_t nxt_http_proxy_create(nxt_mp_t *mp, nxt_http_action_t *action); +nxt_int_t nxt_http_proxy_init(nxt_mp_t *mp, nxt_http_action_t *action, + nxt_http_action_conf_t *acf); nxt_int_t nxt_http_proxy_date(void *ctx, nxt_http_field_t *field, uintptr_t data); nxt_int_t nxt_http_proxy_content_length(void *ctx, nxt_http_field_t *field, diff --git a/src/nxt_http_proxy.c b/src/nxt_http_proxy.c index 338d9fce..6aa3aabb 100644 --- a/src/nxt_http_proxy.c +++ b/src/nxt_http_proxy.c @@ -21,7 +21,7 @@ static void nxt_http_proxy_upstream_ready(nxt_task_t *task, nxt_upstream_server_t *us); static void nxt_http_proxy_upstream_error(nxt_task_t *task, nxt_upstream_server_t *us); -static nxt_http_action_t *nxt_http_proxy_handler(nxt_task_t *task, +static nxt_http_action_t *nxt_http_proxy(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action); static void nxt_http_proxy_header_send(nxt_task_t *task, void *obj, void *data); static void nxt_http_proxy_header_sent(nxt_task_t *task, void *obj, void *data); @@ -50,7 +50,8 @@ static const nxt_upstream_peer_state_t nxt_upstream_proxy_state = { nxt_int_t -nxt_http_proxy_create(nxt_mp_t *mp, nxt_http_action_t *action) +nxt_http_proxy_init(nxt_mp_t *mp, nxt_http_action_t *action, + nxt_http_action_conf_t *acf) { nxt_str_t name; nxt_sockaddr_t *sa; @@ -58,7 +59,7 @@ nxt_http_proxy_create(nxt_mp_t *mp, nxt_http_action_t *action) nxt_upstream_proxy_t *proxy; sa = NULL; - name = action->name; + nxt_conf_get_string(acf->proxy, &name); if (nxt_str_start(&name, "http://", 7)) { name.length -= 7; @@ -92,7 +93,7 @@ nxt_http_proxy_create(nxt_mp_t *mp, nxt_http_action_t *action) up->type.proxy = proxy; action->u.upstream = up; - action->handler = nxt_http_proxy_handler; + action->handler = nxt_http_proxy; } return NXT_OK; @@ -100,10 +101,16 @@ nxt_http_proxy_create(nxt_mp_t *mp, nxt_http_action_t *action) static nxt_http_action_t * -nxt_http_proxy_handler(nxt_task_t *task, nxt_http_request_t *r, +nxt_http_proxy(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action) { - return nxt_upstream_proxy_handler(task, r, action->u.upstream); + nxt_upstream_t *u; + + u = action->u.upstream; + + nxt_debug(task, "http proxy: \"%V\"", &u->name); + + return nxt_upstream_proxy_handler(task, r, u); } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 3cf78ec8..bed127c9 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -657,7 +657,6 @@ nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_mp_t *mp; nxt_int_t ret; nxt_str_t name, *string; - nxt_conf_value_t *conf; nxt_http_action_conf_t acf; nxt_memzero(&acf, sizeof(acf)); @@ -681,23 +680,16 @@ nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } if (acf.proxy != NULL) { - conf = acf.proxy; - - } else { - conf = acf.pass; + return nxt_http_proxy_init(mp, action, &acf); } - nxt_conf_get_string(conf, &name); + nxt_conf_get_string(acf.pass, &name); string = nxt_str_dup(mp, &action->name, &name); if (nxt_slow_path(string == NULL)) { return NXT_ERROR; } - if (acf.proxy != NULL) { - return nxt_http_proxy_create(mp, action); - } - return NXT_OK; } -- cgit From f3a1c1deb541784b2b0ed179514e4d5eba9fe626 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Sat, 24 Jul 2021 11:44:52 +0800 Subject: Router: split nxt_http_app_conf_t from nxt_http_action_t. No functional changes. --- src/nxt_http.h | 5 ----- src/nxt_http_request.c | 4 +--- src/nxt_http_route.c | 26 +++----------------------- src/nxt_router.c | 47 ++++++++++++++++++++++++++++++++++++++--------- src/nxt_router.h | 6 +++--- 5 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/nxt_http.h b/src/nxt_http.h index c08b3ba7..faba83f2 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -225,11 +225,6 @@ struct nxt_http_action_s { nxt_upstream_t *upstream; uint32_t upstream_number; nxt_var_t *var; - - struct { - nxt_app_t *application; - nxt_int_t target; - } app; } u; nxt_str_t name; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 779cfcf8..16563a98 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -348,9 +348,7 @@ nxt_http_application_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_str_set(&r->server_name, "localhost"); } - r->app_target = action->u.app.target; - - nxt_router_process_http_request(task, r, action->u.app.application); + nxt_router_process_http_request(task, r, action); return NULL; } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index bed127c9..c38acdf3 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -1482,9 +1482,7 @@ static nxt_int_t nxt_http_pass_find(nxt_task_t *task, nxt_mp_t *mp, nxt_router_conf_t *rtcf, nxt_http_action_t *action) { - nxt_str_t *targets; nxt_int_t ret; - nxt_uint_t i; nxt_str_t segments[3]; ret = nxt_http_pass_segments(mp, &action->name, segments, 3); @@ -1493,24 +1491,8 @@ nxt_http_pass_find(nxt_task_t *task, nxt_mp_t *mp, nxt_router_conf_t *rtcf, } if (nxt_str_eq(&segments[0], "applications", 12)) { - ret = nxt_router_listener_application(rtcf, &segments[1], action); - - if (ret != NXT_OK) { - return ret; - } - - if (segments[2].length != 0) { - targets = action->u.app.application->targets; - - for (i = 0; !nxt_strstr_eq(&segments[2], &targets[i]); i++); - - action->u.app.target = i; - - } else { - action->u.app.target = 0; - } - - return NXT_OK; + return nxt_router_application_init(rtcf, &segments[1], &segments[2], + action); } if (segments[2].length == 0) { @@ -1643,9 +1625,7 @@ nxt_http_pass_application(nxt_task_t *task, nxt_router_conf_t *rtcf, action->name = *name; - (void) nxt_router_listener_application(rtcf, name, action); - - action->u.app.target = 0; + (void) nxt_router_application_init(rtcf, name, NULL, action); return action; } diff --git a/src/nxt_router.c b/src/nxt_router.c index 96d62575..c766c25e 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -2158,21 +2158,46 @@ nxt_router_apps_hash_use(nxt_task_t *task, nxt_router_conf_t *rtcf, int i) } +typedef struct { + nxt_app_t *app; + nxt_int_t target; +} nxt_http_app_conf_t; + nxt_int_t -nxt_router_listener_application(nxt_router_conf_t *rtcf, nxt_str_t *name, - nxt_http_action_t *action) +nxt_router_application_init(nxt_router_conf_t *rtcf, nxt_str_t *name, + nxt_str_t *target, nxt_http_action_t *action) { - nxt_app_t *app; + nxt_app_t *app; + nxt_str_t *targets; + nxt_uint_t i; + nxt_http_app_conf_t *conf; app = nxt_router_apps_hash_get(rtcf, name); - if (app == NULL) { return NXT_DECLINED; } - action->u.app.application = app; + conf = nxt_mp_get(rtcf->mem_pool, sizeof(nxt_http_app_conf_t)); + if (nxt_slow_path(conf == NULL)) { + return NXT_ERROR; + } + action->handler = nxt_http_application_handler; + action->u.conf = conf; + + conf->app = app; + + if (target != NULL && target->length != 0) { + targets = app->targets; + + for (i = 0; !nxt_strstr_eq(target, &targets[i]); i++); + + conf->target = i; + + } else { + conf->target = 0; + } return NXT_OK; } @@ -4901,13 +4926,17 @@ nxt_router_app_port_get(nxt_task_t *task, nxt_app_t *app, void nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r, - nxt_app_t *app) + nxt_http_action_t *action) { nxt_event_engine_t *engine; + nxt_http_app_conf_t *conf; nxt_request_rpc_data_t *req_rpc_data; + conf = action->u.conf; engine = task->thread->engine; + r->app_target = conf->target; + req_rpc_data = nxt_port_rpc_register_handler_ex(task, engine->port, nxt_router_response_ready_handler, nxt_router_response_error_handler, @@ -4938,11 +4967,11 @@ nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r, r->err_work.obj = r; req_rpc_data->stream = nxt_port_rpc_ex_stream(req_rpc_data); - req_rpc_data->app = app; + req_rpc_data->app = conf->app; req_rpc_data->msg_info.body_fd = -1; req_rpc_data->rpc_cancel = 1; - nxt_router_app_use(task, app, 1); + nxt_router_app_use(task, conf->app, 1); req_rpc_data->request = r; r->req_rpc_data = req_rpc_data; @@ -4951,7 +4980,7 @@ nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r, r->last->completion_handler = nxt_router_http_request_done; } - nxt_router_app_port_get(task, app, req_rpc_data); + nxt_router_app_port_get(task, conf->app, req_rpc_data); nxt_router_app_prepare_request(task, req_rpc_data); } diff --git a/src/nxt_router.h b/src/nxt_router.h index b1ccdf51..9f26a622 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -223,10 +223,10 @@ struct nxt_router_access_log_s { void nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r, - nxt_app_t *app); + nxt_http_action_t *action); void nxt_router_app_port_close(nxt_task_t *task, nxt_port_t *port); -nxt_int_t nxt_router_listener_application(nxt_router_conf_t *rtcf, - nxt_str_t *name, nxt_http_action_t *action); +nxt_int_t nxt_router_application_init(nxt_router_conf_t *rtcf, nxt_str_t *name, + nxt_str_t *target, nxt_http_action_t *action); void nxt_router_listen_event_release(nxt_task_t *task, nxt_listen_event_t *lev, nxt_socket_conf_joint_t *joint); void nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint); -- cgit From fa9fb29be221e0393562831a9e3bcba416652f60 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 29 Jul 2021 19:50:39 +0300 Subject: Application restart introduced. When processing a restart request, the router sends a QUIT message to all existing processes of the application. Then, a new shared application port is created to ensure that new requests won't be handled by the old processes of the application. --- docs/changes.xml | 6 + src/nxt_controller.c | 161 +++++++++++++++++++++++++ src/nxt_port.h | 3 + src/nxt_router.c | 219 +++++++++++++++++++++++++++------- src/nxt_router.h | 2 + test/python/restart/longstart.py | 10 ++ test/python/restart/v1.py | 7 ++ test/python/restart/v2.py | 7 ++ test/test_python_procman.py | 79 ++++++++++++ test/unit/applications/lang/python.py | 1 + 10 files changed, 454 insertions(+), 41 deletions(-) create mode 100644 test/python/restart/longstart.py create mode 100644 test/python/restart/v1.py create mode 100644 test/python/restart/v2.py diff --git a/docs/changes.xml b/docs/changes.xml index 634bf9cd..ce18875a 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -43,6 +43,12 @@ process and thread lifecycle hooks in Ruby. + + +application restart control. + + + TLS connection was rejected for configuration with more than one diff --git a/src/nxt_controller.c b/src/nxt_controller.c index 772d10c8..779a625d 100644 --- a/src/nxt_controller.c +++ b/src/nxt_controller.c @@ -92,6 +92,10 @@ static nxt_bool_t nxt_controller_cert_in_use(nxt_str_t *name); static void nxt_controller_cert_cleanup(nxt_task_t *task, void *obj, void *data); #endif +static void nxt_controller_process_control(nxt_task_t *task, + nxt_controller_request_t *req, nxt_str_t *path); +static void nxt_controller_app_restart_handler(nxt_task_t *task, + nxt_port_recv_msg_t *msg, void *data); static void nxt_controller_conf_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static void nxt_controller_conf_store(nxt_task_t *task, @@ -1022,6 +1026,14 @@ nxt_controller_process_request(nxt_task_t *task, nxt_controller_request_t *req) #endif + if (nxt_str_start(&path, "/control/", 9)) { + path.length -= 9; + path.start += 9; + + nxt_controller_process_control(task, req, &path); + return; + } + nxt_memzero(&resp, sizeof(nxt_controller_response_t)); if (path.length == 1 && path.start[0] == '/') { @@ -1683,6 +1695,155 @@ nxt_controller_conf_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, } +static void +nxt_controller_process_control(nxt_task_t *task, + nxt_controller_request_t *req, nxt_str_t *path) +{ + uint32_t stream; + nxt_buf_t *b; + nxt_int_t rc; + nxt_port_t *router_port, *controller_port; + nxt_runtime_t *rt; + nxt_conf_value_t *value; + nxt_controller_response_t resp; + + static nxt_str_t applications = nxt_string("applications"); + + nxt_memzero(&resp, sizeof(nxt_controller_response_t)); + + if (!nxt_str_eq(&req->parser.method, "GET", 3)) { + goto not_allowed; + } + + if (!nxt_str_start(path, "applications/", 13) + || nxt_memcmp(path->start + path->length - 8, "/restart", 8) != 0) + { + goto not_found; + } + + path->start += 13; + path->length -= 13 + 8; + + if (nxt_controller_check_postpone_request(task)) { + nxt_queue_insert_tail(&nxt_controller_waiting_requests, &req->link); + return; + } + + value = nxt_controller_conf.root; + if (value == NULL) { + goto not_found; + } + + value = nxt_conf_get_object_member(value, &applications, NULL); + if (value == NULL) { + goto not_found; + } + + value = nxt_conf_get_object_member(value, path, NULL); + if (value == NULL) { + goto not_found; + } + + b = nxt_buf_mem_alloc(req->conn->mem_pool, path->length, 0); + if (nxt_slow_path(b == NULL)) { + goto alloc_fail; + } + + b->mem.free = nxt_cpymem(b->mem.pos, path->start, path->length); + + rt = task->thread->runtime; + + controller_port = rt->port_by_type[NXT_PROCESS_CONTROLLER]; + router_port = rt->port_by_type[NXT_PROCESS_ROUTER]; + + stream = nxt_port_rpc_register_handler(task, controller_port, + nxt_controller_app_restart_handler, + nxt_controller_app_restart_handler, + router_port->pid, req); + if (nxt_slow_path(stream == 0)) { + goto alloc_fail; + } + + rc = nxt_port_socket_write(task, router_port, NXT_PORT_MSG_APP_RESTART, + -1, stream, 0, b); + if (nxt_slow_path(rc != NXT_OK)) { + nxt_port_rpc_cancel(task, controller_port, stream); + + goto fail; + } + + nxt_queue_insert_head(&nxt_controller_waiting_requests, &req->link); + + return; + +not_allowed: + + resp.status = 405; + resp.title = (u_char *) "Method isn't allowed."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +not_found: + + resp.status = 404; + resp.title = (u_char *) "Value doesn't exist."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +alloc_fail: + + resp.status = 500; + resp.title = (u_char *) "Memory allocation failed."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +fail: + + resp.status = 500; + resp.title = (u_char *) "Send restart failed."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); +} + + +static void +nxt_controller_app_restart_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, + void *data) +{ + nxt_controller_request_t *req; + nxt_controller_response_t resp; + + req = data; + + nxt_debug(task, "controller app restart handler"); + + nxt_queue_remove(&req->link); + + nxt_memzero(&resp, sizeof(nxt_controller_response_t)); + + if (msg->port_msg.type == NXT_PORT_MSG_RPC_READY) { + resp.status = 200; + resp.title = (u_char *) "Ok"; + + } else { + resp.status = 500; + resp.title = (u_char *) "Failed to restart app."; + resp.offset = -1; + } + + nxt_controller_response(task, req, &resp); + + nxt_controller_flush_requests(task); +} + + static void nxt_controller_conf_store(nxt_task_t *task, nxt_conf_value_t *conf) { diff --git a/src/nxt_port.h b/src/nxt_port.h index 5ece3bfa..a0bc2512 100644 --- a/src/nxt_port.h +++ b/src/nxt_port.h @@ -50,6 +50,7 @@ struct nxt_port_handlers_s { /* Various data. */ nxt_port_handler_t data; + nxt_port_handler_t app_restart; nxt_port_handler_t oosm; nxt_port_handler_t shm_ack; @@ -100,6 +101,7 @@ typedef enum { _NXT_PORT_MSG_WEBSOCKET = nxt_port_handler_idx(websocket_frame), _NXT_PORT_MSG_DATA = nxt_port_handler_idx(data), + _NXT_PORT_MSG_APP_RESTART = nxt_port_handler_idx(app_restart), _NXT_PORT_MSG_OOSM = nxt_port_handler_idx(oosm), _NXT_PORT_MSG_SHM_ACK = nxt_port_handler_idx(shm_ack), @@ -139,6 +141,7 @@ typedef enum { NXT_PORT_MSG_DATA = _NXT_PORT_MSG_DATA, NXT_PORT_MSG_DATA_LAST = nxt_msg_last(_NXT_PORT_MSG_DATA), + NXT_PORT_MSG_APP_RESTART = nxt_msg_last(_NXT_PORT_MSG_APP_RESTART), NXT_PORT_MSG_OOSM = nxt_msg_last(_NXT_PORT_MSG_OOSM), NXT_PORT_MSG_SHM_ACK = nxt_msg_last(_NXT_PORT_MSG_SHM_ACK), diff --git a/src/nxt_router.c b/src/nxt_router.c index c766c25e..8360e75a 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -18,6 +18,8 @@ #include #include +#define NXT_SHARED_PORT_ID 0xFFFFu + typedef struct { nxt_str_t type; uint32_t processes; @@ -67,6 +69,12 @@ typedef struct { } nxt_app_rpc_t; +typedef struct { + nxt_app_joint_t *app_joint; + uint32_t generation; +} nxt_app_joint_rpc_t; + + static nxt_int_t nxt_router_prefork(nxt_task_t *task, nxt_process_t *process, nxt_mp_t *mp); static nxt_int_t nxt_router_start(nxt_task_t *task, nxt_process_data_t *data); @@ -79,6 +87,8 @@ static void nxt_router_new_port_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); static void nxt_router_conf_data_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); +static void nxt_router_app_restart_handler(nxt_task_t *task, + nxt_port_recv_msg_t *msg); static void nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); static void nxt_router_access_log_reopen_handler(nxt_task_t *task, @@ -281,6 +291,7 @@ static const nxt_port_handlers_t nxt_router_process_port_handlers = { .mmap = nxt_port_mmap_handler, .get_mmap = nxt_router_get_mmap_handler, .data = nxt_router_conf_data_handler, + .app_restart = nxt_router_app_restart_handler, .remove_pid = nxt_router_remove_pid_handler, .access_log = nxt_router_access_log_reopen_handler, .rpc_ready = nxt_port_rpc_handler, @@ -379,14 +390,15 @@ static void nxt_router_start_app_process_handler(nxt_task_t *task, nxt_port_t *port, void *data) { - size_t size; - uint32_t stream; - nxt_mp_t *mp; - nxt_int_t ret; - nxt_app_t *app; - nxt_buf_t *b; - nxt_port_t *main_port; - nxt_runtime_t *rt; + size_t size; + uint32_t stream; + nxt_mp_t *mp; + nxt_int_t ret; + nxt_app_t *app; + nxt_buf_t *b; + nxt_port_t *main_port; + nxt_runtime_t *rt; + nxt_app_joint_rpc_t *app_joint_rpc; app = data; @@ -407,30 +419,29 @@ nxt_router_start_app_process_handler(nxt_task_t *task, nxt_port_t *port, *b->mem.free++ = '\0'; nxt_buf_cpystr(b, &app->conf); - nxt_router_app_joint_use(task, app->joint, 1); - - stream = nxt_port_rpc_register_handler(task, port, - nxt_router_app_port_ready, - nxt_router_app_port_error, - -1, app->joint); - - if (nxt_slow_path(stream == 0)) { - nxt_router_app_joint_use(task, app->joint, -1); - + app_joint_rpc = nxt_port_rpc_register_handler_ex(task, port, + nxt_router_app_port_ready, + nxt_router_app_port_error, + sizeof(nxt_app_joint_rpc_t)); + if (nxt_slow_path(app_joint_rpc == NULL)) { goto failed; } + stream = nxt_port_rpc_ex_stream(app_joint_rpc); + ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_START_PROCESS, -1, stream, port->id, b); - if (nxt_slow_path(ret != NXT_OK)) { nxt_port_rpc_cancel(task, port, stream); - nxt_router_app_joint_use(task, app->joint, -1); - goto failed; } + app_joint_rpc->app_joint = app->joint; + app_joint_rpc->generation = app->generation; + + nxt_router_app_joint_use(task, app->joint, 1); + nxt_router_app_use(task, app, -1); return; @@ -504,6 +515,7 @@ nxt_router_msg_cancel(nxt_task_t *task, nxt_request_rpc_data_t *req_rpc_data) { nxt_buf_t *b, *next; nxt_bool_t cancelled; + nxt_port_t *app_port; nxt_msg_info_t *msg_info; msg_info = &req_rpc_data->msg_info; @@ -512,13 +524,20 @@ nxt_router_msg_cancel(nxt_task_t *task, nxt_request_rpc_data_t *req_rpc_data) return 0; } - cancelled = nxt_app_queue_cancel(req_rpc_data->app->shared_port->queue, - msg_info->tracking_cookie, - req_rpc_data->stream); + app_port = req_rpc_data->app_port; + + if (app_port != NULL && app_port->id == NXT_SHARED_PORT_ID) { + cancelled = nxt_app_queue_cancel(app_port->queue, + msg_info->tracking_cookie, + req_rpc_data->stream); + + if (cancelled) { + nxt_debug(task, "stream #%uD: cancelled by router", + req_rpc_data->stream); + } - if (cancelled) { - nxt_debug(task, "stream #%uD: cancelled by router", - req_rpc_data->stream); + } else { + cancelled = 0; } for (b = msg_info->buf; b != NULL; b = next) { @@ -793,6 +812,90 @@ cleanup: } +static void +nxt_router_app_restart_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) +{ + nxt_app_t *app; + nxt_int_t ret; + nxt_str_t app_name; + nxt_port_t *port, *reply_port, *shared_port, *old_shared_port; + nxt_port_msg_type_t reply; + + reply_port = nxt_runtime_port_find(task->thread->runtime, + msg->port_msg.pid, + msg->port_msg.reply_port); + if (nxt_slow_path(reply_port == NULL)) { + nxt_alert(task, "app_restart_handler: reply port not found"); + return; + } + + app_name.length = nxt_buf_mem_used_size(&msg->buf->mem); + app_name.start = msg->buf->mem.pos; + + nxt_debug(task, "app_restart_handler: %V", &app_name); + + app = nxt_router_app_find(&nxt_router->apps, &app_name); + + if (nxt_fast_path(app != NULL)) { + shared_port = nxt_port_new(task, NXT_SHARED_PORT_ID, nxt_pid, + NXT_PROCESS_APP); + if (nxt_slow_path(shared_port == NULL)) { + goto fail; + } + + ret = nxt_port_socket_init(task, shared_port, 0); + if (nxt_slow_path(ret != NXT_OK)) { + nxt_port_use(task, shared_port, -1); + goto fail; + } + + ret = nxt_router_app_queue_init(task, shared_port); + if (nxt_slow_path(ret != NXT_OK)) { + nxt_port_write_close(shared_port); + nxt_port_read_close(shared_port); + nxt_port_use(task, shared_port, -1); + goto fail; + } + + nxt_port_write_enable(task, shared_port); + + nxt_thread_mutex_lock(&app->mutex); + + nxt_queue_each(port, &app->ports, nxt_port_t, app_link) { + + (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1, + 0, 0, NULL); + + } nxt_queue_loop; + + app->generation++; + + shared_port->app = app; + + old_shared_port = app->shared_port; + old_shared_port->app = NULL; + + app->shared_port = shared_port; + + nxt_thread_mutex_unlock(&app->mutex); + + nxt_port_close(task, old_shared_port); + nxt_port_use(task, old_shared_port, -1); + + reply = NXT_PORT_MSG_RPC_READY_LAST; + + } else { + +fail: + + reply = NXT_PORT_MSG_RPC_ERROR; + } + + nxt_port_socket_write(task, reply_port, reply, -1, msg->port_msg.stream, + 0, NULL); +} + + static void nxt_router_app_process_remove_pid(nxt_task_t *task, nxt_port_t *port, void *data) @@ -1607,7 +1710,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, app_joint->free_app_work.task = &engine->task; app_joint->free_app_work.obj = app_joint; - port = nxt_port_new(task, (nxt_port_id_t) -1, nxt_pid, + port = nxt_port_new(task, NXT_SHARED_PORT_ID, nxt_pid, NXT_PROCESS_APP); if (nxt_slow_path(port == NULL)) { return NXT_ERROR; @@ -4233,11 +4336,16 @@ static void nxt_router_app_port_ready(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { - nxt_app_t *app; - nxt_port_t *port; - nxt_app_joint_t *app_joint; + nxt_app_t *app; + nxt_bool_t start_process; + nxt_port_t *port; + nxt_app_joint_t *app_joint; + nxt_app_joint_rpc_t *app_joint_rpc; - app_joint = data; + nxt_assert(data != NULL); + + app_joint_rpc = data; + app_joint = app_joint_rpc->app_joint; port = msg->u.new_port; nxt_assert(app_joint != NULL); @@ -4257,14 +4365,37 @@ nxt_router_app_port_ready(nxt_task_t *task, nxt_port_recv_msg_t *msg, return; } - port->app = app; - port->main_app_port = port; - nxt_thread_mutex_lock(&app->mutex); nxt_assert(app->pending_processes != 0); app->pending_processes--; + + if (nxt_slow_path(app->generation != app_joint_rpc->generation)) { + nxt_debug(task, "new port ready for restarted app, send QUIT"); + + start_process = !task->thread->engine->shutdown + && nxt_router_app_can_start(app) + && nxt_router_app_need_start(app); + + if (start_process) { + app->pending_processes++; + } + + nxt_thread_mutex_unlock(&app->mutex); + + nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1, 0, 0, NULL); + + if (start_process) { + nxt_router_start_app_process(task, app); + } + + return; + } + + port->app = app; + port->main_app_port = port; + app->processes++; nxt_port_hash_add(&app->port_hash, port); app->port_hash_count++; @@ -4318,12 +4449,16 @@ static void nxt_router_app_port_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { - nxt_app_t *app; - nxt_app_joint_t *app_joint; - nxt_queue_link_t *link; - nxt_http_request_t *r; + nxt_app_t *app; + nxt_app_joint_t *app_joint; + nxt_queue_link_t *link; + nxt_http_request_t *r; + nxt_app_joint_rpc_t *app_joint_rpc; + + nxt_assert(data != NULL); - app_joint = data; + app_joint_rpc = data; + app_joint = app_joint_rpc->app_joint; nxt_assert(app_joint != NULL); @@ -4490,7 +4625,7 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port, port->pid, port->id, (int) inc_use, (int) got_response); - if (port == app->shared_port) { + if (port->id == NXT_SHARED_PORT_ID) { nxt_thread_mutex_lock(&app->mutex); app->active_requests -= got_response + dec_requests; @@ -4860,6 +4995,8 @@ nxt_router_free_app(nxt_task_t *task, void *obj, void *data) app->shared_port->app = NULL; nxt_port_close(task, app->shared_port); nxt_port_use(task, app->shared_port, -1); + + app->shared_port = NULL; } nxt_thread_mutex_destroy(&app->mutex); diff --git a/src/nxt_router.h b/src/nxt_router.h index 9f26a622..6611cf45 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -125,6 +125,8 @@ struct nxt_app_s { uint32_t max_pending_processes; uint32_t max_requests; + uint32_t generation; + nxt_msec_t timeout; nxt_msec_t idle_timeout; diff --git a/test/python/restart/longstart.py b/test/python/restart/longstart.py new file mode 100644 index 00000000..777398ac --- /dev/null +++ b/test/python/restart/longstart.py @@ -0,0 +1,10 @@ +import os +import time + +time.sleep(2) + +def application(environ, start_response): + body = str(os.getpid()).encode() + + start_response('200', [('Content-Length', str(len(body)))]) + return [body] diff --git a/test/python/restart/v1.py b/test/python/restart/v1.py new file mode 100644 index 00000000..2e45b269 --- /dev/null +++ b/test/python/restart/v1.py @@ -0,0 +1,7 @@ +import os + +def application(environ, start_response): + body = "v1".encode() + + start_response('200', [('Content-Length', str(len(body)))]) + return [body] diff --git a/test/python/restart/v2.py b/test/python/restart/v2.py new file mode 100644 index 00000000..59e3d30f --- /dev/null +++ b/test/python/restart/v2.py @@ -0,0 +1,7 @@ +import os + +def application(environ, start_response): + body = "v2".encode() + + start_response('200', [('Content-Length', str(len(body)))]) + return [body] diff --git a/test/test_python_procman.py b/test/test_python_procman.py index b0d0f5af..a95c5680 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -1,4 +1,5 @@ import re +import shutil import subprocess import time @@ -201,3 +202,81 @@ class TestPythonProcman(TestApplicationPython): assert 'success' in self.conf({"listeners": {}, "applications": {}}) assert len(self.pids_for_process()) == 0, 'stop all' + + def test_python_restart(self, temp_dir): + shutil.copyfile( + option.test_dir + '/python/restart/v1.py', temp_dir + '/wsgi.py' + ) + + self.load( + temp_dir, + name=self.app_name, + processes=1, + environment={'PYTHONDONTWRITEBYTECODE': '1'}, + ) + + b = self.get()['body'] + assert b == "v1", 'process started' + + shutil.copyfile( + option.test_dir + '/python/restart/v2.py', temp_dir + '/wsgi.py' + ) + + b = self.get()['body'] + assert b == "v1", 'still old process' + + assert 'success' in self.conf_get( + '/control/applications/' + self.app_name + '/restart' + ), 'restart processes' + + b = self.get()['body'] + assert b == "v2", 'new process started' + + assert 'error' in self.conf_get( + '/control/applications/blah/restart' + ), 'application incorrect' + + assert 'error' in self.conf_delete( + '/control/applications/' + self.app_name + '/restart' + ), 'method incorrect' + + def test_python_restart_multi(self): + self.conf_proc('2') + + pids = self.pids_for_process() + assert len(pids) == 2, 'restart 2 started' + + assert 'success' in self.conf_get( + '/control/applications/' + self.app_name + '/restart' + ), 'restart processes' + + new_pids = self.pids_for_process() + assert len(new_pids) == 2, 'restart still 2' + + assert len(new_pids.intersection(pids)) == 0, 'restart all new' + + def test_python_restart_longstart(self): + self.load( + 'restart', + name=self.app_name, + module="longstart", + processes={"spare": 1, "max": 2, "idle_timeout": 5}, + ) + + assert len(self.pids_for_process()) == 1, 'longstarts == 1' + + pid = self.get()['body'] + pids = self.pids_for_process() + assert len(pids) == 2, 'longstarts == 2' + + assert 'success' in self.conf_get( + '/control/applications/' + self.app_name + '/restart' + ), 'restart processes' + + # wait for longstarted app + time.sleep(2) + + new_pids = self.pids_for_process() + assert len(new_pids) == 1, 'restart 1' + + assert len(new_pids.intersection(pids)) == 0, 'restart all new' diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index b399dffd..215aa332 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -44,6 +44,7 @@ class TestApplicationPython(TestApplicationProto): for attr in ( 'callable', + 'environment', 'home', 'limits', 'path', -- cgit From db03dfad6745a7d87d784ac51ed2d52e1c50a557 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 3 Aug 2021 13:59:27 +0300 Subject: Fixed dead assignments. Found by Clang Static Analyzer. --- src/nxt_buf.c | 2 -- src/nxt_event_engine.c | 3 +-- src/nxt_h1proto.c | 1 - src/nxt_http_chunk_parse.c | 1 - src/nxt_isolation.c | 4 ++-- src/nxt_main_process.c | 2 -- 6 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/nxt_buf.c b/src/nxt_buf.c index 83be0fac..cbde069e 100644 --- a/src/nxt_buf.c +++ b/src/nxt_buf.c @@ -201,7 +201,6 @@ nxt_buf_completion(nxt_task_t *task, void *obj, void *data) nxt_buf_t *b, *next, *parent; b = obj; - parent = data; nxt_debug(task, "buf completion: %p %p", b, b->mem.start); @@ -275,7 +274,6 @@ nxt_buf_ts_completion(nxt_task_t *task, void *obj, void *data) nxt_buf_t *b, *next, *parent; b = obj; - parent = data; if (nxt_buf_ts_handle(task, obj, data)) { return; diff --git a/src/nxt_event_engine.c b/src/nxt_event_engine.c index 4384d3b1..78c79bb1 100644 --- a/src/nxt_event_engine.c +++ b/src/nxt_event_engine.c @@ -720,11 +720,10 @@ nxt_event_engine_buf_mem_free(nxt_event_engine_t *engine, nxt_buf_t *b) void nxt_event_engine_buf_mem_completion(nxt_task_t *task, void *obj, void *data) { - nxt_event_engine_t *engine; nxt_buf_t *b, *next, *parent; + nxt_event_engine_t *engine; b = obj; - parent = data; nxt_debug(task, "buf completion: %p %p", b, b->mem.start); diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index d3da6942..b683cb22 100755 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -955,7 +955,6 @@ nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r) } else { size = nxt_min(body_buffer_size, size); b->mem.free = nxt_cpymem(b->mem.free, in->mem.pos, size); - body_buffer_size -= size; } in->mem.pos += size; diff --git a/src/nxt_http_chunk_parse.c b/src/nxt_http_chunk_parse.c index be3a2023..deab116d 100644 --- a/src/nxt_http_chunk_parse.c +++ b/src/nxt_http_chunk_parse.c @@ -253,7 +253,6 @@ nxt_http_chunk_buf_completion(nxt_task_t *task, void *obj, void *data) nxt_buf_t *b, *next, *parent; b = obj; - parent = data; nxt_debug(task, "buf completion: %p %p", b, b->mem.start); diff --git a/src/nxt_isolation.c b/src/nxt_isolation.c index cab0074b..e3cb1f22 100644 --- a/src/nxt_isolation.c +++ b/src/nxt_isolation.c @@ -126,10 +126,10 @@ nxt_isolation_main_prefork(nxt_task_t *task, nxt_process_t *process, return ret; } - has_mnt = 0; - #if (NXT_HAVE_CLONE_NEWNS) has_mnt = nxt_is_clone_flag_set(process->isolation.clone.flags, NEWNS); +#else + has_mnt = 0; #endif if (process->user_cred->uid == 0 && !has_mnt) { diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 10bd2518..16c6a297 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -347,8 +347,6 @@ nxt_port_main_start_process_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) nxt_process_init_t *init; nxt_common_app_conf_t *app_conf; - ret = NXT_ERROR; - rt = task->thread->runtime; process = nxt_main_process_new(task, rt); -- cgit From d16cf0416784db82147ee5aaad1054840d028e7d Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Mon, 2 Aug 2021 12:30:38 +0800 Subject: Router: fixed segmentation fault. In the case that routes or upstreams is empty and the pass option is a variable. If the resolved pass is routes or upstreams, a segment error occurred. --- src/nxt_http_route.c | 4 ++++ src/nxt_upstream.c | 4 ++++ test/test_variables.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index c38acdf3..065b3488 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -1567,6 +1567,10 @@ nxt_http_route_find(nxt_http_routes_t *routes, nxt_str_t *name, { nxt_http_route_t **route, **end; + if (routes == NULL) { + return NXT_DECLINED; + } + route = &routes->route[0]; end = route + routes->items; diff --git a/src/nxt_upstream.c b/src/nxt_upstream.c index 9f81b286..de9b1d49 100644 --- a/src/nxt_upstream.c +++ b/src/nxt_upstream.c @@ -78,6 +78,10 @@ nxt_upstream_find(nxt_upstreams_t *upstreams, nxt_str_t *name, uint32_t i, n; nxt_upstream_t *upstream; + if (upstreams == NULL) { + return NXT_DECLINED; + } + upstream = &upstreams->upstream[0]; n = upstreams->items; diff --git a/test/test_variables.py b/test/test_variables.py index 139d867e..d8547b7b 100644 --- a/test/test_variables.py +++ b/test/test_variables.py @@ -100,6 +100,25 @@ class TestVariables(TestApplicationProto): assert self.get(url='/1')['status'] == 200 assert self.get(url='/2')['status'] == 404 + def test_variables_empty(self): + def update_pass(prefix): + assert 'success' in self.conf( + { + "listeners": { + "*:7080": {"pass": prefix + "/$method"}, + }, + }, + ), 'variables empty' + + update_pass("routes"); + assert self.get(url='/1')['status'] == 404 + + update_pass("upstreams"); + assert self.get(url='/2')['status'] == 404 + + update_pass("applications"); + assert self.get(url='/3')['status'] == 404 + def test_variables_invalid(self): def check_variables(routes): assert 'error' in self.conf( -- cgit From 44fe31dc6198e7a6fd752d6cdb7e51be73f6d8eb Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Wed, 4 Aug 2021 18:09:50 +0800 Subject: Added a changelog for ae4f067a9ea4. --- docs/changes.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changes.xml b/docs/changes.xml index ce18875a..63c52633 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -92,6 +92,13 @@ framework. + + +the router process crashed when a request was passed to an empty "routes" +or "upstreams" using a variable "pass" option. + + + -- cgit From 60cf1399611ae1b2728492c94ff57a4a044774b4 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 5 Aug 2021 16:00:01 +0000 Subject: Router: fixed crash when matching an empty address pattern array. A crash would occur when the router tried to match an against an empty address pattern array. The following configuration was used to reproduce the issue: { "listeners": { "127.0.0.1:8082": { "pass": "routes" } }, "routes": [ { "match": { "source": [] }, "action": { "return": 200 } } ] } --- docs/changes.xml | 7 +++++++ src/nxt_http_route.c | 5 +++++ test/test_routing.py | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/docs/changes.xml b/docs/changes.xml index 63c52633..e183f907 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -99,6 +99,13 @@ or "upstreams" using a variable "pass" option. + + +the router process crashed while matching a request to an empty array of +source or destination address patterns. + + + diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 065b3488..b330796f 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -1936,6 +1936,11 @@ nxt_http_route_addr_rule(nxt_http_request_t *r, nxt_http_route_addr_pattern_t *p; n = addr_rule->items; + + if (n == 0) { + return 0; + } + p = &addr_rule->addr_pattern[0] - 1; do { diff --git a/test/test_routing.py b/test/test_routing.py index eaa0a134..ef5622c2 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1751,6 +1751,10 @@ class TestRouting(TestApplicationProto): self.route_match_invalid({"source": "*:1-a"}) self.route_match_invalid({"source": "*:65536"}) + def test_routes_match_source_none(self): + self.route_match({"source": []}) + assert self.get()['status'] == 404, 'source none' + def test_routes_match_destination(self): assert 'success' in self.conf( {"*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}}, -- cgit From 1a85ad378f3374b9c83b59ae9c12277288aa7c30 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Mon, 9 Aug 2021 10:14:57 +0300 Subject: Java: upgrading third-party components. --- auto/modules/java | 8 ++++---- auto/modules/java_jar.sha512 | 28 ++++++++++++++-------------- test/unit/applications/lang/java.py | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/auto/modules/java b/auto/modules/java index 05b3100c..d2699c36 100644 --- a/auto/modules/java +++ b/auto/modules/java @@ -238,7 +238,7 @@ cat << END > $NXT_JAVA_JARS static const char *nxt_java_system_jars[] = { END -NXT_TOMCAT_VERSION=9.0.44 +NXT_TOMCAT_VERSION=9.0.50 NXT_JAR_VERSION=$NXT_TOMCAT_VERSION @@ -271,7 +271,7 @@ NXT_JAR_NAME=tomcat-util . auto/modules/java_get_jar NXT_JAR_NAME=ecj -NXT_JAR_VERSION=3.25.0 +NXT_JAR_VERSION=3.26.0 NXT_JAR_NAMESPACE=org/eclipse/jdt/ . auto/modules/java_get_jar @@ -284,7 +284,7 @@ static const char *nxt_java_unit_jars[] = { "$NXT_UNIT_JAR", END -NXT_JAR_VERSION=9.4.38.v20210224 +NXT_JAR_VERSION=9.4.43.v20210629 NXT_JAR_NAMESPACE=org/eclipse/jetty/ NXT_JAR_NAME=jetty-util @@ -297,7 +297,7 @@ NXT_JAR_NAME=jetty-http . auto/modules/java_get_jar NXT_JAR_NAME=classgraph -NXT_JAR_VERSION=4.8.102 +NXT_JAR_VERSION=4.8.111 NXT_JAR_NAMESPACE=io/github/classgraph/ . auto/modules/java_get_jar diff --git a/auto/modules/java_jar.sha512 b/auto/modules/java_jar.sha512 index 98d27149..6f792ee7 100644 --- a/auto/modules/java_jar.sha512 +++ b/auto/modules/java_jar.sha512 @@ -1,14 +1,14 @@ -5105e9edf0fc6a4a51eb2bb1e2c23bb78a604fe2df3b1e814cf638ce22845a6ae57e75af31db9dd00d5c650403e751659cca8d3bc465fb96525c695188d2055f classgraph-4.8.102.jar -48cee25d195a5c713a962b035ecba633797753474f290c107a0ee96272cd9ae1b6b62b060ef37e0699f6a5af4d4b45e514ef710309f41bdd9440925cf60a111a ecj-3.25.0.jar -3f2a4a5b7f71c6a1317cec29ea12eb04eac68f4cee94c30ffb75fdd36dbc9c622059fb762e27f74f7b7a38eb84966732ba963cc294f5aaf8b7f5f8c9d60c8899 jetty-http-9.4.38.v20210224.jar -cc416b324841e9c259538a3ec678a9d8e07d3bea9b0bb687bc15a759c5065c7de6ea7bbbb15b369b8c6d2023c3294906d8f9cfda28e1b7859283008790581a4d jetty-server-9.4.38.v20210224.jar -efdbeae97c959459c10b05aae62189c535a0935dadf7debf4257257c31a98a4167ca1318daa81dae32161f51117b17468d5fb5a6bb4eea40864ce1265623d684 jetty-util-9.4.38.v20210224.jar -f2534f246c0cec9be576f18d67759b5a997074bb7870cbf7b9469f062ddc6932e5cec45fd439a5beacc8c6248d39dc458af81245a6eb988e294667f2bcd251df tomcat-api-9.0.44.jar -0c8050b04b02a6a1f1384d1a4041594c09868cfeb139978274c5e5797eb95e0668495f489eb61cfe742905e453184edc0ad8a9fd3b2796e960981a20ae4a0334 tomcat-el-api-9.0.44.jar -cc81a06795553f06d3dfe930429fdf47a8e0c1813c554b640d98090cc661c1eb27a91b9a9055bd14d918fc9451073632a635741dd413fd7b4a380d8907862e14 tomcat-jasper-9.0.44.jar -de50107ff31b8c5a7eb97fe86c2fdfa38305be632c8d62329219884dfb6bbe76372b40a67e808263a55b510a41cde639d55e7925d89db06472392519f0410063 tomcat-jasper-el-9.0.44.jar -69a79e58cea950b713f26a94e1db90221c297375a338f010d1332027ef5d93fd262163ec787b06d8cd41a4a31faa81bb4fc83836148eb14a6757591f6fe5f3c6 tomcat-jsp-api-9.0.44.jar -ca036312596dbd92ae43dda79a9a5ad168cf90a12369636fdc6febbedb559b83d5fa22d77629659b588d70990f8a077fd8356be1c56b0227e07688b67dd7c76e tomcat-juli-9.0.44.jar -952c122413805de16de603cfc233785e53b558b39f146b6fa4e11324e26572e4aee76fec2ee69adecc8f4271cd28acb917e1a9e61a908710b3582ab1f76c9706 tomcat-servlet-api-9.0.44.jar -6920bfb2b1ae173d716fedbf4f238003f569a161f277a888c2d53dac0af80ef0480c9ca3be0ede4583716c1b97c1cfa1665439d14274b7d73ec948fd9f952b89 tomcat-util-9.0.44.jar -915304750cbdffc4c9926fcb22eb928d2895cccd8b3f6f13aefb4e5302cccf5f513502b70812630026dd2968712c4c3b1cfe98d37ecf7b349988e7255ddc5b18 tomcat-util-scan-9.0.44.jar +9ffd4be045bd5f2f0400aa25270ee8663ed8f794994e81a0fe6f2c6e1ca3e77b9dcd1521c208e3ca928535919868c6607e1fc2303188e47347aff1ac58641353 classgraph-4.8.111.jar +ab441acf5551a7dc81c353eaccb3b3df9e89a48987294d19e39acdb83a5b640fcdff7414cee29f5b96eaa8826647f1d5323e185018fe33a64c402d69c73c9158 ecj-3.26.0.jar +a3ce1a5a41c9791ece4cbbf049ec4add1ec41330743d6757daea520f8b329299f5dd274f9e5741ba41fe49510f203efd19540d2404390eca53421132f5f46d4b jetty-http-9.4.43.v20210629.jar +61a14e97baac9962bd68ece24f8b967eec8e32edfebfa27c6a13996a86650d82f8977bf1aa582fc9706a1b028cb3cec0057c97628235dfc130061939845229e6 jetty-server-9.4.43.v20210629.jar +304fcdba2bdbf37e8f2ea69a3f5fbdffdfefd98d80fa78883b1dca1129a4907cef63eb2fa7c83eef359022a3b6a2f3ff742d8d23075c83d049ac01f1402e97f8 jetty-util-9.4.43.v20210629.jar +7fb21023c0469296ccb257d9672d727aeb4b437d1b000344c5087bb120ca0e5fc3fcddd6524b851c7cffbf01c042edb5e779e03f82753cf61d43c920f260ccdf tomcat-api-9.0.50.jar +d505100fd7bca52db627c4f36aeb244ada08121ba35509ef4251a1779ef68261599ba7d5e08667441c6582cb471162092c150adc46e670550efbea16ad8dc3dd tomcat-el-api-9.0.50.jar +a0a9d99428a96f6e33d24938ab23ffff7c0b04e737183616e951e3bb978da9f057a19da6ebe0cbf2e7ad0c87735c19aae12ececd588405779dac725d37dee2ea tomcat-jasper-9.0.50.jar +ce8ee01533b2fea8c2611a96eccc8ce89ad99f8f646e5e9a9e6883909ad133952e7db1424900654747c3140b1581dfcfc25dafa0e9773f4de74082100b7ac2f1 tomcat-jasper-el-9.0.50.jar +fa05e5d950340dbf806f2dd74f1c8549d12e477dd7fd7e4755a64641220190352a19faf669580f35799b884ab08d96770a8514c2b67fce9c6a1742dcaba954ef tomcat-jsp-api-9.0.50.jar +fff70d05d13217fa98247dfa35aae43179aab4bb7b00073f0f5de331eb9f3d331aef218670e5e6e2280fb74d2f98733cd4bd72d499cdc29ff6dcaf6cbbbf470f tomcat-juli-9.0.50.jar +56a6f6c2887546390ca66463f2a748ee56a1537fe9a4a2edc0ea21abf332a473742523e4b8e8433fa9e132f359b106bff7bf96ed611532c6738a236095237c33 tomcat-servlet-api-9.0.50.jar +d02d4f166fa9490e2af22c314180f54b48fec120957bd483529bf65df0f0187736177e140067bc775d7c7b751eedbf953e259a20bc76d3b9a974e7b3b9be2024 tomcat-util-9.0.50.jar +1bd03304eaf7c8bb10092356c31c42366465f29f4828a935cb5ec9a890c05d7b41b8ddcdbad507aeb4cb09441f39e7cb10832737bd8e754e84cf98a6837c1cd5 tomcat-util-scan-9.0.50.jar diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index c9c2095e..3a620aa0 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -52,7 +52,7 @@ class TestApplicationJava(TestApplicationProto): os.makedirs(classes_path) classpath = ( - option.current_dir + '/build/tomcat-servlet-api-9.0.44.jar' + option.current_dir + '/build/tomcat-servlet-api-9.0.50.jar' ) ws_jars = glob.glob( -- cgit From 3580842d34f8543f7bb41551f7a0dec8723289a8 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Mon, 9 Aug 2021 10:15:00 +0300 Subject: Python: fixing misprint in error message. --- src/python/nxt_python.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c index bdce68b2..abb04194 100644 --- a/src/python/nxt_python.c +++ b/src/python/nxt_python.c @@ -364,13 +364,13 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target, obj = PyDict_GetItemString(PyModule_GetDict(module), callable); if (nxt_slow_path(obj == NULL)) { nxt_alert(task, "Python failed to get \"%s\" from module \"%s\"", - callable, module); + callable, module_name); goto fail; } if (nxt_slow_path(PyCallable_Check(obj) == 0)) { nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object", - callable, module); + callable, module_name); goto fail; } -- cgit From 5f67d30ec709ea101870d0a91db8953fc49c7810 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Wed, 11 Aug 2021 19:01:04 +0300 Subject: Tests: retrying directory remove if resource is busy. --- test/conftest.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index 87471287..ea3aaf72 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -328,7 +328,14 @@ def run(request): ): os.remove(path) else: - shutil.rmtree(path) + for attempt in range(10): + try: + shutil.rmtree(path) + break + except OSError as err: + if err.errno != 16: + raise + time.sleep(1) # check descriptors -- cgit From 9988569beafce7f587f17a9ef710b76e9bfa3165 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Wed, 11 Aug 2021 19:01:16 +0300 Subject: Tests: initialising log params before first _print_log(). --- test/conftest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index ea3aaf72..4d46e2fc 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -400,6 +400,8 @@ def unit_run(): with open(temp_dir + '/unit.log', 'w') as log: unit_instance['process'] = subprocess.Popen(unitd_args, stderr=log) + Log.temp_dir = temp_dir + if not waitforfiles(temp_dir + '/control.unit.sock'): _print_log() exit('Could not start unit') @@ -409,7 +411,6 @@ def unit_run(): unit_instance['unitd'] = unitd option.temp_dir = temp_dir - Log.temp_dir = temp_dir with open(temp_dir + '/unit.pid', 'r') as f: unit_instance['pid'] = f.read().rstrip() @@ -487,7 +488,7 @@ def _check_alerts(*, log=None): print('skipped.') -def _print_log(log): +def _print_log(log=None): path = Log.get_path() print('Path to unit.log:\n' + path + '\n') -- cgit From 73ea6a1c3a7a4a7f3ab14230b80e783e57a7d830 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 12 Aug 2021 08:23:09 +0000 Subject: Introduced nxt_sockaddr_parse_optport() for addresses w/o ports. --- src/nxt_http_route_addr.c | 19 +----- src/nxt_sockaddr.c | 156 ++++++++++++++++++++++++++++++++-------------- src/nxt_sockaddr.h | 3 + 3 files changed, 114 insertions(+), 64 deletions(-) diff --git a/src/nxt_http_route_addr.c b/src/nxt_http_route_addr.c index 6d4955ed..2907a902 100644 --- a/src/nxt_http_route_addr.c +++ b/src/nxt_http_route_addr.c @@ -8,7 +8,6 @@ #include -static nxt_bool_t nxt_str_looks_like_ipv6(const nxt_str_t *str); #if (NXT_INET6) static nxt_bool_t nxt_valid_ipv6_blocks(u_char *c, size_t len); #endif @@ -57,7 +56,7 @@ nxt_http_route_addr_pattern_parse(nxt_mp_t *mp, goto parse_port; } - if (nxt_str_looks_like_ipv6(&addr)) { + if (nxt_inet6_probe(&addr)) { #if (NXT_INET6) u_char *end; uint8_t i; @@ -304,22 +303,6 @@ parse_port: } -static nxt_bool_t -nxt_str_looks_like_ipv6(const nxt_str_t *str) -{ - u_char *colon, *end; - - colon = nxt_memchr(str->start, ':', str->length); - - if (colon != NULL) { - end = str->start + str->length; - colon = nxt_memchr(colon + 1, ':', end - (colon + 1)); - } - - return (colon != NULL); -} - - #if (NXT_INET6) static nxt_bool_t diff --git a/src/nxt_sockaddr.c b/src/nxt_sockaddr.c index 47ee165f..730428e4 100644 --- a/src/nxt_sockaddr.c +++ b/src/nxt_sockaddr.c @@ -605,10 +605,35 @@ nxt_sockaddr_parse(nxt_mp_t *mp, nxt_str_t *addr) { nxt_sockaddr_t *sa; + sa = nxt_sockaddr_parse_optport(mp, addr); + + if (sa != NULL + && sa->u.sockaddr.sa_family != AF_UNIX + && nxt_sockaddr_port_number(sa) == 0) + { + nxt_thread_log_error(NXT_LOG_ERR, + "The address \"%V\" must specify a port.", addr); + return NULL; + } + + return sa; +} + + +nxt_sockaddr_t * +nxt_sockaddr_parse_optport(nxt_mp_t *mp, nxt_str_t *addr) +{ + nxt_sockaddr_t *sa; + + if (addr->length == 0) { + nxt_thread_log_error(NXT_LOG_ERR, "socket address cannot be empty"); + return NULL; + } + if (addr->length > 6 && nxt_memcmp(addr->start, "unix:", 5) == 0) { sa = nxt_sockaddr_unix_parse(mp, addr); - } else if (addr->length != 0 && addr->start[0] == '[') { + } else if (addr->start[0] == '[' || nxt_inet6_probe(addr)) { sa = nxt_sockaddr_inet6_parse(mp, addr); } else { @@ -703,44 +728,60 @@ nxt_sockaddr_inet6_parse(nxt_mp_t *mp, nxt_str_t *addr) nxt_int_t ret, port; nxt_sockaddr_t *sa; - length = addr->length - 1; - start = addr->start + 1; + if (addr->start[0] == '[') { + length = addr->length - 1; + start = addr->start + 1; - end = nxt_memchr(start, ']', length); - - if (end != NULL) { - sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in6), - NXT_INET6_ADDR_STR_LEN); - if (nxt_slow_path(sa == NULL)) { + end = nxt_memchr(start, ']', length); + if (nxt_slow_path(end == NULL)) { return NULL; } - ret = nxt_inet6_addr(&sa->u.sockaddr_in6.sin6_addr, start, end - start); + p = end + 1; + + } else { + length = addr->length; + start = addr->start; + end = addr->start + addr->length; + p = NULL; + } - if (nxt_fast_path(ret == NXT_OK)) { - p = end + 1; - length = (start + length) - p; + port = 0; - if (length > 2 && *p == ':') { - port = nxt_int_parse(p + 1, length - 1); + if (p != NULL) { + length = (start + length) - p; - if (port > 0 && port < 65536) { - sa->u.sockaddr_in6.sin6_port = htons((in_port_t) port); - sa->u.sockaddr_in6.sin6_family = AF_INET6; + if (length < 2 || *p != ':') { + nxt_thread_log_error(NXT_LOG_ERR, "invalid IPv6 address in \"%V\"", + addr); + return NULL; + } - return sa; - } - } + port = nxt_int_parse(p + 1, length - 1); + if (port < 1 || port > 65535) { nxt_thread_log_error(NXT_LOG_ERR, "invalid port in \"%V\"", addr); - return NULL; } } - nxt_thread_log_error(NXT_LOG_ERR, "invalid IPv6 address in \"%V\"", addr); + sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in6), + NXT_INET6_ADDR_STR_LEN); + if (nxt_slow_path(sa == NULL)) { + return NULL; + } - return NULL; + ret = nxt_inet6_addr(&sa->u.sockaddr_in6.sin6_addr, start, end - start); + if (nxt_slow_path(ret != NXT_OK)) { + nxt_thread_log_error(NXT_LOG_ERR, "invalid IPv6 address in \"%V\"", + addr); + return NULL; + } + + sa->u.sockaddr_in6.sin6_family = AF_INET6; + sa->u.sockaddr_in6.sin6_port = htons((in_port_t) port); + + return sa; #else /* !(NXT_INET6) */ @@ -763,41 +804,48 @@ nxt_sockaddr_inet_parse(nxt_mp_t *mp, nxt_str_t *addr) p = nxt_memchr(addr->start, ':', addr->length); - if (nxt_fast_path(p != NULL)) { - inaddr = INADDR_ANY; + if (p == NULL) { + length = addr->length; + + } else { length = p - addr->start; + } - if (length != 1 || addr->start[0] != '*') { - inaddr = nxt_inet_addr(addr->start, length); + inaddr = INADDR_ANY; - if (nxt_slow_path(inaddr == INADDR_NONE)) { - nxt_thread_log_error(NXT_LOG_ERR, "invalid address \"%V\"", - addr); - return NULL; - } + if (length != 1 || addr->start[0] != '*') { + inaddr = nxt_inet_addr(addr->start, length); + if (nxt_slow_path(inaddr == INADDR_NONE)) { + nxt_thread_log_error(NXT_LOG_ERR, "invalid address \"%V\"", addr); + return NULL; } + } + + port = 0; + if (p != NULL) { p++; length = (addr->start + addr->length) - p; - port = nxt_int_parse(p, length); - - if (port > 0 && port < 65536) { - sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in), - NXT_INET_ADDR_STR_LEN); - if (nxt_fast_path(sa != NULL)) { - sa->u.sockaddr_in.sin_family = AF_INET; - sa->u.sockaddr_in.sin_port = htons((in_port_t) port); - sa->u.sockaddr_in.sin_addr.s_addr = inaddr; - } + port = nxt_int_parse(p, length); - return sa; + if (port < 1 || port > 65535) { + nxt_thread_log_error(NXT_LOG_ERR, "invalid port in \"%V\"", addr); + return NULL; } } - nxt_thread_log_error(NXT_LOG_ERR, "invalid port in \"%V\"", addr); + sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in), + NXT_INET_ADDR_STR_LEN); + if (nxt_slow_path(sa == NULL)) { + return NULL; + } - return NULL; + sa->u.sockaddr_in.sin_family = AF_INET; + sa->u.sockaddr_in.sin_addr.s_addr = inaddr; + sa->u.sockaddr_in.sin_port = htons((in_port_t) port); + + return sa; } @@ -1320,3 +1368,19 @@ nxt_inet6_addr(struct in6_addr *in6_addr, u_char *buf, size_t length) } #endif + + +nxt_bool_t +nxt_inet6_probe(nxt_str_t *str) +{ + u_char *colon, *end; + + colon = nxt_memchr(str->start, ':', str->length); + + if (colon != NULL) { + end = str->start + str->length; + colon = nxt_memchr(colon + 1, ':', end - (colon + 1)); + } + + return (colon != NULL); +} diff --git a/src/nxt_sockaddr.h b/src/nxt_sockaddr.h index aa4da5d2..a8f1b393 100644 --- a/src/nxt_sockaddr.h +++ b/src/nxt_sockaddr.h @@ -91,12 +91,15 @@ NXT_EXPORT nxt_bool_t nxt_sockaddr_cmp(nxt_sockaddr_t *sa1, NXT_EXPORT size_t nxt_sockaddr_ntop(nxt_sockaddr_t *sa, u_char *buf, u_char *end, nxt_bool_t port); NXT_EXPORT nxt_sockaddr_t *nxt_sockaddr_parse(nxt_mp_t *mp, nxt_str_t *addr); +NXT_EXPORT nxt_sockaddr_t *nxt_sockaddr_parse_optport(nxt_mp_t *mp, + nxt_str_t *addr); NXT_EXPORT void nxt_job_sockaddr_parse(nxt_job_sockaddr_parse_t *jbs); NXT_EXPORT in_addr_t nxt_inet_addr(u_char *buf, size_t len); #if (NXT_INET6) NXT_EXPORT nxt_int_t nxt_inet6_addr(struct in6_addr *in6_addr, u_char *buf, size_t len); #endif +NXT_EXPORT nxt_bool_t nxt_inet6_probe(nxt_str_t *addr); #define NXT_INET_ADDR_STR_LEN nxt_length("255.255.255.255:65535") -- cgit From ca373aaccd276fb412e59557a3971a8d06ada0f8 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 12 Aug 2021 08:23:16 +0000 Subject: Router: client IP address replacement. This commit introduces the replacement of the client address based on the value of a specified HTTP header. This is intended for use when Unit is placed behind a reverse proxy like nginx or a CDN. You must specify the source addresses of the trusted proxies. This can be accomplished with any valid IP pattern supported by Unit's match block: ["10.0.0.1", "10.4.0.0/16", "!192.168.1.1"] The feature is configured per listener. The client address replacement functionality only operates when there is a source IP match and the specified header is present. Typically this would be an 'X-Forwarded-For' header. { "listeners": { "127.0.0.1:8080": { "client_ip": { "header": "X-Forwarded-For", "source": [ "10.0.0.0/8" ] }, "pass": "applications/my_app" }, } } If a request occurs and Unit receives a header like below: "X-Forwarded-For: 84.123.23.23" By default, Unit trusts the last rightmost IP in the header, so REMOTE_ADDR will be set to 84.123.23.23 if the connection originated from 10.0.0.0/8. If Unit runs behind consecutive reverse proxies and receives a header similar to the following: "X-Forwarded-For: 84.123.23.23, 10.0.0.254" You will need to enable "recursive" checking, which walks the header from last address to first and chooses the first non-trusted address it finds. { "listeners": { "127.0.0.1:8080": { "client_ip": { "header": "X-Forwarded-For", "source": [ "10.0.0.0/8" ] "recursive": true, }, "pass": "applications/my_app" }, } } If a connection from 10.0.0.0/8 occurs, the chain is walked. Here, 10.0.0.254 is also a trusted address so the client address will be replaced with 84.123.23.23. If all IP addresses in the header are trusted, the client address is set to the first address in the header: If 10.0.0.0/8 is trusted and "X-Forwarded-For: 10.0.0.3, 10.0.0.2, 10.0.0.1", the client address will be replaced with 10.0.0.3. --- docs/changes.xml | 6 ++ src/nxt_conf_validation.c | 25 ++++++++ src/nxt_http.h | 17 +++++- src/nxt_http_request.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++ src/nxt_http_route.c | 12 ++-- src/nxt_router.c | 86 +++++++++++++++++++++++++- src/nxt_router.h | 3 + 7 files changed, 288 insertions(+), 11 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index e183f907..56dfa038 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -49,6 +49,12 @@ application restart control. + + +client IP address replacement from specified HTTP header field. + + + TLS connection was rejected for configuration with more than one diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 0ec2e811..0106ebc8 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -208,6 +208,7 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_setting_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_websocket_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_static_members[]; +static nxt_conf_vldt_object_t nxt_conf_vldt_client_ip_members[]; #if (NXT_TLS) static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_session_members[]; @@ -351,6 +352,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = { .name = nxt_string("application"), .type = NXT_CONF_VLDT_STRING, .validator = nxt_conf_vldt_app_name, + }, { + .name = nxt_string("client_ip"), + .type = NXT_CONF_VLDT_OBJECT, + .validator = nxt_conf_vldt_object, + .u.members = nxt_conf_vldt_client_ip_members }, #if (NXT_TLS) @@ -366,6 +372,25 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_client_ip_members[] = { + { + .name = nxt_string("source"), + .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + .validator = nxt_conf_vldt_match_addrs, + .flags = NXT_CONF_VLDT_REQUIRED + }, { + .name = nxt_string("header"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED + }, { + .name = nxt_string("recursive"), + .type = NXT_CONF_VLDT_BOOLEAN, + }, + + NXT_CONF_VLDT_END +}; + + #if (NXT_TLS) static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = { diff --git a/src/nxt_http.h b/src/nxt_http.h index faba83f2..3bc2fd61 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -197,8 +197,9 @@ struct nxt_http_request_s { }; -typedef struct nxt_http_route_s nxt_http_route_t; -typedef struct nxt_http_route_rule_s nxt_http_route_rule_t; +typedef struct nxt_http_route_s nxt_http_route_t; +typedef struct nxt_http_route_rule_s nxt_http_route_rule_t; +typedef struct nxt_http_route_addr_rule_s nxt_http_route_addr_rule_t; typedef struct { @@ -254,6 +255,14 @@ typedef struct { } nxt_http_proto_table_t; +struct nxt_http_client_ip_s { + nxt_http_route_addr_rule_t *source; + nxt_str_t *header; + uint32_t header_hash; + uint8_t recursive; /* 1 bit */ +}; + + #define NXT_HTTP_DATE_LEN nxt_length("Wed, 31 Dec 1986 16:40:00 GMT") nxt_inline u_char * @@ -311,6 +320,10 @@ nxt_int_t nxt_http_pass_segments(nxt_mp_t *mp, nxt_str_t *pass, nxt_str_t *segments, nxt_uint_t n); nxt_http_action_t *nxt_http_pass_application(nxt_task_t *task, nxt_router_conf_t *rtcf, nxt_str_t *name); +nxt_http_route_addr_rule_t *nxt_http_route_addr_rule_create( + nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv); +nxt_int_t nxt_http_route_addr_rule(nxt_http_request_t *r, + nxt_http_route_addr_rule_t *addr_rule, nxt_sockaddr_t *sockaddr); nxt_http_route_rule_t *nxt_http_route_types_rule_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *types); nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 16563a98..b71b25d9 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -10,6 +10,10 @@ static nxt_int_t nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp); static void nxt_http_request_start(nxt_task_t *task, void *obj, void *data); +static nxt_int_t nxt_http_request_client_ip(nxt_task_t *task, + nxt_http_request_t *r); +static nxt_sockaddr_t *nxt_http_request_client_ip_sockaddr( + nxt_http_request_t *r, u_char *start, size_t len); static void nxt_http_request_ready(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_proto_info(nxt_task_t *task, nxt_http_request_t *r); @@ -272,16 +276,162 @@ static const nxt_http_request_state_t nxt_http_request_init_state static void nxt_http_request_start(nxt_task_t *task, void *obj, void *data) { + nxt_int_t ret; nxt_http_request_t *r; r = obj; r->state = &nxt_http_request_body_state; + ret = nxt_http_request_client_ip(task, r); + if (nxt_slow_path(ret != NXT_OK)) { + nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); + } + nxt_http_request_read_body(task, r); } +static nxt_int_t +nxt_http_request_client_ip(nxt_task_t *task, nxt_http_request_t *r) +{ + u_char *start, *p; + nxt_int_t ret, i, len; + nxt_str_t *header; + nxt_array_t *fields_arr; /* of nxt_http_field_t * */ + nxt_sockaddr_t *sa, *prev_sa; + nxt_http_field_t *f, **fields; + nxt_http_client_ip_t *client_ip; + + client_ip = r->conf->socket_conf->client_ip; + + if (client_ip == NULL) { + return NXT_OK; + } + + ret = nxt_http_route_addr_rule(r, client_ip->source, r->remote); + if (ret <= 0) { + return NXT_OK; + } + + header = client_ip->header; + + fields_arr = nxt_array_create(r->mem_pool, 2, sizeof(nxt_http_field_t *)); + if (nxt_slow_path(fields_arr == NULL)) { + return NXT_ERROR; + } + + nxt_list_each(f, r->fields) { + if (f->hash == client_ip->header_hash + && f->name_length == client_ip->header->length + && f->value_length > 0 + && nxt_memcasecmp(f->name, header->start, header->length) == 0) + { + fields = nxt_array_add(fields_arr); + if (nxt_slow_path(fields == NULL)) { + return NXT_ERROR; + } + + *fields = f; + } + } nxt_list_loop; + + prev_sa = r->remote; + fields = (nxt_http_field_t **) fields_arr->elts; + + i = fields_arr->nelts; + + while (i-- > 0) { + f = fields[i]; + start = f->value; + len = f->value_length; + + do { + for (p = start + len - 1; p > start; p--, len--) { + if (*p != ' ' && *p != ',') { + break; + } + } + + for (/* void */; p > start; p--) { + if (*p == ' ' || *p == ',') { + p++; + break; + } + } + + sa = nxt_http_request_client_ip_sockaddr(r, p, len - (p - start)); + if (nxt_slow_path(sa == NULL)) { + if (prev_sa != NULL) { + r->remote = prev_sa; + } + + return NXT_OK; + } + + if (!client_ip->recursive) { + r->remote = sa; + + return NXT_OK; + } + + ret = nxt_http_route_addr_rule(r, client_ip->source, sa); + if (ret <= 0 || (i == 0 && p == start)) { + r->remote = sa; + + return NXT_OK; + } + + prev_sa = sa; + len = p - 1 - start; + + } while (len > 0); + } + + return NXT_OK; +} + + +static nxt_sockaddr_t * +nxt_http_request_client_ip_sockaddr(nxt_http_request_t *r, u_char *start, + size_t len) +{ + nxt_str_t addr; + nxt_sockaddr_t *sa; + + addr.start = start; + addr.length = len; + + sa = nxt_sockaddr_parse_optport(r->mem_pool, &addr); + if (nxt_slow_path(sa == NULL)) { + return NULL; + } + + switch (sa->u.sockaddr.sa_family) { + case AF_INET: + if (sa->u.sockaddr_in.sin_addr.s_addr == INADDR_ANY) { + return NULL; + } + + break; + +#if (NXT_INET6) + case AF_INET6: + if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sockaddr_in6.sin6_addr)) { + return NULL; + } + + break; +#endif /* NXT_INET6 */ + + default: + return NULL; + } + + return sa; +} + + static const nxt_http_request_state_t nxt_http_request_body_state nxt_aligned(64) = { diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index b330796f..cff69f96 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -135,12 +135,12 @@ typedef struct { } nxt_http_route_table_t; -typedef struct { +struct nxt_http_route_addr_rule_s { /* The object must be the first field. */ nxt_http_route_object_t object:8; uint32_t items; nxt_http_route_addr_pattern_t addr_pattern[0]; -} nxt_http_route_addr_rule_t; +}; typedef union { @@ -194,8 +194,6 @@ static nxt_http_route_ruleset_t *nxt_http_route_ruleset_create(nxt_task_t *task, static nxt_http_route_rule_t *nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *rule_cv, nxt_str_t *name, nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding); -static nxt_http_route_addr_rule_t *nxt_http_route_addr_rule_create( - nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv); static nxt_http_route_rule_t *nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_bool_t case_sensitive, nxt_http_route_pattern_case_t pattern_case, @@ -237,8 +235,6 @@ static nxt_int_t nxt_http_route_table(nxt_http_request_t *r, nxt_http_route_table_t *table); static nxt_int_t nxt_http_route_ruleset(nxt_http_request_t *r, nxt_http_route_ruleset_t *ruleset); -static nxt_int_t nxt_http_route_addr_rule(nxt_http_request_t *r, - nxt_http_route_addr_rule_t *addr_rule, nxt_sockaddr_t *sockaddr); static nxt_int_t nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule); static nxt_int_t nxt_http_route_header(nxt_http_request_t *r, @@ -940,7 +936,7 @@ nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, } -static nxt_http_route_addr_rule_t * +nxt_http_route_addr_rule_t * nxt_http_route_addr_rule_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv) { @@ -1927,7 +1923,7 @@ nxt_http_route_addr_pattern_match(nxt_http_route_addr_pattern_t *p, } -static nxt_int_t +nxt_int_t nxt_http_route_addr_rule(nxt_http_request_t *r, nxt_http_route_addr_rule_t *addr_rule, nxt_sockaddr_t *sa) { diff --git a/src/nxt_router.c b/src/nxt_router.c index 8360e75a..1aa919c2 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -107,6 +107,9 @@ static nxt_int_t nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, u_char *start, u_char *end); static nxt_int_t nxt_router_conf_process_static(nxt_task_t *task, nxt_router_conf_t *rtcf, nxt_conf_value_t *conf); +static nxt_int_t nxt_router_conf_process_client_ip(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_socket_conf_t *skcf, + nxt_conf_value_t *conf); static nxt_app_t *nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name); static nxt_int_t nxt_router_apps_hash_test(nxt_lvlhsh_query_t *lhq, void *data); @@ -1450,7 +1453,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *conf, *http, *value, *websocket; nxt_conf_value_t *applications, *application; nxt_conf_value_t *listeners, *listener; - nxt_conf_value_t *routes_conf, *static_conf; + nxt_conf_value_t *routes_conf, *static_conf, *client_ip_conf; nxt_socket_conf_t *skcf; nxt_http_routes_t *routes; nxt_event_engine_t *engine; @@ -1472,6 +1475,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, #endif static nxt_str_t static_path = nxt_string("/settings/http/static"); static nxt_str_t websocket_path = nxt_string("/settings/http/websocket"); + static nxt_str_t client_ip_path = nxt_string("/client_ip"); conf = nxt_conf_json_parse(tmcf->mem_pool, start, end, NULL); if (conf == NULL) { @@ -1843,6 +1847,13 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, t->length = nxt_strlen(t->start); } + client_ip_conf = nxt_conf_get_path(listener, &client_ip_path); + ret = nxt_router_conf_process_client_ip(task, tmcf, skcf, + client_ip_conf); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + #if (NXT_TLS) certificate = nxt_conf_get_path(listener, &certificate_path); @@ -2085,6 +2096,79 @@ nxt_router_conf_process_static(nxt_task_t *task, nxt_router_conf_t *rtcf, } +static nxt_int_t +nxt_router_conf_process_client_ip(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_socket_conf_t *skcf, nxt_conf_value_t *conf) +{ + char c; + size_t i; + nxt_mp_t *mp; + uint32_t hash; + nxt_str_t header; + nxt_conf_value_t *source_conf, *header_conf, *recursive_conf; + nxt_http_client_ip_t *client_ip; + nxt_http_route_addr_rule_t *source; + + static nxt_str_t header_path = nxt_string("/header"); + static nxt_str_t source_path = nxt_string("/source"); + static nxt_str_t recursive_path = nxt_string("/recursive"); + + if (conf == NULL) { + skcf->client_ip = NULL; + + return NXT_OK; + } + + mp = tmcf->router_conf->mem_pool; + + source_conf = nxt_conf_get_path(conf, &source_path); + header_conf = nxt_conf_get_path(conf, &header_path); + recursive_conf = nxt_conf_get_path(conf, &recursive_path); + + if (source_conf == NULL || header_conf == NULL) { + return NXT_ERROR; + } + + client_ip = nxt_mp_zget(mp, sizeof(nxt_http_client_ip_t)); + if (nxt_slow_path(client_ip == NULL)) { + return NXT_ERROR; + } + + source = nxt_http_route_addr_rule_create(task, mp, source_conf); + if (nxt_slow_path(source == NULL)) { + return NXT_ERROR; + } + + client_ip->source = source; + + nxt_conf_get_string(header_conf, &header); + + if (recursive_conf != NULL) { + client_ip->recursive = nxt_conf_get_boolean(recursive_conf); + } + + client_ip->header = nxt_str_dup(mp, NULL, &header); + if (nxt_slow_path(client_ip->header == NULL)) { + return NXT_ERROR; + } + + hash = NXT_HTTP_FIELD_HASH_INIT; + + for (i = 0; i < client_ip->header->length; i++) { + c = client_ip->header->start[i]; + hash = nxt_http_field_hash_char(hash, nxt_lowcase(c)); + } + + hash = nxt_http_field_hash_end(hash) & 0xFFFF; + + client_ip->header_hash = hash; + + skcf->client_ip = client_ip; + + return NXT_OK; +} + + static nxt_app_t * nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name) { diff --git a/src/nxt_router.h b/src/nxt_router.h index 6611cf45..fc068b53 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -18,6 +18,7 @@ typedef struct nxt_http_request_s nxt_http_request_t; typedef struct nxt_http_action_s nxt_http_action_t; typedef struct nxt_http_routes_s nxt_http_routes_t; +typedef struct nxt_http_client_ip_s nxt_http_client_ip_t; typedef struct nxt_upstream_s nxt_upstream_t; typedef struct nxt_upstreams_s nxt_upstreams_t; typedef struct nxt_router_access_log_s nxt_router_access_log_t; @@ -195,6 +196,8 @@ typedef struct { uint8_t discard_unsafe_fields; /* 1 bit */ + nxt_http_client_ip_t *client_ip; + #if (NXT_TLS) nxt_tls_conf_t *tls; #endif -- cgit From 039d032dd6f4d9720ea4e009925a45f4160df55c Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 12 Aug 2021 08:23:23 +0000 Subject: Tests: client IP address replacement. --- test/python/client_ip/wsgi.py | 4 ++ test/test_client_ip.py | 129 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 test/python/client_ip/wsgi.py create mode 100644 test/test_client_ip.py diff --git a/test/python/client_ip/wsgi.py b/test/python/client_ip/wsgi.py new file mode 100644 index 00000000..0e12db0a --- /dev/null +++ b/test/python/client_ip/wsgi.py @@ -0,0 +1,4 @@ +def application(env, start_response): + ip = env['REMOTE_ADDR'].encode() + start_response('200', [('Content-Length', str(len(ip)))]) + return ip diff --git a/test/test_client_ip.py b/test/test_client_ip.py new file mode 100644 index 00000000..0084574e --- /dev/null +++ b/test/test_client_ip.py @@ -0,0 +1,129 @@ +import pytest + +from unit.applications.lang.python import TestApplicationPython + + +class TestClientIP(TestApplicationPython): + prerequisites = {'modules': {'python': 'any'}} + + def client_ip(self, options): + assert 'success' in self.conf( + { + "127.0.0.1:7081": + {"client_ip": options, "pass": "applications/client_ip"}, + "[::1]:7082": + {"client_ip": options, "pass": "applications/client_ip"}, + }, + 'listeners', + ), 'listeners configure' + + def get_xff(self, xff, sock_type='ipv4'): + port = 7081 if sock_type == 'ipv4' else 7082 + + return self.get( + sock_type=sock_type, + port=port, + headers={'Connection': 'close', 'X-Forwarded-For': xff}, + )['body'] + + def setup_method(self): + self.load('client_ip') + + def test_settings_client_ip_single_ip(self): + self.client_ip( + {'header': 'X-Forwarded-For', 'source': '123.123.123.123'} + ) + + assert self.get(port=7081)['body'] == '127.0.0.1', 'ipv4 default' + assert ( + self.get(sock_type='ipv6', port=7082)['body'] == '::1' + ), 'ipv6 default' + assert self.get_xff('1.1.1.1') == '127.0.0.1', 'bad source' + assert self.get_xff('blah') == '127.0.0.1', 'bad header' + assert self.get_xff('1.1.1.1', 'ipv6') == '::1', 'bad source ipv6' + + self.client_ip({'header': 'X-Forwarded-For', 'source': '127.0.0.1'}) + + assert self.get(port=7081)['body'] == '127.0.0.1', 'ipv4 default 2' + assert ( + self.get(sock_type='ipv6', port=7082)['body'] == '::1' + ), 'ipv6 default 2' + assert self.get_xff('1.1.1.1') == '1.1.1.1', 'replace' + assert self.get_xff('blah') == '127.0.0.1', 'bad header 2' + assert ( + self.get_xff('1.1.1.1', 'ipv6') == '::1' + ), 'bad source ipv6 2' + + self.client_ip({'header': 'X-Forwarded-For', 'source': '!127.0.0.1'}) + + assert self.get_xff('1.1.1.1') == '127.0.0.1', 'bad source 3' + assert self.get_xff('1.1.1.1', 'ipv6') == '1.1.1.1', 'replace 2' + + def test_settings_client_ip_ipv4(self): + self.client_ip({'header': 'X-Forwarded-For', 'source': '127.0.0.1'}) + + assert ( + self.get_xff('8.8.8.8, 84.23.23.11') == '84.23.23.11' + ), 'xff replace' + assert ( + self.get_xff('8.8.8.8, 84.23.23.11, 127.0.0.1') == '127.0.0.1' + ), 'xff replace 2' + assert ( + self.get_xff(['8.8.8.8', '127.0.0.1, 10.0.1.1']) == '10.0.1.1' + ), 'xff replace multi' + + def test_settings_client_ip_ipv6(self): + self.client_ip({'header': 'X-Forwarded-For', 'source': '::1'}) + + assert self.get_xff('1.1.1.1') == '127.0.0.1', 'bad source ipv4' + + for ip in [ + 'f607:7403:1e4b:6c66:33b2:843f:2517:da27', + '2001:db8:3c4d:15::1a2f:1a2b', + '2001::3c4d:15:1a2f:1a2b', + '::11.22.33.44', + ]: + assert self.get_xff(ip, 'ipv6') == ip, 'replace' + + def test_settings_client_ip_recursive(self): + self.client_ip( + { + 'header': 'X-Forwarded-For', + 'recursive': True, + 'source': ['127.0.0.1', '10.50.0.17', '10.5.2.1'], + } + ) + + assert self.get_xff('1.1.1.1') == '1.1.1.1', 'xff chain' + assert self.get_xff('1.1.1.1, 10.5.2.1') == '1.1.1.1', 'xff chain 2' + assert ( + self.get_xff('8.8.8.8, 1.1.1.1, 10.5.2.1') == '1.1.1.1' + ), 'xff chain 3' + assert ( + self.get_xff('10.50.0.17, 10.5.2.1, 10.5.2.1') == '10.50.0.17' + ), 'xff chain 4' + assert ( + self.get_xff(['8.8.8.8', '1.1.1.1, 127.0.0.1']) == '1.1.1.1' + ), 'xff replace multi' + assert ( + self.get_xff(['8.8.8.8', '1.1.1.1, 127.0.0.1', '10.5.2.1']) + == '1.1.1.1' + ), 'xff replace multi 2' + assert ( + self.get_xff(['10.5.2.1', '10.50.0.17, 1.1.1.1', '10.5.2.1']) + == '1.1.1.1' + ), 'xff replace multi 3' + assert ( + self.get_xff('8.8.8.8, 2001:db8:3c4d:15::1a2f:1a2b, 127.0.0.1') + == '2001:db8:3c4d:15::1a2f:1a2b' + ), 'xff chain ipv6' + + def test_settings_client_ip_invalid(self): + assert 'error' in self.conf( + {"http": {"client_ip": {'header': 'X-Forwarded-For', 'source': []}}}, + 'settings', + ), 'empty array source' + assert 'error' in self.conf( + {"http":{"client_ip": {'header': 'X-Forwarded-For', 'source': 'a'}}}, + 'settings', + ), 'empty source invalid' -- cgit From b586707c861448cfdd6d8783864c4a1ce7112a77 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 12 Aug 2021 14:55:51 +0300 Subject: Java: upgrading third-party components. --- auto/modules/java | 4 ++-- auto/modules/java_jar.sha512 | 20 ++++++++++---------- test/unit/applications/lang/java.py | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/auto/modules/java b/auto/modules/java index d2699c36..e8137217 100644 --- a/auto/modules/java +++ b/auto/modules/java @@ -238,7 +238,7 @@ cat << END > $NXT_JAVA_JARS static const char *nxt_java_system_jars[] = { END -NXT_TOMCAT_VERSION=9.0.50 +NXT_TOMCAT_VERSION=9.0.52 NXT_JAR_VERSION=$NXT_TOMCAT_VERSION @@ -297,7 +297,7 @@ NXT_JAR_NAME=jetty-http . auto/modules/java_get_jar NXT_JAR_NAME=classgraph -NXT_JAR_VERSION=4.8.111 +NXT_JAR_VERSION=4.8.112 NXT_JAR_NAMESPACE=io/github/classgraph/ . auto/modules/java_get_jar diff --git a/auto/modules/java_jar.sha512 b/auto/modules/java_jar.sha512 index 6f792ee7..5289081c 100644 --- a/auto/modules/java_jar.sha512 +++ b/auto/modules/java_jar.sha512 @@ -1,14 +1,14 @@ -9ffd4be045bd5f2f0400aa25270ee8663ed8f794994e81a0fe6f2c6e1ca3e77b9dcd1521c208e3ca928535919868c6607e1fc2303188e47347aff1ac58641353 classgraph-4.8.111.jar +e7ad5ee436f6befaddcdd1046022a2e30444a435d9419a33f8316f66e794cf710809dbcf7e408a087f434cd9cb724682965b5e4098e34803569241eb44288322 classgraph-4.8.112.jar ab441acf5551a7dc81c353eaccb3b3df9e89a48987294d19e39acdb83a5b640fcdff7414cee29f5b96eaa8826647f1d5323e185018fe33a64c402d69c73c9158 ecj-3.26.0.jar a3ce1a5a41c9791ece4cbbf049ec4add1ec41330743d6757daea520f8b329299f5dd274f9e5741ba41fe49510f203efd19540d2404390eca53421132f5f46d4b jetty-http-9.4.43.v20210629.jar 61a14e97baac9962bd68ece24f8b967eec8e32edfebfa27c6a13996a86650d82f8977bf1aa582fc9706a1b028cb3cec0057c97628235dfc130061939845229e6 jetty-server-9.4.43.v20210629.jar 304fcdba2bdbf37e8f2ea69a3f5fbdffdfefd98d80fa78883b1dca1129a4907cef63eb2fa7c83eef359022a3b6a2f3ff742d8d23075c83d049ac01f1402e97f8 jetty-util-9.4.43.v20210629.jar -7fb21023c0469296ccb257d9672d727aeb4b437d1b000344c5087bb120ca0e5fc3fcddd6524b851c7cffbf01c042edb5e779e03f82753cf61d43c920f260ccdf tomcat-api-9.0.50.jar -d505100fd7bca52db627c4f36aeb244ada08121ba35509ef4251a1779ef68261599ba7d5e08667441c6582cb471162092c150adc46e670550efbea16ad8dc3dd tomcat-el-api-9.0.50.jar -a0a9d99428a96f6e33d24938ab23ffff7c0b04e737183616e951e3bb978da9f057a19da6ebe0cbf2e7ad0c87735c19aae12ececd588405779dac725d37dee2ea tomcat-jasper-9.0.50.jar -ce8ee01533b2fea8c2611a96eccc8ce89ad99f8f646e5e9a9e6883909ad133952e7db1424900654747c3140b1581dfcfc25dafa0e9773f4de74082100b7ac2f1 tomcat-jasper-el-9.0.50.jar -fa05e5d950340dbf806f2dd74f1c8549d12e477dd7fd7e4755a64641220190352a19faf669580f35799b884ab08d96770a8514c2b67fce9c6a1742dcaba954ef tomcat-jsp-api-9.0.50.jar -fff70d05d13217fa98247dfa35aae43179aab4bb7b00073f0f5de331eb9f3d331aef218670e5e6e2280fb74d2f98733cd4bd72d499cdc29ff6dcaf6cbbbf470f tomcat-juli-9.0.50.jar -56a6f6c2887546390ca66463f2a748ee56a1537fe9a4a2edc0ea21abf332a473742523e4b8e8433fa9e132f359b106bff7bf96ed611532c6738a236095237c33 tomcat-servlet-api-9.0.50.jar -d02d4f166fa9490e2af22c314180f54b48fec120957bd483529bf65df0f0187736177e140067bc775d7c7b751eedbf953e259a20bc76d3b9a974e7b3b9be2024 tomcat-util-9.0.50.jar -1bd03304eaf7c8bb10092356c31c42366465f29f4828a935cb5ec9a890c05d7b41b8ddcdbad507aeb4cb09441f39e7cb10832737bd8e754e84cf98a6837c1cd5 tomcat-util-scan-9.0.50.jar +f14ac948559c0b6e20f6d84e5177fea46ea1321a7a401f280ee9323f3a07e70e141d2b12c8c466c446efb55a58743af931b0584f56494f17311cab511bcd214a tomcat-api-9.0.52.jar +a5ca293732267854a296ccc79b25051acf76fa8dea6118d43aa2e95b6d1951dfaffb941430b5080d7ab62d82d2c82c9385baf99e3c4a2bb6bf4a04372149169d tomcat-el-api-9.0.52.jar +8660e11dd0f994de43b19ba251a789dc3194a6b82d674085fed510200c789b402b27ab97bcecfec0720f563bb0dd18c2631cd8bb5c35e604c1460d7357492123 tomcat-jasper-9.0.52.jar +5b0b3e0edb47e3c4269736542d66d6fc099a74965fdcd5d3a6382db3f75bec7329e81f0719aaafccd318a058ec8fbba113a6ae9234ca94a00c8c39e5c8885568 tomcat-jasper-el-9.0.52.jar +a4368d1073d79a4f8a1cb8967a5e39af87723a17b2d94466554e8e4d3e8bb2dec3ee37db9f606e0c775dd4028604d4e87921f0dda764c8ef138aa50acf03d549 tomcat-jsp-api-9.0.52.jar +8687e108489996226a83e8310c73a2a176fac9ce365a7bd3fc23955fe968c3858a24c047cb5c7fbd1f3a890c893dcdf55e88180eefe61b98c1a3bf4e316fb07e tomcat-juli-9.0.52.jar +f9929f433e2b2f93897a87d117af2519e44020b44e3a475dfc81662b08d08e010b14a3dd6df2d4800196cdba7cbb8db2b879341c5a0ef1d11e5abe63d872bc34 tomcat-servlet-api-9.0.52.jar +c21ccf969378f2cad0ead32451c2527ea944207b5a894b642ee554042fe87eb0ce647aacbf8a51d12b4ecf2bf13e9380da78d8f7792486909daba72e8d0f83f2 tomcat-util-9.0.52.jar +14b4eb31c124d22c7ea7f05808cd6a46076f9d72648afd76e2d11924874117266771a455686d704225d2eff94656f024293140a3259b108857fa6b8b218ddd63 tomcat-util-scan-9.0.52.jar diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index 3a620aa0..53b27b07 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -52,7 +52,7 @@ class TestApplicationJava(TestApplicationProto): os.makedirs(classes_path) classpath = ( - option.current_dir + '/build/tomcat-servlet-api-9.0.50.jar' + option.current_dir + '/build/tomcat-servlet-api-9.0.52.jar' ) ws_jars = glob.glob( -- cgit From 598f1493f6cbcd2a680b4f01ca7490e06092f5e2 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Thu, 12 Aug 2021 17:41:21 +0800 Subject: Log: renamed related variables "log" as "_log" to prevent conflicts. --- src/nxt_log.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/nxt_log.h b/src/nxt_log.h index 48742721..0cf10b5c 100644 --- a/src/nxt_log.h +++ b/src/nxt_log.h @@ -48,29 +48,29 @@ nxt_log_level_enough(log, level) \ #define nxt_alert(task, ...) \ do { \ - nxt_log_t *log = (task)->log; \ + nxt_log_t *_log = (task)->log; \ \ - log->handler(NXT_LOG_ALERT, log, __VA_ARGS__); \ + _log->handler(NXT_LOG_ALERT, _log, __VA_ARGS__); \ } while (0) #define nxt_log(task, _level, ...) \ do { \ - nxt_log_t *log = (task)->log; \ + nxt_log_t *_log = (task)->log; \ nxt_uint_t _level_ = (_level); \ \ - if (nxt_slow_path(log->level >= _level_)) { \ - log->handler(_level_, log, __VA_ARGS__); \ + if (nxt_slow_path(_log->level >= _level_)) { \ + _log->handler(_level_, _log, __VA_ARGS__); \ } \ } while (0) #define nxt_trace(task, ...) \ do { \ - nxt_log_t *log = (task)->log; \ + nxt_log_t *_log = (task)->log; \ \ - if (nxt_slow_path(log->level >= NXT_LOG_NOTICE || nxt_trace)) { \ - log->handler(NXT_LOG_NOTICE, log, __VA_ARGS__); \ + if (nxt_slow_path(_log->level >= NXT_LOG_NOTICE || nxt_trace)) { \ + _log->handler(NXT_LOG_NOTICE, _log, __VA_ARGS__); \ } \ } while (0) @@ -99,10 +99,10 @@ nxt_log_error(_level, _log, ...) \ #define nxt_debug(task, ...) \ do { \ - nxt_log_t *log = (task)->log; \ + nxt_log_t *_log = (task)->log; \ \ - if (nxt_slow_path(log->level == NXT_LOG_DEBUG || nxt_debug)) { \ - log->handler(NXT_LOG_DEBUG, log, __VA_ARGS__); \ + if (nxt_slow_path(_log->level == NXT_LOG_DEBUG || nxt_debug)) { \ + _log->handler(NXT_LOG_DEBUG, _log, __VA_ARGS__); \ } \ } while (0) -- cgit From 48a9399f23b9aaa2c9e5deb8013c58313e76740e Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Thu, 12 Aug 2021 17:39:00 +0800 Subject: Introduced the generic API nxt_buf_dummy_completion(). No functional changes. --- src/nxt_buf.h | 6 ++++++ src/nxt_router.c | 14 +++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/nxt_buf.h b/src/nxt_buf.h index 25e8499a..5121d659 100644 --- a/src/nxt_buf.h +++ b/src/nxt_buf.h @@ -288,4 +288,10 @@ nxt_buf_cpystr(nxt_buf_t *b, const nxt_str_t *str) } +nxt_inline void +nxt_buf_dummy_completion(nxt_task_t *task, void *obj, void *data) +{ +} + + #endif /* _NXT_BUF_H_INCLIDED_ */ diff --git a/src/nxt_router.c b/src/nxt_router.c index 1aa919c2..e0029d13 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -237,8 +237,6 @@ static void nxt_router_http_request_error(nxt_task_t *task, void *obj, static void nxt_router_http_request_done(nxt_task_t *task, void *obj, void *data); -static void nxt_router_dummy_buf_completion(nxt_task_t *task, void *obj, - void *data); static void nxt_router_app_prepare_request(nxt_task_t *task, nxt_request_rpc_data_t *req_rpc_data); static nxt_buf_t *nxt_router_prepare_msg(nxt_task_t *task, @@ -2532,7 +2530,7 @@ nxt_router_listen_socket_rpc_create(nxt_task_t *task, goto fail; } - b->completion_handler = nxt_router_dummy_buf_completion; + b->completion_handler = nxt_buf_dummy_completion; b->mem.free = nxt_cpymem(b->mem.free, skcf->listen->sockaddr, size); @@ -2763,7 +2761,7 @@ nxt_router_app_rpc_create(nxt_task_t *task, goto fail; } - b->completion_handler = nxt_router_dummy_buf_completion; + b->completion_handler = nxt_buf_dummy_completion; nxt_buf_cpystr(b, &app->name); *b->mem.free++ = '\0'; @@ -3792,7 +3790,7 @@ nxt_router_access_log_open(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) goto fail; } - b->completion_handler = nxt_router_dummy_buf_completion; + b->completion_handler = nxt_buf_dummy_completion; nxt_buf_cpystr(b, &access_log->path); *b->mem.free++ = '\0'; @@ -5242,12 +5240,6 @@ nxt_router_http_request_done(nxt_task_t *task, void *obj, void *data) } -static void -nxt_router_dummy_buf_completion(nxt_task_t *task, void *obj, void *data) -{ -} - - static void nxt_router_app_prepare_request(nxt_task_t *task, nxt_request_rpc_data_t *req_rpc_data) -- cgit From 3bd60e317c142f4596bdc0ef4747ea0f2cc03503 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Tue, 17 Aug 2021 16:45:51 +0300 Subject: Packages: added Debian 11 "bullseye" support. --- pkg/deb/Makefile | 12 ++++++++++++ pkg/deb/Makefile.jsc-common | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index c343eb53..85d5545e 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -135,6 +135,18 @@ include Makefile.python include Makefile.perl endif +# Debian 11 +ifeq ($(CODENAME),bullseye) +include Makefile.php +include Makefile.python27 +include Makefile.python39 +include Makefile.go +include Makefile.perl +include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc11 +endif + # Debian 10 ifeq ($(CODENAME),buster) include Makefile.php diff --git a/pkg/deb/Makefile.jsc-common b/pkg/deb/Makefile.jsc-common index 1c4a77b5..5f727124 100644 --- a/pkg/deb/Makefile.jsc-common +++ b/pkg/deb/Makefile.jsc-common @@ -6,7 +6,7 @@ MODULE_SUMMARY_jsc_common= Java shared packages for NGINX Unit MODULE_VERSION_jsc_common= $(VERSION) MODULE_RELEASE_jsc_common= 1 -ifneq (,$(findstring $(CODENAME),hirsute groovy focal eoan disco buster)) +ifneq (,$(findstring $(CODENAME),hirsute groovy focal eoan disco buster bullseye)) JAVA_MINVERSION= 11 else JAVA_MINVERSION= 8 -- cgit From e0aa132172f03fe7c31484ce7d301813b5dacb89 Mon Sep 17 00:00:00 2001 From: Andrey Suvorov Date: Tue, 17 Aug 2021 16:52:32 -0700 Subject: Added TLS session tickets support. --- auto/ssltls | 17 +++ docs/changes.xml | 6 + src/nxt_conf_validation.c | 73 +++++++++++ src/nxt_openssl.c | 321 ++++++++++++++++++++++++++++++++++++++++++++++ src/nxt_router.c | 4 + src/nxt_tls.h | 21 +++ 6 files changed, 442 insertions(+) diff --git a/auto/ssltls b/auto/ssltls index f9363dde..d678ba74 100644 --- a/auto/ssltls +++ b/auto/ssltls @@ -66,6 +66,23 @@ if [ $NXT_OPENSSL = YES ]; then return 0; }" . auto/feature + + + nxt_feature="OpenSSL tlsext support" + nxt_feature_name=NXT_HAVE_OPENSSL_TLSEXT + nxt_feature_run= + nxt_feature_incs= + nxt_feature_libs="$NXT_OPENSSL_LIBS" + nxt_feature_test="#include + + int main() { + #if (OPENSSL_NO_TLSEXT) + #error OpenSSL: no tlsext support. + #else + return 0; + #endif + }" + . auto/feature fi diff --git a/docs/changes.xml b/docs/changes.xml index 56dfa038..bb894e3d 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,12 @@ NGINX Unit updated to 1.25.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +TLS session tickets. + + + TLS sessions cache. diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 0106ebc8..a53fff74 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -99,6 +99,12 @@ static nxt_int_t nxt_conf_vldt_tls_cache_size(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +#if (NXT_HAVE_OPENSSL_TLSEXT) +static nxt_int_t nxt_conf_vldt_ticket_key(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_ticket_key_element(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); +#endif #endif static nxt_int_t nxt_conf_vldt_action(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); @@ -428,6 +434,17 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_session_members[] = { .name = nxt_string("timeout"), .type = NXT_CONF_VLDT_INTEGER, .validator = nxt_conf_vldt_tls_timeout, + }, { + .name = nxt_string("tickets"), + .type = NXT_CONF_VLDT_STRING + | NXT_CONF_VLDT_ARRAY + | NXT_CONF_VLDT_BOOLEAN, +#if (NXT_HAVE_OPENSSL_TLSEXT) + .validator = nxt_conf_vldt_ticket_key, +#else + .validator = nxt_conf_vldt_unsupported, + .u.string = "tickets", +#endif }, NXT_CONF_VLDT_END @@ -469,6 +486,62 @@ nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, #endif +#if (NXT_HAVE_OPENSSL_TLSEXT) + +static nxt_int_t +nxt_conf_vldt_ticket_key(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + if (nxt_conf_type(value) == NXT_CONF_BOOLEAN) { + return NXT_OK; + } + + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_ticket_key_element); + } + + /* NXT_CONF_STRING */ + + return nxt_conf_vldt_ticket_key_element(vldt, value); +} + + +static nxt_int_t +nxt_conf_vldt_ticket_key_element(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value) +{ + nxt_str_t key; + nxt_int_t ret; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"key\" array must " + "contain only string values."); + } + + nxt_conf_get_string(value, &key); + + ret = nxt_openssl_base64_decode(NULL, 0, key.start, key.length); + if (nxt_slow_path(ret == NXT_ERROR)) { + return NXT_ERROR; + } + + if (ret == NXT_DECLINED) { + return nxt_conf_vldt_error(vldt, "Invalid Base64 format for the ticket " + "key \"%V\".", &key); + } + + if (ret != 48 && ret != 80) { + return nxt_conf_vldt_error(vldt, "Invalid length %d of the ticket " + "key \"%V\". Must be 48 or 80 bytes.", + ret, &key); + } + + return NXT_OK; +} + +#endif + static nxt_conf_vldt_object_t nxt_conf_vldt_route_members[] = { { diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index 297e11cf..273ca7f4 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include typedef struct { @@ -50,6 +52,12 @@ static nxt_int_t nxt_openssl_chain_file(nxt_task_t *task, SSL_CTX *ctx, static nxt_int_t nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx, nxt_conf_value_t *value, nxt_mp_t *mp); #endif +#if (NXT_HAVE_OPENSSL_TLSEXT) +static nxt_int_t nxt_tls_ticket_keys(nxt_task_t *task, SSL_CTX *ctx, + nxt_tls_init_t *tls_init, nxt_mp_t *mp); +static int nxt_tls_ticket_key_callback(SSL *s, unsigned char *name, + unsigned char *iv, EVP_CIPHER_CTX *ectx,HMAC_CTX *hctx, int enc); +#endif static void nxt_ssl_session_cache(SSL_CTX *ctx, size_t cache_size, time_t timeout); static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, @@ -350,6 +358,12 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_mp_t *mp, nxt_ssl_session_cache(ctx, tls_init->cache_size, tls_init->timeout); +#if (NXT_HAVE_OPENSSL_TLSEXT) + if (nxt_tls_ticket_keys(task, ctx, tls_init, mp) != NXT_OK) { + goto fail; + } +#endif + SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); if (conf->ca_certificate != NULL) { @@ -587,6 +601,241 @@ fail: #endif +#if (NXT_HAVE_OPENSSL_TLSEXT) + +static nxt_int_t +nxt_tls_ticket_keys(nxt_task_t *task, SSL_CTX *ctx, nxt_tls_init_t *tls_init, + nxt_mp_t *mp) +{ + uint32_t i; + nxt_int_t ret; + nxt_str_t value; + nxt_uint_t count; + nxt_conf_value_t *member, *tickets_conf; + nxt_tls_ticket_t *ticket; + nxt_tls_tickets_t *tickets; + u_char buf[80]; + + tickets_conf = tls_init->tickets_conf; + + if (tickets_conf == NULL) { + goto no_ticket; + } + + if (nxt_conf_type(tickets_conf) == NXT_CONF_BOOLEAN) { + if (nxt_conf_get_boolean(tickets_conf) == 0) { + goto no_ticket; + } + + return NXT_OK; + } + + if (nxt_conf_type(tickets_conf) == NXT_CONF_ARRAY) { + count = nxt_conf_array_elements_count(tickets_conf); + + if (count == 0) { + goto no_ticket; + } + + } else { + /* nxt_conf_type(tickets_conf) == NXT_CONF_STRING */ + count = 1; + } + +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + + tickets = nxt_mp_get(mp, sizeof(nxt_tls_tickets_t) + + count * sizeof(nxt_tls_ticket_t)); + if (nxt_slow_path(tickets == NULL)) { + return NXT_ERROR; + } + + tickets->count = count; + tls_init->conf->tickets = tickets; + i = 0; + + do { + ticket = &tickets->tickets[i]; + + i++; + + if (nxt_conf_type(tickets_conf) == NXT_CONF_ARRAY) { + member = nxt_conf_get_array_element(tickets_conf, count - i); + if (member == NULL) { + break; + } + + } else { + /* nxt_conf_type(tickets_conf) == NXT_CONF_STRING */ + member = tickets_conf; + } + + nxt_conf_get_string(member, &value); + + ret = nxt_openssl_base64_decode(buf, 80, value.start, value.length); + if (nxt_slow_path(ret == NXT_ERROR)) { + return NXT_ERROR; + } + + if (ret == 48) { + ticket->aes128 = 1; + nxt_memcpy(ticket->aes_key, buf + 16, 16); + nxt_memcpy(ticket->hmac_key, buf + 32, 16); + + } else { + ticket->aes128 = 0; + nxt_memcpy(ticket->hmac_key, buf + 16, 32); + nxt_memcpy(ticket->aes_key, buf + 48, 32); + } + + nxt_memcpy(ticket->name, buf, 16); + } while (i < count); + + if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, nxt_tls_ticket_key_callback) + == 0) + { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "Unit was built with Session Tickets support, however, " + "now it is linked dynamically to an OpenSSL library " + "which has no tlsext support, therefore Session Tickets " + "are not available"); + + return NXT_ERROR; + } + + return NXT_OK; + +#else + nxt_alert(task, "Setting custom session ticket keys is not supported with " + "this version of OpenSSL library"); + + return NXT_ERROR; + +#endif + +no_ticket: + + SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); + + return NXT_OK; +} + + +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + +static int +nxt_tls_ticket_key_callback(SSL *s, unsigned char *name, unsigned char *iv, + EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc) +{ + size_t size; + nxt_uint_t i; + nxt_conn_t *c; + const EVP_MD *digest; + const EVP_CIPHER *cipher; + nxt_tls_ticket_t *ticket; + nxt_openssl_conn_t *tls; + + c = SSL_get_ex_data(s, nxt_openssl_connection_index); + + if (nxt_slow_path(c == NULL)) { + nxt_thread_log_alert("SSL_get_ex_data() failed"); + return -1; + } + + tls = c->u.tls; + ticket = tls->conf->tickets->tickets; + +#ifdef OPENSSL_NO_SHA256 + digest = EVP_sha1(); +#else + digest = EVP_sha256(); +#endif + + if (enc == 1) { + /* encrypt session ticket */ + + nxt_debug(c->socket.task, "TLS session ticket encrypt"); + + if (ticket[0].aes128 == 1) { + cipher = EVP_aes_128_cbc(); + size = 16; + + } else { + cipher = EVP_aes_256_cbc(); + size = 32; + } + + if (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) != 1) { + nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT, + "RAND_bytes() failed"); + return -1; + } + + if (EVP_EncryptInit_ex(ectx, cipher, NULL, ticket[0].aes_key, iv) + != 1) + { + nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT, + "EVP_EncryptInit_ex() failed"); + return -1; + } + + if (HMAC_Init_ex(hctx, ticket[0].hmac_key, size, digest, NULL) != 1) { + nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT, + "HMAC_Init_ex() failed"); + return -1; + } + + nxt_memcpy(name, ticket[0].name, 16); + + return 1; + + } else { + /* decrypt session ticket */ + + for (i = 0; i < tls->conf->tickets->count; i++) { + if (nxt_memcmp(name, ticket[i].name, 16) == 0) { + goto found; + } + } + + nxt_debug(c->socket.task, "TLS session ticket decrypt, key not found"); + + return 0; + + found: + + nxt_debug(c->socket.task, + "TLS session ticket decrypt, key number: \"%d\"", i); + + if (ticket[i].aes128 == 1) { + cipher = EVP_aes_128_cbc(); + size = 16; + + } else { + cipher = EVP_aes_256_cbc(); + size = 32; + } + + if (EVP_DecryptInit_ex(ectx, cipher, NULL, ticket[i].aes_key, iv) != 1) { + nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT, + "EVP_DecryptInit_ex() failed"); + return -1; + } + + if (HMAC_Init_ex(hctx, ticket[i].hmac_key, size, digest, NULL) != 1) { + nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT, + "HMAC_Init_ex() failed"); + return -1; + } + + return (i == 0) ? 1 : 2 /* renew */; + } +} + +#endif /* SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB */ + +#endif /* NXT_HAVE_OPENSSL_TLSEXT */ + static void nxt_ssl_session_cache(SSL_CTX *ctx, size_t cache_size, time_t timeout) @@ -904,6 +1153,11 @@ nxt_openssl_server_free(nxt_task_t *task, nxt_tls_conf_t *conf) bundle = bundle->next; } while (bundle != NULL); + if (conf->tickets) { + nxt_memzero(conf->tickets->tickets, + conf->tickets->count * sizeof(nxt_tls_ticket_t)); + } + #if (OPENSSL_VERSION_NUMBER >= 0x1010100fL \ && OPENSSL_VERSION_NUMBER < 0x1010101fL) RAND_keep_random_devices_open(0); @@ -1565,3 +1819,70 @@ nxt_openssl_copy_error(u_char *p, u_char *end) return p; } + + +nxt_int_t +nxt_openssl_base64_decode(u_char *d, size_t dlen, const u_char *s, size_t slen) +{ + BIO *bio, *b64; + nxt_int_t count, ret; + u_char buf[128]; + + b64 = BIO_new(BIO_f_base64()); + if (nxt_slow_path(b64 == NULL)) { + goto error; + } + + bio = BIO_new_mem_buf(s, slen); + if (nxt_slow_path(bio == NULL)) { + goto error; + } + + bio = BIO_push(b64, bio); + + BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); + + count = 0; + + if (d == NULL) { + + for ( ;; ) { + ret = BIO_read(bio, buf, 128); + + if (ret < 0) { + goto invalid; + } + + count += ret; + + if (ret != 128) { + break; + } + } + + } else { + count = BIO_read(bio, d, dlen); + + if (count < 0) { + goto invalid; + } + } + + BIO_free_all(bio); + + return count; + +error: + + BIO_vfree(b64); + ERR_clear_error(); + + return NXT_ERROR; + +invalid: + + BIO_free_all(bio); + ERR_clear_error(); + + return NXT_DECLINED; +} diff --git a/src/nxt_router.c b/src/nxt_router.c index e0029d13..39d375f8 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -1470,6 +1470,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, static nxt_str_t conf_commands_path = nxt_string("/tls/conf_commands"); static nxt_str_t conf_cache_path = nxt_string("/tls/session/cache_size"); static nxt_str_t conf_timeout_path = nxt_string("/tls/session/timeout"); + static nxt_str_t conf_tickets = nxt_string("/tls/session/tickets"); #endif static nxt_str_t static_path = nxt_string("/settings/http/static"); static nxt_str_t websocket_path = nxt_string("/settings/http/websocket"); @@ -1877,6 +1878,9 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, tls_init->conf_cmds = nxt_conf_get_path(listener, &conf_commands_path); + tls_init->tickets_conf = nxt_conf_get_path(listener, + &conf_tickets); + if (nxt_conf_type(certificate) == NXT_CONF_ARRAY) { n = nxt_conf_array_elements_count(certificate); diff --git a/src/nxt_tls.h b/src/nxt_tls.h index a92f1c21..eeb4e7ba 100644 --- a/src/nxt_tls.h +++ b/src/nxt_tls.h @@ -29,6 +29,8 @@ typedef struct nxt_tls_conf_s nxt_tls_conf_t; typedef struct nxt_tls_bundle_conf_s nxt_tls_bundle_conf_t; typedef struct nxt_tls_init_s nxt_tls_init_t; +typedef struct nxt_tls_ticket_s nxt_tls_ticket_t; +typedef struct nxt_tls_tickets_s nxt_tls_tickets_t; typedef struct { nxt_int_t (*library_init)(nxt_task_t *task); @@ -63,6 +65,8 @@ struct nxt_tls_conf_s { nxt_tls_bundle_conf_t *bundle; nxt_lvlhsh_t bundle_hash; + nxt_tls_tickets_t *tickets; + void (*conn_init)(nxt_task_t *task, nxt_tls_conf_t *conf, nxt_conn_t *c); @@ -82,17 +86,34 @@ struct nxt_tls_init_s { size_t cache_size; nxt_time_t timeout; nxt_conf_value_t *conf_cmds; + nxt_conf_value_t *tickets_conf; nxt_tls_conf_t *conf; }; +struct nxt_tls_ticket_s { + uint8_t aes128; + u_char name[16]; + u_char hmac_key[32]; + u_char aes_key[32]; +}; + + +struct nxt_tls_tickets_s { + nxt_uint_t count; + nxt_tls_ticket_t tickets[]; +}; + + #if (NXT_HAVE_OPENSSL) extern const nxt_tls_lib_t nxt_openssl_lib; void nxt_cdecl nxt_openssl_log_error(nxt_task_t *task, nxt_uint_t level, const char *fmt, ...); u_char *nxt_openssl_copy_error(u_char *p, u_char *end); +nxt_int_t nxt_openssl_base64_decode(u_char *d, size_t dlen, const u_char *s, + size_t slen); #endif #if (NXT_HAVE_GNUTLS) -- cgit From 8b3a8eaf986df9fe2a01bd5d62703687297db2d4 Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Thu, 19 Aug 2021 16:15:07 +0300 Subject: Edited changes.xml for the 1.25.0 release. --- docs/changes.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index bb894e3d..110d8bef 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -57,14 +57,14 @@ application restart control. -client IP address replacement from specified HTTP header field. +client IP address replacement from a specified HTTP header field. -TLS connection was rejected for configuration with more than one -certificate bundle in a listener if a client did not use SNI. +TLS connections were rejected for configurations with multiple +certificate bundles in a listener if the client did not use SNI. @@ -72,13 +72,13 @@ certificate bundle in a listener if a client did not use SNI. the router process could crash on TLS connection open when multiple listeners -with TLS certificate configured; the bug had appeared in 1.23.0. +with TLS certificates were configured; the bug had appeared in 1.23.0. -the router process could crash on rapid mutithreaded application +the router process could crash with frequent mutithreaded application reconfiguration. @@ -99,7 +99,7 @@ a full-form IPv6 in a listener address. -compatibility issues with some Python ASGI apps, notably based on Starlette +compatibility issues with some Python ASGI apps, notably based on the Starlette framework. -- cgit From 90680c2cafa052363f3bf00c7dcc883f4d13c467 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 19 Aug 2021 17:43:04 +0300 Subject: Reordered changes for 1.25.0 by significance (subjective). --- docs/changes.xml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 110d8bef..78171167 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -33,7 +33,7 @@ NGINX Unit updated to 1.25.0. -TLS session tickets. +client IP address replacement from a specified HTTP header field. @@ -45,7 +45,7 @@ TLS sessions cache. -process and thread lifecycle hooks in Ruby. +TLS session tickets. @@ -57,22 +57,21 @@ application restart control. -client IP address replacement from a specified HTTP header field. +process and thread lifecycle hooks in Ruby. -TLS connections were rejected for configurations with multiple -certificate bundles in a listener if the client did not use SNI. +the router process could crash on TLS connection open when multiple listeners +with TLS certificates were configured; the bug had appeared in 1.23.0. - -the router process could crash on TLS connection open when multiple listeners -with TLS certificates were configured; the bug had appeared in 1.23.0. +TLS connections were rejected for configurations with multiple certificate +bundles in a listener if the client did not use SNI. @@ -85,22 +84,22 @@ reconfiguration. -a descriptor and memory leak occurred in the router process when an app -process stopped or crashed. +compatibility issues with some Python ASGI apps, notably based on the Starlette +framework. -the controller or router process could crash if the configuration contained -a full-form IPv6 in a listener address. +a descriptor and memory leak occurred in the router process when an app process +stopped or crashed. -compatibility issues with some Python ASGI apps, notably based on the Starlette -framework. +the controller or router process could crash if the configuration contained +a full-form IPv6 in a listener address. @@ -113,8 +112,8 @@ or "upstreams" using a variable "pass" option. -the router process crashed while matching a request to an empty array of -source or destination address patterns. +the router process crashed while matching a request to an empty array of source +or destination address patterns. -- cgit From 9aefc734764260d79b3d51b2284b1776c9ecf7c2 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 19 Aug 2021 17:48:21 +0300 Subject: Added version 1.25.0 CHANGES. --- CHANGES | 40 ++++++++++++++++++++++++++++++++++++++++ docs/changes.xml | 4 ++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index cbc6678e..aab3a557 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,44 @@ +Changes with Unit 1.25.0 19 Aug 2021 + + *) Feature: client IP address replacement from a specified HTTP header + field. + + *) Feature: TLS sessions cache. + + *) Feature: TLS session tickets. + + *) Feature: application restart control. + + *) Feature: process and thread lifecycle hooks in Ruby. + + *) Bugfix: the router process could crash on TLS connection open when + multiple listeners with TLS certificates were configured; the bug had + appeared in 1.23.0. + + *) Bugfix: TLS connections were rejected for configurations with + multiple certificate bundles in a listener if the client did not use + SNI. + + *) Bugfix: the router process could crash with frequent mutithreaded + application reconfiguration. + + *) Bugfix: compatibility issues with some Python ASGI apps, notably + based on the Starlette framework. + + *) Bugfix: a descriptor and memory leak occurred in the router process + when an app process stopped or crashed. + + *) Bugfix: the controller or router process could crash if the + configuration contained a full-form IPv6 in a listener address. + + *) Bugfix: the router process crashed when a request was passed to an + empty "routes" or "upstreams" using a variable "pass" option. + + *) Bugfix: the router process crashed while matching a request to an + empty array of source or destination address patterns. + + Changes with Unit 1.24.0 27 May 2021 *) Change: PHP added to the default MIME type list. diff --git a/docs/changes.xml b/docs/changes.xml index 78171167..7e7f79ed 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -15,7 +15,7 @@ unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11 unit-jsc13 unit-jsc14 unit-jsc15 unit-jsc16 unit-jsc17" ver="1.25.0" rev="1" - date="" time="" + date="2021-08-19" time="18:00:00 +0300" packager="Andrei Belov <defan@nginx.com>"> @@ -28,7 +28,7 @@ NGINX Unit updated to 1.25.0. -- cgit From 66e986fc19dd12c731be5f52d0aa4cea32931b7d Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 19 Aug 2021 17:52:54 +0300 Subject: Generated Dockerfiles for Unit 1.25.0. --- pkg/docker/Dockerfile.go1.15 | 2 +- pkg/docker/Dockerfile.jsc11 | 2 +- pkg/docker/Dockerfile.minimal | 2 +- pkg/docker/Dockerfile.node15 | 2 +- pkg/docker/Dockerfile.perl5.32 | 2 +- pkg/docker/Dockerfile.php8.0 | 2 +- pkg/docker/Dockerfile.python3.9 | 2 +- pkg/docker/Dockerfile.ruby2.7 | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/docker/Dockerfile.go1.15 b/pkg/docker/Dockerfile.go1.15 index d446a934..0c88ff64 100644 --- a/pkg/docker/Dockerfile.go1.15 +++ b/pkg/docker/Dockerfile.go1.15 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.24.0 \ + && hg up 1.25.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.jsc11 b/pkg/docker/Dockerfile.jsc11 index b66ebe73..8f62ad3e 100644 --- a/pkg/docker/Dockerfile.jsc11 +++ b/pkg/docker/Dockerfile.jsc11 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.24.0 \ + && hg up 1.25.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal index 69a70e33..00875732 100644 --- a/pkg/docker/Dockerfile.minimal +++ b/pkg/docker/Dockerfile.minimal @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.24.0 \ + && hg up 1.25.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.node15 b/pkg/docker/Dockerfile.node15 index 1e3846a3..f98d0ef3 100644 --- a/pkg/docker/Dockerfile.node15 +++ b/pkg/docker/Dockerfile.node15 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.24.0 \ + && hg up 1.25.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.perl5.32 b/pkg/docker/Dockerfile.perl5.32 index 2fccbf63..244eb076 100644 --- a/pkg/docker/Dockerfile.perl5.32 +++ b/pkg/docker/Dockerfile.perl5.32 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.24.0 \ + && hg up 1.25.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.php8.0 b/pkg/docker/Dockerfile.php8.0 index 02db27cf..ba85ce0e 100644 --- a/pkg/docker/Dockerfile.php8.0 +++ b/pkg/docker/Dockerfile.php8.0 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.24.0 \ + && hg up 1.25.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.python3.9 b/pkg/docker/Dockerfile.python3.9 index 44472a12..a3ca8d4a 100644 --- a/pkg/docker/Dockerfile.python3.9 +++ b/pkg/docker/Dockerfile.python3.9 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.24.0 \ + && hg up 1.25.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.ruby2.7 b/pkg/docker/Dockerfile.ruby2.7 index 7875c470..d5140288 100644 --- a/pkg/docker/Dockerfile.ruby2.7 +++ b/pkg/docker/Dockerfile.ruby2.7 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.24.0 \ + && hg up 1.25.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ -- cgit From 13c0025dfa6e041563d0ad5dd81679b44522694c Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 19 Aug 2021 17:55:12 +0300 Subject: Unit 1.25.0 release. --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index b7cf1b6b..5e8ca333 100644 --- a/.hgtags +++ b/.hgtags @@ -31,3 +31,4 @@ f804aaf7eee10a7d8116820840d6312dd4914a41 1.21.0 331bdadeca30a49dd11b86af99124c2ffeb22d05 1.22.0 49ee24c03f5749f8a1b69dc1c600ad48517d9d7a 1.23.0 847c88d10f26765b45149c14f88c2274adfc3f42 1.24.0 +54ffe5ce4fb3c4304faf6d342d9b17dee2c745ac 1.25.0 -- cgit