Skip to content

Instantly share code, notes, and snippets.

@sheeit
Created May 29, 2020 07:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sheeit/e497994a5d12bc69d33068eb31f4cab2 to your computer and use it in GitHub Desktop.
Save sheeit/e497994a5d12bc69d33068eb31f4cab2 to your computer and use it in GitHub Desktop.
Perl script that helps with iptables port-forwarding.
#!/usr/bin/env perl
use 5.028;
use strict;
use warnings ( FATAL => 'all' );
# Automatically die after failed system calls
use autodie;
# Use UTF-8 encoding
use utf8;
# Use croak/carp instead of die/warn
# (cf. Effective Perl Programming, item 102)
use Carp;
use Regexp::Common qw(net number);
use String::ShellQuote;
# Satisfy perlcritic
use Readonly;
use English '-no_match_vars';
Readonly our $VERSION => '0.0.1';
Readonly my $DEFAULT_INTERFACE => 'wlp2s0_sta';
Readonly my $MAX_PORT_NUMBER => 65_535;
Readonly my $PORTNUM => $RE{num}{int}{ -places => '1,5' }{ -sign => q() };
Readonly my @VALID_ARGUMENTS => ( 2, 3 );
sub show_help;
sub getcommands;
sub execute_command;
if ( @ARGV < $VALID_ARGUMENTS[0] || @ARGV > $VALID_ARGUMENTS[1] ) {
show_help();
}
foreach my $arg (@ARGV) {
if ( $arg =~ m/\A -(?: h | -? help ) \z/mosx ) {
show_help();
}
}
my ( $ip_addr, $port, $iface ) = @ARGV;
$iface //= $DEFAULT_INTERFACE;
# Validate $port argument
if ( $port !~ m/\A $PORTNUM (?: [:] $PORTNUM )? \z/mosx ) {
croak("$PROGRAM_NAME: invalid port argument: $port.");
}
else {
my ( $pstart, $pend ) = map { int } split /:/mosx, $port;
if ( $pstart > $MAX_PORT_NUMBER
|| ( defined $pend && $pend > $MAX_PORT_NUMBER ) )
{
croak("$PROGRAM_NAME: port argument $port out of range.");
}
if ( defined $pend && $pend <= $pstart ) {
croak("$PROGRAM_NAME: $port: invalid range ($pstart > $pend).");
}
}
my @commands = getcommands( $ip_addr, $port, $iface, [qw(tcp udp)] );
my $i = 0;
foreach my $command (@commands) {
say "Command $i: ", shell_quote( @{$command} )
or croak("say failed: $OS_ERROR");
++$i;
execute_command($command);
}
sub execute_command {
my $command = shift;
( defined $command and ref $command eq 'ARRAY' and @{$command} > 0 )
or croak('execute_command expects an array reference.');
my $pid = fork // croak("fork failed: $OS_ERROR");
if ( $pid == 0 ) {
# Child process
local $ENV{PATH} = join q(:), qw(
/sbin
/usr/sbin
/bin
/usr/bin
/usr/local/bin
);
exec { $command->[0] } @{$command}
or croak("exec failed: $OS_ERROR");
}
# Back to parent, wait for child to terminate
waitpid $pid, 0;
return;
}
sub getcommands {
my ( $ip, $po, $if, $protos ) = @_;
Readonly my $NUMBER_OF_ARGUMENTS => 4;
@_ == $NUMBER_OF_ARGUMENTS
or croak('command1: wrong number of arguments.');
ref $protos eq 'ARRAY'
or croak( 'command1: $' . 'protcols should be an array ref.' );
my ( $p1, $p2 ) = split /:/mosx, $po;
$p2 //= $p1;
my @cmds;
foreach my $p ( $p1 .. $p2 ) {
foreach my $pr ( @{$protos} ) {
my @cmd1 = (
qw(sudo iptables),
'-t' => 'nat',
'-A' => 'PREROUTING',
'-p' => $pr,
'-i' => $if,
'--dport' => $p,
'-j' => 'DNAT',
'--to-destination' => ( join q(:), $ip, $p ),
);
my @cmd2 = (
qw(sudo iptables),
'-A' => 'FORWARD',
'-p' => $pr,
'-d' => $ip,
'--dport' => $p,
'-m' => 'state',
'--state' => ( uc join q(,), qw(new established related) ),
'-j' => 'ACCEPT',
);
push @cmds, \@cmd1, \@cmd2;
}
}
return @cmds;
}
sub show_help {
say uc 'Usage: ', $PROGRAM_NAME, ' ip_addr port[:range] [interface]'
or croak("say failed: $OS_ERROR");
exit 0;
}
# vim: set et ft=perl sw=4 sts=4 ts=8 fenc=utf-8:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment