From ae4c0e63aa31c910cbc164194d59eed60f949fe7 Mon Sep 17 00:00:00 2001 From: r-richardson Date: Wed, 18 Dec 2024 13:17:22 +0100 Subject: [PATCH] Make script/dewebsockify a Modulino --- OpenQA/Isotovideo/dewebsockify.pm | 129 ++++++++++++++++++++++++++ script/dewebsockify | 148 +++++------------------------- 2 files changed, 152 insertions(+), 125 deletions(-) create mode 100644 OpenQA/Isotovideo/dewebsockify.pm diff --git a/OpenQA/Isotovideo/dewebsockify.pm b/OpenQA/Isotovideo/dewebsockify.pm new file mode 100644 index 00000000000..384da8b1942 --- /dev/null +++ b/OpenQA/Isotovideo/dewebsockify.pm @@ -0,0 +1,129 @@ +# Copyright SUSE LLC +# SPDX-License-Identifier: GPL-2.0-or-later +# + +package OpenQA::Isotovideo::dewebsockify; + +use Mojo::Base -strict, -signatures; + +use Mojo::IOLoop::Server; +use Mojo::IOLoop::Stream; +use Mojo::Log; +use Mojo::UserAgent; + +sub main ($args) { + die "Arguments must be a hash reference\n" unless defined $args && ref($args) eq 'HASH'; + + my $ws_url = $args->{websocketurl} or die "websocket URL missing\n"; + my $port = $args->{listenport} // 5900; + my $cookie = $args->{cookie} // undef; + my $log = Mojo::Log->new(level => $args->{loglevel} // 'info'); + + $log->debug("websocket url: $ws_url"); + $log->debug("listen port: $port"); + $log->debug("cookie: " . $cookie) if $cookie; + + # create listen socket + my $ua = Mojo::UserAgent->new; + my $server = Mojo::IOLoop::Server->new; + my $ws_connection; + my $stream; + my @tosend; + $ua->insecure($args->{insecure} // 0); + + # accept new connections + $server->on(accept => sub ($server, $handle) { + if ($stream) { + $log->info('Rejecting new client; already one client connected'); + return undef; + } + $stream = Mojo::IOLoop::Stream->new($handle); + $stream->start; + $stream->reactor->start unless $stream->reactor->is_running; + + # establish websocket connection + if (!$ws_connection) { + $log->info("Establishing WebSocket connection to $ws_url"); + @tosend = (); + my $tx = $ua->build_websocket_tx($ws_url); + my $req = $tx->req; + my $headers = $tx->req->headers; + $req->cookies($cookie) if $cookie; + $headers->add(Pragma => 'no-cache'); + $headers->add('Sec-WebSocket-Protocol' => 'binary, vmware-vvc'); + $ua->start($tx => sub ($ua, $tx) { + + # handle errors + if (!$tx->is_websocket) { + if (my $err = $tx->error) { + $log->error($err->{code} ? "WebSocket $err->{code} response: $err->{message}" + : "WebSocket connection error: $err->{message}"); + } + else { + $log->error('Unable to upgrade to WebSocket connection'); + } + my $body = $tx->res->body; + $log->trace($body) if $body; + $ws_connection = undef; + $stream->close_gracefully if $stream; + return undef; + } + $log->info('WebSocket connection established'); + $tx->max_websocket_size(1024**3); # required to avoid 1009 error, at least when using raw encoding + $ws_connection = $tx; + + # pass data from websocket to raw socket + $tx->on(text => sub ($tx, $text) { + $log->trace("WebSocket text message: $text"); + }); + $tx->on(binary => sub ($tx, $bytes) { + $log->trace("WebSocket binary message:\n" . sprintf("%v02X", $bytes)); + $stream->write($bytes) if $stream; + }); + + # pass pending data from raw socket to websocket + $tx->send($_) for @tosend; + @tosend = (); + + # handle websocket connection finish + # note: Terminating here because at least for VMWare one needed a new URL/ticket anyways. + $tx->on(finish => sub ($tx, $code, $reason) { + $log->info("WebSocket closed with status $code."); + $ws_connection = undef; + $stream->close_gracefully if $stream; + Mojo::IOLoop->stop_gracefully; + }); + }); + } + + # pass data from raw socket to websocket + $stream->on(read => sub ($s, $bytes) { + if ($ws_connection) { + $log->debug("Raw socket message:\n" . sprintf("%v02X", $bytes)); # uncoverable statement + $ws_connection->send({binary => $bytes}); # uncoverable statement + } + else { + $log->debug("Raw socket message (forwarding later):\n" . sprintf("%v02X", $bytes)); # uncoverable statement + push @tosend, $bytes; # uncoverable statement + } + }); + + # handle raw socket close/error + $stream->on(close => sub ($s) { + $log->info('Client closed connection'); + $stream = undef; + $ws_connection ? $ws_connection->finish : Mojo::IOLoop->stop_gracefully; + }); + $stream->on(error => sub ($stream, $err) { + $log->error("Client error: $err"); # uncoverable statement + }); + + $log->info('Client accepted'); + }); + $server->listen(port => $port); + $server->start; + Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +} + +1; + diff --git a/script/dewebsockify b/script/dewebsockify index ab3bcc2915d..70e052a3f78 100755 --- a/script/dewebsockify +++ b/script/dewebsockify @@ -3,132 +3,30 @@ # SPDX-License-Identifier: GPL-2.0-or-later # -use Mojo::Base -strict, -signatures; - use Getopt::Long; -use Mojo::Cookie::Request; -use Mojo::IOLoop; -use Mojo::IOLoop::Server; -use Mojo::IOLoop::Stream; -use Mojo::Log; -use Mojo::UserAgent; - -sub usage ($r = 0) { - say 'Listens on a TCP port forwarding data to the specified websocket server - example: --websocketurl wss://... --listenport 590x --cookie "vmware_client=VMware; some_session=foobar" --insecure'; - exit $r; +use OpenQA::Isotovideo::dewebsockify; + +OpenQA::Isotovideo::dewebsockify::main(parse_args()) unless caller(); + +sub usage () { + print <<"END_USAGE"; +Usage: $0 --websocketurl [--listenport ] [--cookie ] [--loglevel ] [--insecure] +Options: + --websocketurl The WebSocket server URL (required). + --listenport Port to listen on (default: 5900). + --cookie Cookie to include in WebSocket requests. + --loglevel Log level (default: 'info'). + --insecure Allow insecure WebSocket connections. + --help, -h, -? Show this help message. +END_USAGE + exit $_[0]; } -# read CLI args -my %options; -GetOptions(\%options, 'websocketurl=s', 'listenport=s', 'cookie=s', 'loglevel=s', 'insecure', 'help|h|?') or usage(1); -usage if $options{help}; -my $ws_url = $options{websocketurl} or die "websocket URL missing\n"; -my $port = $options{listenport} // 5900; -my $cookie = $options{cookie} ? $options{cookie} : undef; -my $log = Mojo::Log->new(level => $options{loglevel} // 'info'); -$log->debug("websocket url: $ws_url"); -$log->debug("listen port: $port"); -$log->debug("cookie: " . $cookie) if $cookie; - -# create listen socket -my $ua = Mojo::UserAgent->new; -my $server = Mojo::IOLoop::Server->new; -my $ws_connection; -my $stream; -my @tosend; -$ua->insecure($options{insecure} // 0); - -# accept new connections -$server->on(accept => sub ($server, $handle) { - if ($stream) { - $log->info('Rejecting new client; already one client connected'); - return undef; - } - $stream = Mojo::IOLoop::Stream->new($handle); - $stream->start; - $stream->reactor->start unless $stream->reactor->is_running; - - # establish websocket connection - if (!$ws_connection) { - $log->info("Establishing WebSocket connection to $ws_url"); - @tosend = (); - my $tx = $ua->build_websocket_tx($ws_url); - my $req = $tx->req; - my $headers = $tx->req->headers; - $req->cookies($cookie) if $cookie; - $headers->add(Pragma => 'no-cache'); - $headers->add('Sec-WebSocket-Protocol' => 'binary, vmware-vvc'); - $ua->start($tx => sub ($ua, $tx) { - - # handle errors - if (!$tx->is_websocket) { - if (my $err = $tx->error) { - $log->error($err->{code} ? "WebSocket $err->{code} response: $err->{message}" - : "WebSocket connection error: $err->{message}"); - } - else { - $log->error('Unable to upgrade to WebSocket connection'); - } - my $body = $tx->res->body; - $log->trace($body) if $body; - $ws_connection = undef; - $stream->close_gracefully if $stream; - return undef; - } - $log->info('WebSocket connection established'); - $tx->max_websocket_size(1024**3); # required to avoid 1009 error, at least when using raw encoding - $ws_connection = $tx; - - # pass data from websocket to raw socket - $tx->on(text => sub ($tx, $text) { - $log->trace("WebSocket text message: $text"); - }); - $tx->on(binary => sub ($tx, $bytes) { - $log->trace("WebSocket binary message:\n" . sprintf("%v02X", $bytes)); - $stream->write($bytes) if $stream; - }); - - # pass pending data from raw socket to websocket - $tx->send($_) for @tosend; - @tosend = (); - - # handle websocket connection finish - # note: Terminating here because at least for VMWare one needed a new URL/ticket anyways. - $tx->on(finish => sub ($tx, $code, $reason) { - $log->info("WebSocket closed with status $code."); - $ws_connection = undef; - $stream->close_gracefully if $stream; - Mojo::IOLoop->stop_gracefully; - }); - }); - } - - # pass data from raw socket to websocket - $stream->on(read => sub ($s, $bytes) { - if ($ws_connection) { - $log->debug("Raw socket message:\n" . sprintf("%v02X", $bytes)); # uncoverable statement - $ws_connection->send({binary => $bytes}); # uncoverable statement - } - else { - $log->debug("Raw socket message (forwarding later):\n" . sprintf("%v02X", $bytes)); # uncoverable statement - push @tosend, $bytes; # uncoverable statement - } - }); - - # handle raw socket close/error - $stream->on(close => sub ($s) { - $log->info('Client closed connection'); - $stream = undef; - $ws_connection ? $ws_connection->finish : Mojo::IOLoop->stop_gracefully; - }); - $stream->on(error => sub ($stream, $err) { - $log->error("Client error: $err"); # uncoverable statement - }); - - $log->info('Client accepted'); -}); +sub parse_args () { + my %options; + GetOptions(\%options, 'websocketurl=s', 'listenport=s', 'cookie=s', 'loglevel=s', 'insecure', 'help|h|?') + or usage(1); + usage(0) if $options{help}; + return \%options; +} -$server->listen(port => $port); -$server->start; -Mojo::IOLoop->start unless Mojo::IOLoop->is_running;