Skip to content

Commit f9abe00

Browse files
committed
Add unit test for stunnel_log_scanner
Signed-off-by: Changlei Li <changlei.li@cloud.com>
1 parent 9e8f41a commit f9abe00

11 files changed

Lines changed: 379 additions & 0 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2026.01.09 06:01:27 LOG5[ui]: stunnel 5.60 on x86_64-koji-linux-gnu platform
2+
2026.01.09 06:01:27 LOG5[ui]: Compiled/running with OpenSSL 3.0.9 30 May 2023
3+
2026.01.09 06:01:27 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI Auth:LIBWRAP
4+
2026.01.09 06:01:27 LOG5[ui]: Reading configuration from descriptor 8
5+
2026.01.09 06:01:27 LOG5[ui]: UTF-8 byte order mark not detected
6+
2026.01.09 06:01:27 LOG5[ui]: FIPS mode disabled
7+
2026.01.09 06:01:27 LOG5[ui]: Configuration successful
8+
2026.01.09 06:01:28 LOG5[0]: Service [client-proxy] accepted connection from unnamed socket
9+
2026.01.09 06:01:28 LOG5[0]: s_connect: connected 10.79.16.92:4433
10+
2026.01.09 06:01:28 LOG5[0]: Service [client-proxy] connected remote server from 10.79.16.89:56850
11+
2026.01.09 06:01:28 LOG4[0]: CERT: Pre-verification error: certificate has expired
12+
2026.01.09 06:01:28 LOG4[0]: Rejected by CERT at depth=0: C=US, ST=California, L=San Francisco, O=MyCompany, CN=testserver
13+
2026.01.09 06:01:28 LOG3[0]: SSL_connect: ssl/statem/statem_clnt.c:1889: error:0A000086:SSL routines::certificate verify failed
14+
2026.01.09 06:01:28 LOG5[0]: Connection reset: 0 byte(s) sent to TLS, 0 byte(s) sent to socket
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2026.01.09 06:36:43 LOG5[ui]: stunnel 5.60 on x86_64-koji-linux-gnu platform
2+
2026.01.09 06:36:43 LOG5[ui]: Compiled/running with OpenSSL 3.0.9 30 May 2023
3+
2026.01.09 06:36:43 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI Auth:LIBWRAP
4+
2026.01.09 06:36:43 LOG5[ui]: Reading configuration from descriptor 8
5+
2026.01.09 06:36:43 LOG5[ui]: UTF-8 byte order mark not detected
6+
2026.01.09 06:36:43 LOG5[ui]: FIPS mode disabled
7+
2026.01.09 06:36:43 LOG5[ui]: Configuration successful
8+
2026.01.09 06:36:44 LOG5[0]: Service [client-proxy] accepted connection from unnamed socket
9+
2026.01.09 06:36:44 LOG5[0]: s_connect: connected 10.79.16.92:4433
10+
2026.01.09 06:36:44 LOG5[0]: Service [client-proxy] connected remote server from 10.79.16.89:56884
11+
2026.01.09 06:36:44 LOG4[0]: CERT: Pre-verification error: self-signed certificate
12+
2026.01.09 06:36:44 LOG4[0]: Rejected by CERT at depth=0: CN=testserver
13+
2026.01.09 06:36:44 LOG3[0]: SSL_connect: ssl/statem/statem_clnt.c:1889: error:0A000086:SSL routines::certificate verify failed
14+
2026.01.09 06:36:44 LOG5[0]: Connection reset: 0 byte(s) sent to TLS, 0 byte(s) sent to socket
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[ ] Initializing inetd mode configuration
2+
[ ] Clients allowed=500
3+
[.] stunnel 5.60 on x86_64-koji-linux-gnu platform
4+
[.] Compiled/running with OpenSSL 3.0.9 30 May 2023
5+
[.] Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI Auth:LIBWRAP
6+
[ ] errno: (*__errno_location ())
7+
[ ] Initializing inetd mode configuration
8+
[.] Reading configuration from descriptor 8
9+
[.] UTF-8 byte order mark not detected
10+
[.] FIPS mode disabled
11+
[ ] No PRNG seeding was required
12+
[ ] Initializing service [client-proxy]
13+
[ ] stunnel default security level set: 2
14+
[ ] Ciphers: ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
15+
[ ] TLSv1.3 ciphersuites: TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256
16+
[ ] TLS options: 0x02100000 (+0x00000000, -0x00000000)
17+
[ ] Session resumption enabled
18+
[ ] No certificate or private key specified
19+
[!] error queue: crypto/x509/by_file.c:234: error:05880009:x509 certificate routines::PEM lib
20+
[!] error queue: crypto/pem/pem_info.c:169: error:0488000D:PEM routines::ASN1 lib
21+
[!] error queue: crypto/asn1/tasn_dec.c:349: error:0688010A:asn1 encoding routines::nested asn1 error
22+
[!] SSL_CTX_load_verify_locations: crypto/asn1/tasn_dec.c:1188: error:068000A8:asn1 encoding routines::wrong tag
23+
[!] Service [client-proxy]: Failed to initialize TLS context
24+
[!] Configuration failed
25+
[ ] Deallocating temporary section defaults
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2026.01.09 05:45:23 LOG5[ui]: stunnel 5.60 on x86_64-koji-linux-gnu platform
2+
2026.01.09 05:45:23 LOG5[ui]: Compiled/running with OpenSSL 3.0.9 30 May 2023
3+
2026.01.09 05:45:23 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI Auth:LIBWRAP
4+
2026.01.09 05:45:23 LOG5[ui]: Reading configuration from descriptor 8
5+
2026.01.09 05:45:23 LOG5[ui]: UTF-8 byte order mark not detected
6+
2026.01.09 05:45:23 LOG5[ui]: FIPS mode disabled
7+
2026.01.09 05:45:23 LOG5[ui]: Configuration successful
8+
2026.01.09 05:45:24 LOG5[0]: Service [client-proxy] accepted connection from unnamed socket
9+
2026.01.09 05:45:24 LOG5[0]: s_connect: connecting 192.168.1.100:443
10+
2026.01.09 05:45:24 LOG3[0]: s_connect: connect 192.168.1.100:443: Connection refused
11+
2026.01.09 05:45:24 LOG5[0]: Connection reset: 0 byte(s) sent to TLS, 0 byte(s) sent to socket

ocaml/libs/stunnel/test/data/empty.log

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2026.01.09 05:52:10 LOG5[ui]: stunnel 5.60 on x86_64-koji-linux-gnu platform
2+
2026.01.09 05:52:10 LOG5[ui]: Compiled/running with OpenSSL 3.0.9 30 May 2023
3+
2026.01.09 05:52:10 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI Auth:LIBWRAP
4+
2026.01.09 05:52:10 LOG5[ui]: Reading configuration from descriptor 8
5+
2026.01.09 05:52:10 LOG5[ui]: UTF-8 byte order mark not detected
6+
2026.01.09 05:52:10 LOG5[ui]: FIPS mode disabled
7+
2026.01.09 05:52:10 LOG5[ui]: Configuration successful
8+
2026.01.09 05:52:11 LOG5[0]: Service [client-proxy] accepted connection from unnamed socket
9+
2026.01.09 05:52:11 LOG5[0]: s_connect: connecting invalid.example.com:443
10+
2026.01.09 05:52:11 LOG3[0]: s_connect: No host resolved
11+
2026.01.09 05:52:11 LOG5[0]: Connection reset: 0 byte(s) sent to TLS, 0 byte(s) sent to socket
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2026.01.09 05:47:45 LOG5[ui]: stunnel 5.60 on x86_64-koji-linux-gnu platform
2+
2026.01.09 05:47:45 LOG5[ui]: Compiled/running with OpenSSL 3.0.9 30 May 2023
3+
2026.01.09 05:47:45 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI Auth:LIBWRAP
4+
2026.01.09 05:47:45 LOG5[ui]: Reading configuration from descriptor 8
5+
2026.01.09 05:47:45 LOG5[ui]: UTF-8 byte order mark not detected
6+
2026.01.09 05:47:45 LOG5[ui]: FIPS mode disabled
7+
2026.01.09 05:47:45 LOG5[ui]: Configuration successful
8+
2026.01.09 05:47:46 LOG5[0]: Service [client-proxy] accepted connection from unnamed socket
9+
2026.01.09 05:47:46 LOG5[0]: s_connect: connected 10.79.16.92:4433
10+
2026.01.09 05:47:46 LOG5[0]: Service [client-proxy] connected remote server from 10.79.16.89:56830
11+
2026.01.09 05:47:46 LOG4[0]: CERT: Subject checks failed
12+
2026.01.09 05:47:46 LOG4[0]: Rejected by CERT at depth=0: C=US, ST=California, L=San Francisco, O=MyCompany, CN=testserver
13+
2026.01.09 05:47:46 LOG3[0]: SSL_connect: ssl/statem/statem_clnt.c:1889: error:0A000086:SSL routines::certificate verify failed
14+
2026.01.09 05:47:46 LOG5[0]: Connection reset: 0 byte(s) sent to TLS, 0 byte(s) sent to socket
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2026.01.09 06:17:43 LOG5[ui]: stunnel 5.60 on x86_64-koji-linux-gnu platform
2+
2026.01.09 06:17:43 LOG5[ui]: Compiled/running with OpenSSL 3.0.9 30 May 2023
3+
2026.01.09 06:17:43 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI Auth:LIBWRAP
4+
2026.01.09 06:17:43 LOG5[ui]: Reading configuration from descriptor 8
5+
2026.01.09 06:17:43 LOG5[ui]: UTF-8 byte order mark not detected
6+
2026.01.09 06:17:43 LOG5[ui]: FIPS mode disabled
7+
2026.01.09 06:17:43 LOG5[ui]: Configuration successful
8+
2026.01.09 06:17:44 LOG5[0]: Service [client-proxy] accepted connection from unnamed socket
9+
2026.01.09 06:17:44 LOG5[0]: s_connect: connected 10.71.212.108:8083
10+
2026.01.09 06:17:44 LOG5[0]: Service [client-proxy] connected remote server from 10.79.16.89:34844
11+
2026.01.09 06:17:45 LOG5[0]: Certificate accepted at depth=0: C=US, ST=Florida, L=Fort Lauderdale, O="Citrix Systems, Inc.", CN=*.xenrt.citrite.net
12+
2026.01.09 06:17:45 LOG5[0]: Connection closed: 6 byte(s) sent to TLS, 519 byte(s) sent to socket

ocaml/libs/stunnel/test/dune

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(test
2+
(name test_stunnel_log_scanner)
3+
(libraries
4+
alcotest
5+
astring
6+
stunnel
7+
unix
8+
xapi-stdext-pervasives
9+
xapi-stdext-std
10+
xapi-stdext-unix)
11+
(deps
12+
(source_tree data)))
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
(*
2+
* Copyright (c) Cloud Software Group, Inc.
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published
6+
* by the Free Software Foundation; version 2.1 only. with the special
7+
* exception on linking described in file LICENSE.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*)
14+
15+
open Stunnel_log_scanner
16+
17+
(** Path to test data directory - relative to where dune runs the test *)
18+
let data_dir = "data"
19+
20+
(** Helper to build path to test log file *)
21+
let log_path filename = Filename.concat data_dir filename
22+
23+
(** Collect logged lines for verification *)
24+
let make_logger () =
25+
let lines = ref [] in
26+
let log line =
27+
Printf.printf "%s\n" line ;
28+
lines := line :: !lines
29+
in
30+
(log, fun () -> List.rev !lines)
31+
32+
let calculate_next_line logfile substring =
33+
let lines = Xapi_stdext_unix.Unixext.read_lines ~path:logfile in
34+
match
35+
Xapi_stdext_std.Listext.List.find_index
36+
(fun line -> Astring.String.is_infix ~affix:substring line)
37+
lines
38+
with
39+
| Some index ->
40+
List.nth_opt lines (index + 1)
41+
| None ->
42+
None
43+
44+
(** Read the next line from an input channel *)
45+
let read_next_line ic = try Some (input_line ic) with End_of_file -> None
46+
47+
(** Test successful connection log *)
48+
let test_successful_connection () =
49+
let logfile = log_path "successful_connection.log" in
50+
let logger, _get_lines = make_logger () in
51+
Xapi_stdext_unix.Unixext.with_input_channel logfile @@ fun ic ->
52+
match check_stunnel_logfile ~ic logger with
53+
| Ok () ->
54+
Alcotest.(check bool)
55+
"Should reach the end of the log" true
56+
(read_next_line ic = None)
57+
| Error e ->
58+
Alcotest.fail
59+
("Should not error on successful connection log: "
60+
^ Stunnel_error.to_string e
61+
)
62+
63+
let test_certificate_verify logfile expected_substring () =
64+
let logfile = log_path logfile in
65+
let logger, _get_lines = make_logger () in
66+
Xapi_stdext_unix.Unixext.with_input_channel logfile @@ fun ic ->
67+
match check_stunnel_logfile ~ic logger with
68+
| Error (Stunnel_error.Certificate_verify msg) ->
69+
Alcotest.(check bool)
70+
"Error message should contain expected substring" true
71+
(Astring.String.is_infix ~affix:expected_substring msg) ;
72+
let next_line = calculate_next_line logfile "certificate verify failed" in
73+
let next_line_str = Option.value next_line ~default:"<no next line>" in
74+
Alcotest.(check bool)
75+
("next line should be matched: " ^ next_line_str)
76+
true
77+
(read_next_line ic = next_line)
78+
| Ok () ->
79+
Alcotest.fail "Should detect certificate verification failure"
80+
| Error e ->
81+
Alcotest.fail
82+
("Wrong error type: "
83+
^ Stunnel_error.to_string e
84+
^ ", expected certificate verify error"
85+
)
86+
87+
let test_stunnel_error logfile expected_substring () =
88+
let logfile = log_path logfile in
89+
let logger, _get_lines = make_logger () in
90+
Xapi_stdext_unix.Unixext.with_input_channel logfile @@ fun ic ->
91+
match check_stunnel_logfile ~ic logger with
92+
| Error (Stunnel_error.Stunnel msg) ->
93+
Alcotest.(check string)
94+
"Error message should be expected substring" expected_substring msg ;
95+
let next_line = calculate_next_line logfile expected_substring in
96+
let next_line_str = Option.value next_line ~default:"<no next line>" in
97+
Alcotest.(check bool)
98+
("next line should be matched: " ^ next_line_str)
99+
true
100+
(read_next_line ic = next_line)
101+
| Ok () ->
102+
Alcotest.fail "Should detect stunnel error"
103+
| Error e ->
104+
Alcotest.fail
105+
("Wrong error type: "
106+
^ Stunnel_error.to_string e
107+
^ ", expected stunnel error"
108+
)
109+
110+
let test_wait_for_configuration_success () =
111+
let logfile = log_path "successful_connection.log" in
112+
Xapi_stdext_unix.Unixext.with_input_channel logfile @@ fun ic ->
113+
match wait_for_configuration_success ~ic with
114+
| Ok ind ->
115+
Alcotest.(check string)
116+
"Indicator should be expected substring" "Configuration successful" ind
117+
| Error e ->
118+
Alcotest.fail
119+
("Should detect configuration success, but got error: "
120+
^ Stunnel_error.to_string e
121+
)
122+
123+
let test_wait_for_configuration_fail () =
124+
let logfile = log_path "configuration_failed.log" in
125+
Xapi_stdext_unix.Unixext.with_input_channel logfile @@ fun ic ->
126+
match wait_for_configuration_success ~ic with
127+
| Ok _ ->
128+
Alcotest.fail "Should detect configuration failure"
129+
| Error (Stunnel_error.Stunnel msg) ->
130+
Alcotest.(check string)
131+
"Should detect configuration failure" "Configuration failed" msg
132+
| Error e ->
133+
Alcotest.fail
134+
("Wrong error type: "
135+
^ Stunnel_error.to_string e
136+
^ ", expected stunnel error for configuration failure"
137+
)
138+
139+
let test_wait_timeout_for_configuration_success () =
140+
let logfile = log_path "empty.log" in
141+
Xapi_stdext_unix.Unixext.with_input_channel logfile @@ fun ic ->
142+
match
143+
check_stunnel_log_until_found_or_error ~ic
144+
~line_checker:configuration_success_checker 1.0 3
145+
with
146+
| Ok _ ->
147+
Alcotest.fail "Should detect timeout waiting for configuration success"
148+
| Error (Stunnel_error.Stunnel msg) ->
149+
Printf.printf "Received error message: %s\n" msg ;
150+
Alcotest.(check string)
151+
"Should detect timeout" "Timed out waiting for stunnel condition" msg
152+
| Error e ->
153+
Alcotest.fail
154+
("Wrong error type: "
155+
^ Stunnel_error.to_string e
156+
^ ", expected stunnel error for timeout"
157+
)
158+
159+
let file_append filename contents =
160+
let oc = open_out_gen [Open_wronly; Open_append; Open_creat] 0o644 filename in
161+
output_string oc contents ; close_out oc
162+
163+
let with_created_logfile logfile f =
164+
Xapi_stdext_unix.Unixext.touch_file logfile ;
165+
Xapi_stdext_pervasives.Pervasiveext.finally
166+
(fun () -> f logfile)
167+
(fun () -> Xapi_stdext_unix.Unixext.unlink_safe logfile)
168+
169+
let test_writing_log () =
170+
(* Create a test log file *)
171+
let logfile = log_path "test.log" in
172+
let logger, get_lines = make_logger () in
173+
let config_part =
174+
{|2026.01.09 05:45:23 LOG5[ui]: stunnel 5.60 on x86_64-koji-linux-gnu platform
175+
2026.01.09 05:45:23 LOG5[ui]: Compiled/running with OpenSSL 3.0.9 30 May 2023
176+
2026.01.09 05:45:23 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI Auth:LIBWRAP
177+
2026.01.09 05:45:23 LOG5[ui]: Reading configuration from descriptor 8
178+
2026.01.09 05:45:23 LOG5[ui]: UTF-8 byte order mark not detected
179+
2026.01.09 05:45:23 LOG5[ui]: FIPS mode disabled
180+
2026.01.09 05:45:23 LOG5[ui]: Configuration successful
181+
|}
182+
in
183+
let connection_part =
184+
{|2026.01.09 05:45:24 LOG5[0]: Service [client-proxy] accepted connection from unnamed socket
185+
2026.01.09 05:45:24 LOG5[0]: s_connect: connecting 192.168.1.100:443
186+
2026.01.09 05:45:24 LOG3[0]: s_connect: connect 192.168.1.100:443: Connection refused
187+
2026.01.09 05:45:24 LOG5[0]: Connection reset: 0 byte(s) sent to TLS, 0 byte(s) sent to socket
188+
|}
189+
in
190+
with_created_logfile logfile @@ fun logfile ->
191+
file_append logfile config_part ;
192+
Xapi_stdext_unix.Unixext.with_input_channel logfile @@ fun ic ->
193+
( match wait_for_configuration_success ~ic with
194+
| Ok ind ->
195+
Alcotest.(check string)
196+
"Indicator should be expected substring" "Configuration successful" ind
197+
| Error e ->
198+
Alcotest.fail
199+
("Should detect configuration success, but got error: "
200+
^ Stunnel_error.to_string e
201+
)
202+
) ;
203+
file_append logfile connection_part ;
204+
let next_line = calculate_next_line logfile "Configuration successful" in
205+
let next_line_str = Option.value next_line ~default:"<no next line>" in
206+
(* test stunnel error *)
207+
match check_stunnel_logfile ~ic logger with
208+
| Error (Stunnel_error.Stunnel msg) ->
209+
Alcotest.(check string)
210+
"Error message should be expected substring" "Connection refused" msg ;
211+
let first_line =
212+
match get_lines () with
213+
| first :: _ ->
214+
first
215+
| [] ->
216+
"<no logged lines>"
217+
in
218+
Alcotest.(check string)
219+
("first logged line should be matched: " ^ first_line)
220+
next_line_str first_line
221+
| Ok () ->
222+
let str = String.concat "\n" (get_lines ()) in
223+
Alcotest.fail ("Should detect stunnel error, but got: " ^ str)
224+
| Error e ->
225+
Alcotest.fail
226+
("Wrong error type: "
227+
^ Stunnel_error.to_string e
228+
^ ", expected stunnel error"
229+
)
230+
231+
let tests =
232+
[
233+
( "test_stunnel_log_scanner"
234+
, [
235+
Alcotest.test_case "successful_connection" `Quick
236+
test_successful_connection
237+
; Alcotest.test_case "certificate_self_signed" `Quick
238+
(test_certificate_verify "certificate_self_signed.log"
239+
"self-signed certificate"
240+
)
241+
; Alcotest.test_case "certificate_expired" `Quick
242+
(test_certificate_verify "certificate_expired.log"
243+
"certificate has expired"
244+
)
245+
; Alcotest.test_case "subject_checks_failed" `Quick
246+
(test_certificate_verify "subject_checks_failed.log"
247+
"Subject checks failed"
248+
)
249+
; Alcotest.test_case "connection_refused" `Quick
250+
(test_stunnel_error "connection_refused.log" "Connection refused")
251+
; Alcotest.test_case "no_host_resolved" `Quick
252+
(test_stunnel_error "no_host_resolved.log" "No host resolved")
253+
; Alcotest.test_case "configuration_failed" `Quick
254+
(test_stunnel_error "configuration_failed.log" "Configuration failed")
255+
; Alcotest.test_case "wait_for_configuration_success" `Quick
256+
test_wait_for_configuration_success
257+
; Alcotest.test_case "wait_for_configuration_fail" `Quick
258+
test_wait_for_configuration_fail
259+
; Alcotest.test_case "test_wait_timeout_for_configuration_success" `Quick
260+
test_wait_timeout_for_configuration_success
261+
; Alcotest.test_case "writing_log" `Quick test_writing_log
262+
]
263+
)
264+
]
265+
266+
let () = Alcotest.run "StunnelLogScanner" tests

0 commit comments

Comments
 (0)