#!/usr/bin/perl -w # Copyright 2010 Jeff Garzik # Distributed under the MIT/X11 software license, # see http://www.opensource.org/licenses/mit-license.php use strict; use POE; use POE::Component::Server::TCP; use Net::Netmask; use Getopt::Long; use Pod::Usage; my $opt_debug = 0; my $opt_man = 0; my $opt_help = 0; my $opt_port = 27007; my $opt_sub_port = 37007; my $opt_conf = '/etc/bcast-server.conf'; my (%cli, @filter); # process command line parameters GetOptions('help|?' => \$opt_help, debug => \$opt_debug, man => \$opt_man, 'conf=s' => \$opt_conf, 'subport=i' => \$opt_sub_port, 'port=i') or pod2usage(2); pod2usage(1) if $opt_help; pod2usage(1) unless (($opt_sub_port > 1024) && ($opt_sub_port < 65536)); pod2usage(1) unless (($opt_port > 1024) && ($opt_port < 65536)); pod2usage(-exitstatus => 0, -verbose => 2) if $opt_man; read_conf_file(); # initialize TCP server to which the world may connect POE::Component::Server::TCP->new( Port => $opt_port, ClientPreConnect => \&cli_pre_conn, ClientConnected => \&cli_conn, ClientDisconnected => \&cli_disconn, ClientInput => sub { }, InlineStates => { send_output => \&cli_send_output, }, ); # initialize TCP server to which we connect, to broadcast data POE::Component::Server::TCP->new( Port => $opt_sub_port, Address => '127.0.0.1', ClientInput => \&server_broadcast, ); # run main loop POE::Kernel->run(); exit(0); sub read_conf_file { open(F, $opt_conf) or return; my $line = 0; while () { $line++; next if /^\s*$/; # skip blank lines next if /^\s*#/; # skip lines beginning with '#' # filter 1.2.3.4/24 if (/^\s*filter\s+(\S+)\s*$/) { my $mask = new Net::Netmask($1); print STDERR "$opt_conf($line): invalid netmask $1\n" unless $mask; push(@filter, $mask); } # filter 1.2.3.4 255.255.255.0 elsif (/^\s*filter\s+(\S+)\s+(\S+)\s*$/) { my $mask = new Net::Netmask($1, $2); print STDERR "$opt_conf($line): invalid netmask $1 $2\n" unless $mask; push(@filter, $mask); } # port 12345 elsif (/^\s*port\s+(\d+)\s*$/ && ($1 > 1024) && ($1 < 65536)) { $opt_port = $1; } # subport 12345 elsif (/^\s*subport\s+(\d+)\s*$/ && ($1 > 1024) && ($1 < 65536)) { $opt_sub_port = $1; } # debug elsif (/^\s*debug\s*$/) { $opt_debug = 1; } else { print STDERR "$opt_conf($line): invalid configuration directive\n"; } } close(F); } sub ip_validate { my ($ip) = @_; return 1 unless @filter; foreach my $f (@filter) { return 0 if $f->match($ip); } return 1; } sub server_broadcast { return unless (length($_[ARG0]) > 0); my $n_cli = 0; foreach my $sid (keys %cli) { $poe_kernel->post($sid, 'send_output', $_[ARG0]); $n_cli++; } printf STDERR "BROADCAST: %d bytes sent to %d clients\n", length($_[ARG0]), $n_cli if $opt_debug; } sub cli_send_output { my $data = $_[ARG0]; $_[HEAP]{client}->put($data); } sub cli_pre_conn { return (new IO::Handle) unless ip_validate($_[HEAP]{remote_ip}); return $_[ARG0]; } sub cli_conn { my $sid = $_[SESSION]->ID; my %info = ( ip => $_[HEAP]{remote_ip}, ); printf STDERR "Client %s:%d connected\n", $_[HEAP]{remote_ip}, $_[HEAP]{remote_port} if $opt_debug; $cli{$sid} = \%info; } sub cli_disconn { my $sid = $_[SESSION]->ID; printf STDERR "Client %s:%d disconnected\n", $_[HEAP]{remote_ip}, $_[HEAP]{remote_port} if $opt_debug; delete $cli{$sid}; } __END__ =head1 NAME bcast-server.pl - TCP server for broadcasting data to listening clients =head1 SYNOPSIS bcast-server.pl [options] Options: --conf=FILE Specify server configuration file. Default: /etc/bcast-server.conf --subport=PORT Specify TCP listening port for data submissions. Valid values range from 1025-65535. --port=PORT Specify TCP listening port for incoming clients. Valid values range from 1025-65535. --debug Enable debug output. --help Print brief help message. --man Print full documentation. =head1 OPTIONS =over 8 =item B<--debug> Enable debug output to standard error (STDERR). =item B<--help> Print a brief help message and exits. =item B<--man> Prints the manual page and exits. =item B<--port> Specify TCP port for incoming data-receive client connections. =item B<--subport> Specify TCP port for incoming data-submit connections. =back =head1 DESCRIPTION B will broadcast the given input to all connected clients. =cut