Skip to content

Commit

Permalink
Make script/dewebsockify a Modulino
Browse files Browse the repository at this point in the history
  • Loading branch information
r-richardson committed Dec 19, 2024
1 parent 4b05d9d commit b0e33d6
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 134 deletions.
152 changes: 152 additions & 0 deletions OpenQA/Isotovideo/dewebsockify.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/perl
# Copyright 2022 SUSE LLC
# 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;

package OpenQA::Isotovideo::dewebsockify;
use Exporter 'import';
our @EXPORT_OK = ('main');

sub main {
my ($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;
}

# Entry point for script execution
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;
}

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;
}

main(parse_args()) if not caller();
1;

159 changes: 27 additions & 132 deletions script/dewebsockify
Original file line number Diff line number Diff line change
@@ -1,134 +1,29 @@
#!/usr/bin/perl
# Copyright 2022 SUSE LLC
# 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;
#!/usr/bin/env perl
use strict;
use warnings;

use FindBin;
use lib "$FindBin::Bin/../lib"; # Ensure OpenQA namespace is in @INC
use OpenQA::Isotovideo::dewebsockify;

my %args;
GetOptions(\%args, 'websocketurl=s', 'listenport=s', 'cookie=s', 'loglevel=s', 'insecure', 'help|h|?')
or usage(1);
usage(0) if $args{help};

OpenQA::Isotovideo::dewebsockify::main(\%args);

sub usage {
print <<"END_USAGE";
Usage: $0 --websocketurl <URL> [--listenport <PORT>] [--cookie <COOKIE>] [--loglevel <LEVEL>] [--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');
});

$server->listen(port => $port);
$server->start;
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
4 changes: 2 additions & 2 deletions t/27-consoles-vmware.t
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ subtest 'turning WebSocket into normal socket via dewebsockify' => sub {
$vmware->_cleanup_previous_dewebsockify_process;

# test error handling of dewebsockify
my $dewebsockify_cmd_start = "$bmwqemu::topdir/script/dewebsockify --listenport $tcp_port --websocketurl";
my $dewebsockify_cmd_start = "$bmwqemu::topdir/script/dewebsockify.pm --listenport $tcp_port --websocketurl";
my $assert_log = sub ($dewebsockify_pipe, $expected) {
my $dewebsockify_log;
read($dewebsockify_pipe, $dewebsockify_log, 1000) or die "Unable read dewebsockify pipe: $!";
Expand Down Expand Up @@ -283,7 +283,7 @@ subtest 'test against real VMWare instance' => sub {

# spawn test instance of dewebsockify for manually testing with vncviewer
if (my $port = $ENV{OS_AUTOINST_DEWEBSOCKIFY_PORT}) { # uncoverable statement
system "'$bmwqemu::topdir/script/dewebsockify' --listenport '$port' --websocketurl '$wss_url' --cookie 'vmware_client=VMware; $session' --insecure"; # uncoverable statement
system "'$bmwqemu::topdir/script/dewebsockify.pm' --listenport '$port' --websocketurl '$wss_url' --cookie 'vmware_client=VMware; $session' --insecure"; # uncoverable statement

}
};
Expand Down

0 comments on commit b0e33d6

Please sign in to comment.