comparison ssl_proxy_upgrade.t @ 585:5bb19f7448b5

Tests: Upgrade handling tests with http ssl module.
author Sergey Kandaurov <pluknet@nginx.com>
date Wed, 20 May 2015 14:44:14 +0300
parents
children 53b7c3c33a79
comparison
equal deleted inserted replaced
584:7d6db4ac6ab0 585:5bb19f7448b5
1 #!/usr/bin/perl
2
3 # (C) Maxim Dounin
4 # (C) Sergey Kandaurov
5 # (C) Nginx, Inc.
6
7 # Tests for http proxy upgrade support with http ssl module.
8 # In contrast to proxy_websocket.t, this test doesn't try to use binary
9 # WebSocket protocol, but uses simple plain text protocol instead.
10
11 ###############################################################################
12
13 use warnings;
14 use strict;
15
16 use Test::More;
17
18 use IO::Poll;
19 use IO::Select;
20 use Socket qw/ CRLF /;
21
22 BEGIN { use FindBin; chdir($FindBin::Bin); }
23
24 use lib 'lib';
25 use Test::Nginx;
26
27 ###############################################################################
28
29 select STDERR; $| = 1;
30 select STDOUT; $| = 1;
31
32 eval { require IO::Socket::SSL; };
33 plan(skip_all => 'IO::Socket::SSL not installed') if $@;
34 eval { IO::Socket::SSL::SSL_VERIFY_NONE(); };
35 plan(skip_all => 'IO::Socket::SSL too old') if $@;
36
37 my $t = Test::Nginx->new()->has(qw/http proxy http_ssl/)->has_daemon('openssl')
38 ->write_file_expand('nginx.conf', <<'EOF')->plan(30);
39
40 %%TEST_GLOBALS%%
41
42 daemon off;
43
44 events {
45 }
46
47 http {
48 %%TEST_GLOBALS_HTTP%%
49
50 log_format test "$bytes_sent $body_bytes_sent";
51 access_log %%TESTDIR%%/cc.log test;
52
53 server {
54 listen 127.0.0.1:8080 ssl;
55 server_name localhost;
56
57 ssl_certificate_key localhost.key;
58 ssl_certificate localhost.crt;
59
60 location / {
61 proxy_pass http://127.0.0.1:8081;
62 proxy_http_version 1.1;
63 proxy_set_header Upgrade $http_upgrade;
64 proxy_set_header Connection "Upgrade";
65 proxy_read_timeout 2s;
66 send_timeout 2s;
67 }
68 }
69 }
70
71 EOF
72
73 $t->write_file('openssl.conf', <<EOF);
74 [ req ]
75 default_bits = 2048
76 encrypt_key = no
77 distinguished_name = req_distinguished_name
78 [ req_distinguished_name ]
79 EOF
80
81 my $d = $t->testdir();
82
83 foreach my $name ('localhost') {
84 system('openssl req -x509 -new '
85 . "-config '$d/openssl.conf' -subj '/CN=$name/' "
86 . "-out '$d/$name.crt' -keyout '$d/$name.key' "
87 . ">>$d/openssl.out 2>&1") == 0
88 or die "Can't create certificate for $name: $!\n";
89 }
90
91 $t->run_daemon(\&upgrade_fake_daemon);
92 $t->run();
93
94 $t->waitforsocket('127.0.0.1:8081')
95 or die "Can't start test backend";
96
97 ###############################################################################
98
99 # establish connection
100
101 my @r;
102 my $s = upgrade_connect();
103 ok($s, "handshake");
104
105 SKIP: {
106 skip "handshake failed", 22 unless $s;
107
108 # send a frame
109
110 upgrade_write($s, 'foo');
111 is(upgrade_read($s), 'bar', "upgrade response");
112
113 # send some big frame
114
115 upgrade_write($s, 'foo' x 16384);
116 like(upgrade_read($s), qr/^(bar){16384}$/, "upgrade big response");
117
118 # send multiple frames
119
120 for my $i (1 .. 10) {
121 upgrade_write($s, ('foo' x 16384) . $i);
122 upgrade_write($s, 'bazz' . $i);
123 }
124
125 for my $i (1 .. 10) {
126 like(upgrade_read($s), qr/^(bar){16384}\d+$/, "upgrade $i");
127 is(upgrade_read($s), 'bazz' . $i, "upgrade small $i");
128 }
129 }
130
131 push @r, $s ? ${*$s}->{_upgrade_private}->{r} : 'failed';
132 undef $s;
133
134 # establish connection with some pipelined data
135 # and make sure they are correctly passed upstream
136
137 $s = upgrade_connect(message => "foo");
138 ok($s, "handshake pipelined");
139
140 SKIP: {
141 skip "handshake failed", 2 unless $s;
142
143 is(upgrade_read($s), "bar", "response pipelined");
144
145 upgrade_write($s, "foo");
146 is(upgrade_read($s), "bar", "next to pipelined");
147 }
148
149 push @r, $s ? ${*$s}->{_upgrade_private}->{r} : 'failed';
150 undef $s;
151
152 # connection should not be upgraded unless upgrade was actually
153 # requested and allowed by configuration
154
155 $s = upgrade_connect(noheader => 1);
156 ok(!$s, "handshake noupgrade");
157
158 # bytes sent on upgraded connection, fixed in c2f309fb7ad2 (1.7.11)
159 # verify with 1) data actually read by client, 2) expected data from backend
160
161 $t->stop();
162
163 open my $f, '<', "$d/cc.log" or die "Can't open cc.log: $!";
164
165 is($f->getline(), shift (@r) . " 540793\n", 'log - bytes');
166 is($f->getline(), shift (@r) . " 22\n", 'log - bytes pipelined');
167 is($f->getline(), "0 0\n", 'log - bytes noupgrade');
168
169 ###############################################################################
170
171 sub upgrade_connect {
172 my (%opts) = @_;
173
174 my $s = IO::Socket::SSL->new(
175 Proto => 'tcp',
176 PeerAddr => '127.0.0.1:8080',
177 SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
178 )
179 or die "Can't connect to nginx: $!\n";
180
181 # send request, $h->to_string
182
183 my $buf = "GET / HTTP/1.1" . CRLF
184 . "Host: localhost" . CRLF
185 . ($opts{noheader} ? '' : "Upgrade: foo" . CRLF)
186 . "Connection: Upgrade" . CRLF . CRLF;
187
188 $buf .= $opts{message} . CRLF if defined $opts{message};
189
190 local $SIG{PIPE} = 'IGNORE';
191
192 log_out($buf);
193 $s->syswrite($buf);
194
195 # read response
196
197 my $got = '';
198 $buf = '';
199
200 while (1) {
201 $buf = upgrade_getline($s);
202 last unless defined $buf and length $buf;
203 log_in($buf);
204 $got .= $buf;
205 last if $got =~ /\x0d?\x0a\x0d?\x0a$/;
206 }
207
208 # parse server response
209
210 return if $got !~ m!HTTP/1.1 101!;
211
212 # make sure next line is "handshaked"
213
214 $buf = upgrade_read($s);
215
216 return if !defined $buf or $buf ne 'handshaked';
217 return $s;
218 }
219
220 sub upgrade_getline {
221 my ($s) = @_;
222 my ($h, $buf, $line);
223
224 ${*$s}->{_upgrade_private} ||= { b => '', r => 0 };
225 $h = ${*$s}->{_upgrade_private};
226
227 if ($h->{b} =~ /^(.*?\x0a)(.*)/ms) {
228 $h->{b} = $2;
229 return $1;
230 }
231
232 $s->blocking(0);
233 while (IO::Select->new($s)->can_read(1.5)) {
234 my $n = $s->sysread($buf, 1024);
235 last unless $n;
236
237 $h->{b} .= $buf;
238 $h->{r} += $n;
239
240 if ($h->{b} =~ /^(.*?\x0a)(.*)/ms) {
241 $h->{b} = $2;
242 return $1;
243 }
244 };
245 }
246
247 sub upgrade_write {
248 my ($s, $message) = @_;
249
250 $message = $message . CRLF;
251
252 local $SIG{PIPE} = 'IGNORE';
253
254 $s->blocking(0);
255 while (IO::Select->new($s)->can_write(1.5)) {
256 my $n = $s->syswrite($message);
257 last unless $n;
258 $message = substr($message, $n);
259 last unless length $message;
260 }
261
262 if (length $message) {
263 $s->close();
264 }
265 }
266
267 sub upgrade_read {
268 my ($s) = @_;
269 my $m = upgrade_getline($s);
270 $m =~ s/\x0d?\x0a// if defined $m;
271 log_in($m);
272 return $m;
273 }
274
275 ###############################################################################
276
277 sub upgrade_fake_daemon {
278 my $server = IO::Socket::INET->new(
279 Proto => 'tcp',
280 LocalAddr => '127.0.0.1:8081',
281 Listen => 5,
282 Reuse => 1
283 )
284 or die "Can't create listening socket: $!\n";
285
286 while (my $client = $server->accept()) {
287 upgrade_handle_client($client);
288 }
289 }
290
291 sub upgrade_handle_client {
292 my ($client) = @_;
293
294 $client->autoflush(1);
295 $client->blocking(0);
296
297 my $poll = IO::Poll->new;
298
299 my $handshake = 1;
300 my $unfinished = '';
301 my $buffer = '';
302 my $n;
303
304 log2c("(new connection $client)");
305
306 while (1) {
307 $poll->mask($client => ($buffer ? POLLIN|POLLOUT : POLLIN));
308 my $p = $poll->poll(0.5);
309 log2c("(poll $p)");
310
311 foreach my $reader ($poll->handles(POLLIN)) {
312 $n = $client->sysread(my $chunk, 65536);
313 return unless $n;
314
315 log2i($chunk);
316
317 if ($handshake) {
318 $buffer .= $chunk;
319 next unless $buffer =~ /\x0d?\x0a\x0d?\x0a$/;
320
321 log2c("(handshake done)");
322
323 $handshake = 0;
324 $buffer = 'HTTP/1.1 101 Switching' . CRLF
325 . 'Upgrade: foo' . CRLF
326 . 'Connection: Upgrade' . CRLF . CRLF
327 . 'handshaked' . CRLF;
328
329 log2o($buffer);
330
331 next;
332 }
333
334 $unfinished .= $chunk;
335
336 if ($unfinished =~ m/\x0d?\x0a\z/) {
337 $unfinished =~ s/foo/bar/g;
338 log2o($unfinished);
339 $buffer .= $unfinished;
340 $unfinished = '';
341 }
342 }
343
344 foreach my $writer ($poll->handles(POLLOUT)) {
345 next unless length $buffer;
346 $n = $writer->syswrite($buffer);
347 substr $buffer, 0, $n, '';
348 }
349 }
350 }
351
352 sub log2i { Test::Nginx::log_core('|| <<', @_); }
353 sub log2o { Test::Nginx::log_core('|| >>', @_); }
354 sub log2c { Test::Nginx::log_core('||', @_); }
355
356 ###############################################################################