Skip to content

Commit e756f26

Browse files
committed
Redesign proxy module
Signed-off-by: Changlei Li <changlei.li@cloud.com> Fix module Signed-off-by: Changlei Li <changlei.li@cloud.com> Fix module Signed-off-by: Changlei Li <changlei.li@cloud.com> Fix module Signed-off-by: Changlei Li <changlei.li@cloud.com>
1 parent 1280bff commit e756f26

2 files changed

Lines changed: 123 additions & 62 deletions

File tree

ocaml/libs/stunnel/stunnel.ml

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,35 @@ let check_stunnel_logfile logfile =
538538
539539
let diagnose_failure st_proc = check_stunnel_logfile st_proc.logfile
540540
541-
let check_stunnel_status logfile =
542-
match check_stunnel_logfile logfile with
541+
(** Check only new log entries since the given position *)
542+
let check_stunnel_logfile_from_position logfile start_pos =
543+
let cert_errors = ref [] in
544+
let fd = Unix.openfile logfile [Unix.O_RDONLY] 0 in
545+
let finally = Xapi_stdext_pervasives.Pervasiveext.finally in
546+
finally
547+
(fun () ->
548+
let _ = Unix.lseek fd start_pos Unix.SEEK_SET in
549+
let ic = Unix.in_channel_of_descr fd in
550+
try
551+
while true do
552+
let line = input_line ic in
553+
!stunnel_logger line ;
554+
Astring.String.cut ~rev:true ~sep:"CERT: " line
555+
|> Option.iter (fun (_, cert_error) ->
556+
cert_errors := cert_error :: !cert_errors
557+
) ;
558+
check_verify_error !cert_errors line ;
559+
check_error "Connection refused" line ;
560+
check_error "No host resolved" line ;
561+
check_error "No route to host" line ;
562+
check_error "Invalid argument" line
563+
done
564+
with End_of_file -> ()
565+
)
566+
(fun () -> Unix.close fd)
567+
568+
let check_stunnel_status_from_position logfile start_pos =
569+
match check_stunnel_logfile_from_position logfile start_pos with
543570
| () ->
544571
Ok ()
545572
| exception Stunnel_verify_error reasons ->
@@ -600,7 +627,12 @@ let wait_for_connection_done logfile =
600627
601628
module UnixSocketProxy = struct
602629
(** Handle for a long-running stunnel proxy *)
603-
type t = {proxy_pid: pid; proxy_socket_path: string; proxy_logfile: string}
630+
type t = {
631+
proxy_pid: pid
632+
; proxy_socket_path: string
633+
; proxy_logfile: string
634+
; mutable last_checked_position: int
635+
}
604636
605637
let socket_path handle = handle.proxy_socket_path
606638
@@ -610,13 +642,55 @@ module UnixSocketProxy = struct
610642
Printf.sprintf "/var/run/stunnel-proxy-%s-%d-%s.sock" remote_host
611643
remote_port uuid
612644
645+
(** Diagnose the status of a running stunnel proxy by checking its logfile.
646+
Only checks new log entries since the last call to diagnose.
647+
Updates the last_checked_position after checking.
648+
Returns Ok () if no errors found, Error with details otherwise. *)
649+
let diagnose handle =
650+
let start_pos = handle.last_checked_position in
651+
let current_size = (Unix.stat handle.proxy_logfile).Unix.st_size in
652+
653+
if current_size <= start_pos then (
654+
D.debug "%s: no new log entries (position %d)" __FUNCTION__ start_pos ;
655+
Ok ()
656+
) else (
657+
D.debug "%s: checking log from position %d to %d" __FUNCTION__ start_pos
658+
current_size ;
659+
(* Print new log content for debugging *)
660+
let fd = Unix.openfile handle.proxy_logfile [Unix.O_RDONLY] 0 in
661+
let finally = Xapi_stdext_pervasives.Pervasiveext.finally in
662+
finally
663+
(fun () ->
664+
let _ = Unix.lseek fd start_pos Unix.SEEK_SET in
665+
let len = current_size - start_pos in
666+
let buf = Bytes.create len in
667+
let n = Unix.read fd buf 0 len in
668+
if n > 0 then
669+
D.debug "%s: new log content:\n%s" __FUNCTION__
670+
(Bytes.sub_string buf 0 n)
671+
)
672+
(fun () -> Unix.close fd) ;
673+
match
674+
check_stunnel_status_from_position handle.proxy_logfile start_pos
675+
with
676+
| Ok () ->
677+
handle.last_checked_position <- current_size ;
678+
Ok ()
679+
| Error _ as e ->
680+
handle.last_checked_position <- current_size ;
681+
e
682+
)
683+
613684
(** Start a long-running stunnel proxy listening on a UNIX socket.
614685
Returns Ok handle that must be explicitly stopped with [stop].
615686
The stunnel process will continue running until stopped, allowing
616687
multiple clients to connect to the UNIX socket over time.
617688
If [unix_socket_path] is not provided, a unique path will be generated.
618-
Note: This only starts the proxy - it does NOT verify the certificate.
619-
Use [check_cert] after starting to verify the remote server's certificate. *)
689+
690+
This function performs initial certificate verification by making a test
691+
connection. If certificate verification fails, returns Error and the proxy
692+
is not started. If successful, subsequent connections by stubs will also
693+
be verified automatically by stunnel. *)
620694
let start ~verify_cert ~remote_host ~remote_port ?unix_socket_path () =
621695
try
622696
let unix_socket_path =
@@ -638,12 +712,39 @@ module UnixSocketProxy = struct
638712
D.debug "%s: started stunnel proxy (pid:%d):%s -> %s:%d log: %s"
639713
__FUNCTION__ (getpid pid) unix_socket_path remote_host remote_port
640714
logfile ;
641-
Ok
715+
716+
let handle =
642717
{
643718
proxy_pid= pid
644719
; proxy_socket_path= unix_socket_path
645720
; proxy_logfile= logfile
721+
; last_checked_position= 0
646722
}
723+
in
724+
725+
(* Make initial connection to verify certificate *)
726+
let sock = Unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in
727+
let finally = Xapi_stdext_pervasives.Pervasiveext.finally in
728+
finally
729+
(fun () ->
730+
Unix.connect sock (Unix.ADDR_UNIX unix_socket_path) ;
731+
(* Wait for TLS handshake and certificate verification *)
732+
wait_for_connection_done logfile
733+
)
734+
(fun () -> Unix.close sock) ;
735+
736+
(* Check for certificate verification errors using diagnose *)
737+
match diagnose handle with
738+
| Ok () ->
739+
D.debug "%s: initial certificate verification passed" __FUNCTION__ ;
740+
Ok handle
741+
| Error e ->
742+
(* Certificate verification failed, clean up *)
743+
D.debug "%s: initial certificate verification failed" __FUNCTION__ ;
744+
disconnect_with_pid ~wait:false ~force:true pid ;
745+
Unixext.unlink_safe unix_socket_path ;
746+
Unixext.unlink_safe logfile ;
747+
Error e
647748
with
648749
| Stunnel_error reason ->
649750
Error (Stunnel reason)
@@ -664,12 +765,6 @@ module UnixSocketProxy = struct
664765
D.debug "%s: stopped stunnel proxy (pid:%d):%s" __FUNCTION__
665766
(getpid handle.proxy_pid) handle.proxy_socket_path
666767
667-
(** Diagnose the status of a running stunnel proxy by checking its logfile.
668-
Returns Ok () if no errors found, Error with details otherwise. *)
669-
let diagnose handle =
670-
print_log handle ;
671-
check_stunnel_status handle.proxy_logfile
672-
673768
(** Start a proxy, execute a function with it, and automatically stop it.
674769
The proxy is guaranteed to be stopped even if the function raises an exception.
675770
If [unix_socket_path] is not provided, a unique path will be generated.
@@ -716,28 +811,6 @@ let fetch_server_cert ~remote_host ~remote_port =
716811
None
717812
with _ -> None
718813
719-
(** Check certificate verification using a temporary stunnel connection.
720-
This creates an isolated stunnel connection solely for certificate verification.
721-
Returns Ok () if certificate is valid, Error with details otherwise. *)
722-
let check_cert ~verify_cert ~remote_host ~remote_port =
723-
UnixSocketProxy.with_proxy ~verify_cert ~remote_host ~remote_port
724-
(fun handle ->
725-
(* Make one connection to trigger certificate verification *)
726-
let sock = Unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in
727-
let finally = Xapi_stdext_pervasives.Pervasiveext.finally in
728-
finally
729-
(fun () ->
730-
(* Connecting to the socket triggers stunnel to establish TLS connection
731-
and perform certificate verification *)
732-
Unix.connect sock (Unix.ADDR_UNIX handle.proxy_socket_path) ;
733-
(* Wait for the connection attempt to complete and be logged *)
734-
wait_for_connection_done handle.proxy_logfile
735-
)
736-
(fun () -> Unix.close sock) ;
737-
(* Check the logfile for certificate verification result *)
738-
UnixSocketProxy.diagnose handle
739-
)
740-
741814
(* If we reach here the whole stunnel log should have been gone through
742815
(possibly printed/logged somewhere. No necessity to raise an exception,
743816
since when this function being called, there is usually some exception

ocaml/libs/stunnel/stunnel.mli

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -78,28 +78,31 @@ module UnixSocketProxy : sig
7878
-> unit
7979
-> (t, stunnel_error) result
8080
(** Start a long-running stunnel proxy listening on a UNIX socket.
81-
Returns Ok handle that must be explicitly stopped with [stop].
82-
The stunnel process will continue running until stopped, allowing
83-
multiple clients to connect to the UNIX socket over time.
84-
Returns Error if stunnel fails to start or initialize.
85-
81+
82+
This function starts the proxy and immediately performs certificate
83+
verification by making an initial test connection. If certificate
84+
verification fails, the proxy is stopped and cleaned up automatically.
85+
86+
Returns [Ok handle] if stunnel starts successfully and the certificate
87+
is valid. Returns [Error] if stunnel fails to start, initialize, or
88+
if certificate verification fails.
89+
8690
If [unix_socket_path] is not provided, a unique path will be generated
8791
automatically in /var/run with the format:
88-
stunnel-proxy-{host}-{port}-{uuid}.sock
89-
90-
Note: This only starts the proxy - it does NOT verify the certificate.
91-
The TLS connection and certificate verification happen when a client
92-
actually connects through the socket. Use the standalone [check_cert]
93-
function to verify the remote server's certificate before starting the
94-
proxy if needed. *)
92+
stunnel-proxy-{host}-{port}-{uuid}.sock *)
9593

9694
val stop : t -> unit
9795
(** Stop a running stunnel proxy and clean up resources.
9896
This kills the stunnel process and removes the socket and log files. *)
9997

10098
val diagnose : t -> (unit, stunnel_error) result
10199
(** Diagnose the status of a running stunnel proxy by checking its logfile.
102-
Returns Ok () if no errors found, Error with details otherwise. *)
100+
101+
Only checks NEW log entries since the last call to [diagnose] (or since
102+
[start] if never called). This allows efficient monitoring of connection
103+
failures that occur after the initial certificate verification.
104+
105+
Returns [Ok ()] if no new errors found, [Error] with details otherwise. *)
103106

104107
val with_proxy :
105108
verify_cert:verification_config option
@@ -119,21 +122,6 @@ val fetch_server_cert : remote_host:string -> remote_port:int -> string option
119122
Uses openssl s_client to connect and retrieve the certificate in PEM format.
120123
This is useful for TOFU (Trust-On-First-Use) scenarios. *)
121124

122-
val check_cert :
123-
verify_cert:verification_config option
124-
-> remote_host:string
125-
-> remote_port:int
126-
-> (unit, stunnel_error) result
127-
(** Check certificate verification using a temporary stunnel connection.
128-
Returns [Ok ()] if the certificate is valid according to the verification
129-
policy (VerifyPeer or CheckHost), or [Error] with details if verification fails.
130-
131-
This creates an isolated, temporary stunnel connection solely for certificate
132-
verification. The connection is automatically cleaned up after the check.
133-
134-
This is useful for pre-flight certificate validation before starting a
135-
long-running proxy, or for periodic re-validation of certificates. *)
136-
137125
val appliance : verification_config
138126

139127
val pool : verification_config

0 commit comments

Comments
 (0)