Created
May 29, 2020 07:04
-
-
Save sheeit/e497994a5d12bc69d33068eb31f4cab2 to your computer and use it in GitHub Desktop.
Perl script that helps with iptables port-forwarding.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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