view stream_js.t @ 1842:af47a0b348a5

Tests: LibreSSL certificate negotiation with TLSv1.3. LibreSSL fails to negotiate certificates based on signature algorithms when using TLSv1.3, and fails with "missing rsa certificate" and "unknown pkey type" errors.
author Maxim Dounin <mdounin@mdounin.ru>
date Thu, 23 Mar 2023 19:50:17 +0300
parents 520fb74cce4c
children
line wrap: on
line source

#!/usr/bin/perl

# (C) Andrey Zelenkov
# (C) Dmitry Volyntsev
# (C) Nginx, Inc.

# Tests for stream njs module.

###############################################################################

use warnings;
use strict;

use Test::More;

BEGIN { use FindBin; chdir($FindBin::Bin); }

use lib 'lib';
use Test::Nginx;
use Test::Nginx::Stream qw/ dgram stream /;

###############################################################################

select STDERR; $| = 1;
select STDOUT; $| = 1;

my $t = Test::Nginx->new()->has(qw/http proxy rewrite stream stream_return udp/)
	->write_file_expand('nginx.conf', <<'EOF');

%%TEST_GLOBALS%%

daemon off;

events {
}

http {
    %%TEST_GLOBALS_HTTP%%

    js_import test.js;

    server {
        listen       127.0.0.1:8079;
        server_name  localhost;

        location /njs {
            js_content test.njs;
        }

        location /p/ {
            proxy_pass http://127.0.0.1:8095/;

        }

        location /return {
            return 200 $http_foo;
        }
    }
}

stream {
    %%TEST_GLOBALS_STREAM%%

    js_set $js_addr      test.addr;
    js_set $js_var       test.variable;
    js_set $js_log       test.log;
    js_set $js_unk       test.unk;
    js_set $js_req_line  test.req_line;
    js_set $js_sess_unk  test.sess_unk;
    js_set $js_async     test.asyncf;

    js_import test.js;

    log_format status $server_port:$status;

    server {
        listen  127.0.0.1:8080;
        return  $js_addr;
    }

    server {
        listen  127.0.0.1:8081;
        return  $js_log;
    }

    server {
        listen  127.0.0.1:8082;
        return  $js_var;
    }

    server {
        listen  127.0.0.1:8083;
        return  $js_unk;
    }

    server {
        listen  127.0.0.1:8084;
        return  $js_sess_unk;
    }

    server {
        listen  127.0.0.1:%%PORT_8985_UDP%% udp;
        return  $js_addr;
    }

    server {
        listen      127.0.0.1:8086;
        js_access   test.access_step;
        js_preread  test.preread_step;
        js_filter   test.filter_step;
        proxy_pass  127.0.0.1:8090;
    }

    server {
        listen      127.0.0.1:8087;
        js_access   test.access_undecided;
        return      OK;
        access_log  %%TESTDIR%%/status.log status;
    }

    server {
        listen      127.0.0.1:8088;
        js_access   test.access_allow;
        return      OK;
        access_log  %%TESTDIR%%/status.log status;
    }

    server {
        listen      127.0.0.1:8089;
        js_access   test.access_deny;
        return      OK;
        access_log  %%TESTDIR%%/status.log status;
    }

    server {
        listen      127.0.0.1:8091;
        js_preread  test.preread_async;
        proxy_pass  127.0.0.1:8090;
    }

    server {
        listen      127.0.0.1:8092;
        js_preread  test.preread_data;
        proxy_pass  127.0.0.1:8090;
    }

    server {
        listen      127.0.0.1:8093;
        js_preread  test.preread_req_line;
        return      $js_req_line;
    }

    server {
        listen      127.0.0.1:8094;
        js_filter   test.filter_empty;
        proxy_pass  127.0.0.1:8090;
    }

    server {
        listen      127.0.0.1:8095;
        js_filter   test.filter_header_inject;
        proxy_pass  127.0.0.1:8079;
    }

    server {
        listen      127.0.0.1:8096;
        js_filter   test.filter_search;
        proxy_pass  127.0.0.1:8090;
    }

    server {
        listen      127.0.0.1:8097;
        js_access   test.access_except;
        proxy_pass  127.0.0.1:8090;
    }

    server {
        listen      127.0.0.1:8098;
        js_preread  test.preread_except;
        proxy_pass  127.0.0.1:8090;
    }

    server {
        listen      127.0.0.1:8099;
        js_filter   test.filter_except;
        proxy_pass  127.0.0.1:8090;
    }

    server {
        listen      127.0.0.1:8100;
        return      $js_async;
    }
}

EOF

$t->write_file('test.js', <<EOF);
    function test_njs(r) {
        r.return(200, njs.version);
    }

    function addr(s) {
        return 'addr=' + s.remoteAddress;
    }

    function variable(s) {
        return 'variable=' + s.variables.remote_addr;
    }

    function sess_unk(s) {
        return 'sess_unk=' + s.unk;
    }

    function log(s) {
        s.log("SEE-THIS");
    }

    var res = '';

    function access_step(s) {
        res += '1';

        setTimeout(function() {
            if (s.remoteAddress.match('127.0.0.1')) {
                s.allow();
            }
        }, 1);
    }

    function preread_step(s) {
        s.on('upload', function (data) {
            res += '2';
            if (res.length >= 3) {
                s.done();
            }
        });
    }

    function filter_step(s) {
        s.on('upload', function(data, flags) {
            s.send(data);
            res += '3';
        });

        s.on('download', function(data, flags) {

            if (!flags.last) {
                res += '4';
                s.send(data);

            } else {
                res += '5';
                s.send(res, {last:1});
                s.off('download');
            }
        });
    }

    function access_undecided(s) {
        s.decline();
    }

    function access_allow(s) {
        if (s.remoteAddress.match('127.0.0.1')) {
            s.done();
            return;
        }

        s.deny();
    }

    function access_deny(s) {
        if (s.remoteAddress.match('127.0.0.1')) {
            s.deny();
            return;
        }

        s.allow();
    }


    function preread_async(s) {
        setTimeout(function() {
            s.done();
        }, 1);
    }

    function preread_data(s) {
        s.on('upload', function (data, flags) {
            if (data.indexOf('z') != -1) {
                s.done();
            }
        });
    }

    var line = '';

    function preread_req_line(s) {
        s.on('upload', function (data, flags) {
            var n = data.indexOf('\\n');
            if (n != -1) {
                line = data.substr(0, n);
                s.done();
            }
        });
    }

    function req_line(s) {
        return line;
    }

    function filter_empty(s) {
    }

    function filter_header_inject(s) {
        var req = '';

        s.on('upload', function(data, flags) {
            req += data;

            var n = req.search('\\n');
            if (n != -1) {
                var rest = req.substr(n + 1);
                req = req.substr(0, n + 1);

                s.send(req + 'Foo: foo' + '\\r\\n' + rest, flags);

                s.off('upload');
            }
        });
    }

    function filter_search(s) {
        s.on('download', function(data, flags) {
            var n = data.search('y');
            if (n != -1) {
                s.send('z');
            }
        });

        s.on('upload', function(data, flags) {
            var n = data.search('x');
            if (n != -1) {
                s.send('y');
            }
        });
    }

    function access_except(s) {
        function done() {return s.a.a};

        setTimeout(done, 1);
        setTimeout(done, 2);
    }

    function preread_except(s) {
        var fs = require('fs');
        fs.readFileSync();
    }

    function filter_except(s) {
        s.on('unknown', function() {});
    }

    function pr(x) {
        return new Promise(resolve => {resolve(x)}).then(v => v).then(v => v);
    }

    async function asyncf(s) {
        const a1 = await pr(10);
        const a2 = await pr(20);

        s.setReturnValue(`retval: \${a1 + a2}`);
    }

    export default {njs:test_njs, addr, variable, sess_unk, log, access_step,
                    preread_step, filter_step, access_undecided, access_allow,
                    access_deny, preread_async, preread_data, preread_req_line,
                    req_line, filter_empty, filter_header_inject, filter_search,
                    access_except, preread_except, filter_except, asyncf};

EOF

$t->run_daemon(\&stream_daemon, port(8090));
$t->try_run('no stream njs available')->plan(23);
$t->waitforsocket('127.0.0.1:' . port(8090));

###############################################################################

is(stream('127.0.0.1:' . port(8080))->read(), 'addr=127.0.0.1',
	's.remoteAddress');
is(dgram('127.0.0.1:' . port(8985))->io('.'), 'addr=127.0.0.1',
	's.remoteAddress udp');
is(stream('127.0.0.1:' . port(8081))->read(), 'undefined', 's.log');
is(stream('127.0.0.1:' . port(8082))->read(), 'variable=127.0.0.1',
	's.variables');
is(stream('127.0.0.1:' . port(8083))->read(), '', 'stream js unknown function');
is(stream('127.0.0.1:' . port(8084))->read(), 'sess_unk=undefined', 's.unk');

is(stream('127.0.0.1:' . port(8086))->io('0'), '0122345',
	'async handlers order');
is(stream('127.0.0.1:' . port(8087))->io('#'), 'OK', 'access_undecided');
is(stream('127.0.0.1:' . port(8088))->io('#'), 'OK', 'access_allow');
is(stream('127.0.0.1:' . port(8089))->io('#'), '', 'access_deny');

is(stream('127.0.0.1:' . port(8091))->io('#'), '#', 'preread_async');
is(stream('127.0.0.1:' . port(8092))->io('#z'), '#z', 'preread_async_data');
is(stream('127.0.0.1:' . port(8093))->io("xy\na"), 'xy', 'preread_req_line');

is(stream('127.0.0.1:' . port(8094))->io('x'), 'x', 'filter_empty');
like(get('/p/return'), qr/foo/, 'filter_injected_header');
is(stream('127.0.0.1:' . port(8096))->io('x'), 'z', 'filter_search');

stream('127.0.0.1:' . port(8097))->io('x');
stream('127.0.0.1:' . port(8098))->io('x');
stream('127.0.0.1:' . port(8099))->io('x');

TODO: {
local $TODO = 'not yet' unless has_version('0.7.0');

is(stream('127.0.0.1:' . port(8100))->read(), 'retval: 30', 'asyncf');

}

$t->stop();

ok(index($t->read_file('error.log'), 'SEE-THIS') > 0, 'stream js log');
ok(index($t->read_file('error.log'), 'at fs.readFileSync') > 0,
	'stream js_preread backtrace');
ok(index($t->read_file('error.log'), 'at filter_except') > 0,
	'stream js_filter backtrace');

my @p = (port(8087), port(8088), port(8089));
like($t->read_file('status.log'), qr/$p[0]:200/, 'status undecided');
like($t->read_file('status.log'), qr/$p[1]:200/, 'status allow');
like($t->read_file('status.log'), qr/$p[2]:403/, 'status deny');

###############################################################################

sub has_version {
	my $need = shift;

	get('/njs') =~ /^([.0-9]+)$/m;

	my @v = split(/\./, $1);
	my ($n, $v);

	for $n (split(/\./, $need)) {
		$v = shift @v || 0;
		return 0 if $n > $v;
		return 1 if $v > $n;
	}

	return 1;
}

###############################################################################

sub stream_daemon {
	my $server = IO::Socket::INET->new(
		Proto => 'tcp',
		LocalAddr => '127.0.0.1:' . port(8090),
		Listen => 5,
		Reuse => 1
	)
		or die "Can't create listening socket: $!\n";

	local $SIG{PIPE} = 'IGNORE';

	while (my $client = $server->accept()) {
		$client->autoflush(1);

		log2c("(new connection $client)");

		$client->sysread(my $buffer, 65536) or next;

		log2i("$client $buffer");

		log2o("$client $buffer");

		$client->syswrite($buffer);

		close $client;
	}
}

sub log2i { Test::Nginx::log_core('|| <<', @_); }
sub log2o { Test::Nginx::log_core('|| >>', @_); }
sub log2c { Test::Nginx::log_core('||', @_); }

sub get {
	my ($url, %extra) = @_;

	my $s = IO::Socket::INET->new(
		Proto => 'tcp',
		PeerAddr => '127.0.0.1:' . port(8079)
	) or die "Can't connect to nginx: $!\n";

	return http_get($url, socket => $s);
}

###############################################################################