mirror of
https://github.com/gnh1201/caterpillar.git
synced 2025-02-06 15:05:00 +00:00
205 lines
5.5 KiB
Perl
205 lines
5.5 KiB
Perl
# https://github.com/gnh1201/caterpillar
|
|
|
|
use JSON;
|
|
use IO::Socket::INET;
|
|
use IO::Socket::SSL;
|
|
use Time::HiRes qw(time);
|
|
|
|
use constant DEFAULT_SOCKET_TIMEOUT => 1;
|
|
use constant STATEFUL_SOCKET_TIMEOUT => 30;
|
|
|
|
sub jsonrpc2_encode {
|
|
my ($method, $params, $id) = @_;
|
|
my $data = {
|
|
jsonrpc => "2.0",
|
|
method => $method,
|
|
params => $params,
|
|
id => $id
|
|
};
|
|
return encode_json($data);
|
|
}
|
|
|
|
sub jsonrpc2_result_encode {
|
|
my ($result, $id) = @_;
|
|
my $data = {
|
|
jsonrpc => "2.0",
|
|
result => $result,
|
|
id => $id
|
|
};
|
|
return encode_json($data);
|
|
}
|
|
|
|
sub jsonrpc2_error_encode {
|
|
my ($error, $id) = @_;
|
|
my $data = {
|
|
jsonrpc => "2.0",
|
|
error => $error,
|
|
id => $id
|
|
};
|
|
return encode_json($data);
|
|
}
|
|
|
|
sub parse_headers {
|
|
my ($str) = @_;
|
|
my %headers;
|
|
|
|
my @lines = split(/\r?\n/, $str);
|
|
|
|
my $first_line = shift(@lines);
|
|
$headers{'@method'} = [split(' ', $first_line)];
|
|
|
|
foreach my $line (@lines) {
|
|
if ($line =~ /^([^:]+):(.*)$/) {
|
|
$headers{$1} = trim($2);
|
|
}
|
|
}
|
|
|
|
return \%headers;
|
|
}
|
|
|
|
sub read_from_remote_server {
|
|
my ($remote_address, $remote_port, $scheme, $data, $conn, $buffer_size, $id) = @_;
|
|
my $sock;
|
|
if ($scheme ~~ ["https", "ssl", "tls"]) {
|
|
$sock = IO::Socket::SSL->new(
|
|
PeerAddr => $remote_address,
|
|
PeerPort => $remote_port,
|
|
SSL_verify_mode => 0, # You may adjust SSL options as needed
|
|
Timeout => DEFAULT_SOCKET_TIMEOUT
|
|
);
|
|
} else {
|
|
$sock = IO::Socket::INET->new(
|
|
PeerAddr => $remote_address,
|
|
PeerPort => $remote_port,
|
|
Proto => 'tcp',
|
|
Timeout => DEFAULT_SOCKET_TIMEOUT
|
|
);
|
|
}
|
|
|
|
if (!$sock) {
|
|
my $error = {
|
|
status => 502,
|
|
code => $!,
|
|
message => $@
|
|
};
|
|
|
|
if (!$conn) {
|
|
print jsonrpc2_error_encode($error, $id);
|
|
} else {
|
|
my $buf = sprintf("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
|
$buf .= jsonrpc2_error_encode($error, $id);
|
|
print $conn $buf;
|
|
}
|
|
} else {
|
|
if (!$conn) {
|
|
# send data
|
|
print $sock $data;
|
|
|
|
# receive data
|
|
my $buf;
|
|
while (!eof($sock) && defined($buf = <$sock>)) {
|
|
print $buf;
|
|
}
|
|
} else {
|
|
# send data
|
|
my $buf;
|
|
while (!eof($conn) && defined($buf = <$conn>)) {
|
|
print $sock $buf;
|
|
}
|
|
|
|
# receive data
|
|
$buf = "";
|
|
while (!eof($sock) && defined($buf = <$sock>)) {
|
|
print $conn $buf;
|
|
}
|
|
}
|
|
|
|
close($sock);
|
|
}
|
|
}
|
|
|
|
sub relay_request {
|
|
my ($params, $id) = @_;
|
|
my $buffer_size = $params->{'buffer_size'};
|
|
my $request_data = decode_base64($params->{'request_data'});
|
|
my $request_header = parse_headers($request_data);
|
|
my $request_length = int($params->{'request_length'});
|
|
my $client_address = $params->{'client_address'};
|
|
my $client_port = int($params->{'client_port'});
|
|
my $client_encoding = $params->{'client_encoding'};
|
|
my $remote_address = $params->{'remote_address'};
|
|
my $remote_port = int($params->{'remote_port'});
|
|
my $scheme = $params->{'scheme'};
|
|
my $datetime = $params->{'datetime'};
|
|
|
|
given ($request_header->{'@method'}[0]) {
|
|
when ("CONNECT") {
|
|
my $error = {
|
|
status => 405,
|
|
code => -1,
|
|
message => "Method Not Allowed"
|
|
};
|
|
print jsonrpc2_error_encode($error, $id);
|
|
}
|
|
default {
|
|
read_from_remote_server($remote_address, $remote_port, $scheme, $request_data, undef, $buffer_size, $id);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub relay_connect {
|
|
my ($params, $id) = @_;
|
|
my $buffer_size = $params->{'buffer_size'};
|
|
my $client_address = $params->{'client_address'};
|
|
my $client_port = int($params->{'client_port'});
|
|
my $client_encoding = $params->{'client_encoding'};
|
|
my $remote_address = $params->{'remote_address'};
|
|
my $remote_port = int($params->{'remote_port'});
|
|
my $scheme = $params->{'scheme'};
|
|
my $datetime = $params->{'datetime'};
|
|
|
|
my $starttime = time();
|
|
my $conn = IO::Socket::INET->new(
|
|
PeerAddr => $client_address,
|
|
PeerPort => $client_port,
|
|
Proto => 'tcp',
|
|
Timeout => STATEFUL_SOCKET_TIMEOUT
|
|
);
|
|
if (!$conn) {
|
|
my $error = {
|
|
status => 502,
|
|
code => $!,
|
|
message => $@
|
|
};
|
|
print jsonrpc2_error_encode($error, $id);
|
|
} else {
|
|
my $stoptime = time();
|
|
my $connection_speed = int(($stoptime - $starttime) * 1000);
|
|
my $data = jsonrpc2_encode("relay_accept", {
|
|
success => 1,
|
|
connection_speed => $connection_speed
|
|
}, $id);
|
|
print $conn $data . "\r\n\r\n";
|
|
|
|
read_from_remote_server($remote_address, $remote_port, $scheme, undef, $conn, $buffer_size, $id);
|
|
close($conn);
|
|
}
|
|
}
|
|
|
|
# Parse a context
|
|
my $json_input = do { local $/; <STDIN> };
|
|
my $context = decode_json($json_input);
|
|
|
|
# Check if it's JSON-RPC 2 (stateless)
|
|
if ($context->{'jsonrpc'} eq "2.0") {
|
|
my $method = $context->{'method'};
|
|
given ($method) {
|
|
when ("relay_request") {
|
|
relay_request($context->{'params'}, $context->{'id'}); # stateless mode
|
|
}
|
|
when ("relay_connect") {
|
|
relay_connect($context->{'params'}, $context->{'id'}); # stateful mode
|
|
}
|
|
}
|
|
}
|