use Route::Node;
use strict;
-use vars qw($me $pc11_max_age $pc23_max_age
+
+use vars qw($VERSION $BRANCH);
+$VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ );
+$BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/ ) || 0;
+$main::build += $VERSION;
+$main::branch += $BRANCH;
+
+use vars qw($me $pc11_max_age $pc23_max_age $last_pc50
$last_hour $last10 %eph %pings %rcmds
%nodehops $baddx $badspotter $badnode $censorpc
$allowzero $decode_dk0wcy $send_opernam @checklist);
$baddx = new DXHash "baddx";
$badspotter = new DXHash "badspotter";
$badnode = new DXHash "badnode";
-$last10 = time;
+$last10 = $last_pc50 = time;
@checklist =
(
# remember type of connection
$self->{consort} = $line;
$self->{outbound} = $sort eq 'O';
- $self->{priv} = $user->priv || 1; # other clusters can always be 'normal' users
+ my $priv = $user->priv;
+ $priv = $user->priv(1) unless $priv;
+ $self->{priv} = $priv; # other clusters can always be 'normal' users
$self->{lang} = $user->lang || 'en';
$self->{isolate} = $user->{isolate};
$self->{consort} = $line; # save the connection type
$self->{here} = 1;
+ $self->{width} = 80;
# get the output filters
$self->{spotsfilter} = Filter::read_in('spots', $call, 0) || Filter::read_in('spots', 'node_default', 0);
$self->{wwvfilter} = Filter::read_in('wwv', $call, 0) || Filter::read_in('wwv', 'node_default', 0);
$self->{wcyfilter} = Filter::read_in('wcy', $call, 0) || Filter::read_in('wcy', 'node_default', 0);
$self->{annfilter} = Filter::read_in('ann', $call, 0) || Filter::read_in('ann', 'node_default', 0) ;
- $self->{routefilter} = Filter::read_in('route', $call, 0) || Filter::read_in('route', 'node_default', 0) ;
+ $self->{routefilter} = Filter::read_in('route', $call, 0) || Filter::read_in('route', 'node_default', 0) unless $self->{isolate} ;
# get the INPUT filters (these only pertain to Clusters)
$self->{inwwvfilter} = Filter::read_in('wwv', $call, 1) || Filter::read_in('wwv', 'node_default', 1);
$self->{inwcyfilter} = Filter::read_in('wcy', $call, 1) || Filter::read_in('wcy', 'node_default', 1);
$self->{inannfilter} = Filter::read_in('ann', $call, 1) || Filter::read_in('ann', 'node_default', 1);
- $self->{inroutefilter} = Filter::read_in('route', $call, 1) || Filter::read_in('route', 'node_default', 1);
+ $self->{inroutefilter} = Filter::read_in('route', $call, 1) || Filter::read_in('route', 'node_default', 1) unless $self->{isolate};
# set unbuffered and no echo
$self->send_now('B',"0");
$self->{pingint} = $ping;
$self->{nopings} = $user->nopings || 2;
$self->{pingtime} = [ ];
- $self->{pingave} = 0;
+ $self->{pingave} = 999;
+ $self->{lastping} = $main::systime;
# send initialisation string
unless ($self->{outbound}) {
$self->send(pc18());
- $self->{lastping} = $main::systime;
- } else {
- $self->{lastping} = $main::systime + ($self->pingint / 2);
}
+
$self->state('init');
$self->{pc50_t} = $main::systime;
my $node;
my $to = $user->homenode;
my $last = $user->lastoper || 0;
- if ($to ne $main::mycall && $send_opernam && $main::systime > $last + $DXUser::lastoperinterval && $to && ($node = Route::Node::get($to)) ) {
+ if ($send_opernam && $to && $to ne $main::mycall && $main::systime > $last + $DXUser::lastoperinterval && ($node = Route::Node::get($to)) ) {
my $cmd = "forward/opernam $spot[4]";
# send the rcmd but we aren't interested in the replies...
my $dxchan = $node->dxchan;
my ($call, $conf, $here) = $field[$i] =~ /^(\S+) (\S) (\d)/o;
next unless $call && $conf && defined $here && is_callsign($call);
next if $call eq $main::mycall;
+
+ eph_del_regex("^PC17\^$call\^$ncall");
$conf = $conf eq '*';
- my $r = Route::User::get($call);
+ # reject this if we think it is a node already
+ my $r = Route::Node::get($call);
+ my $u = DXUser->get_current($call) unless $r;
+ if ($r || ($u && $u->is_node)) {
+ dbg("PCPROT: $call is a node") if isdbg('chanerr');
+ next;
+ }
+
+ $r = Route::User::get($call);
my $flags = Route::here($here)|Route::conf($conf);
if ($r) {
$r->flags($flags);
push @rout, $r;
}
- $r->addparent($ncall);
+ $r->addparent($parent);
} else {
push @rout, $parent->add_user($call, $flags);
}
$user->put;
}
+ if (eph_dup($line)) {
+ dbg("PCPROT: dup PC16 detected") if isdbg('chanerr');
+ return;
+ }
# queue up any messages (look for privates only)
DXMsg::queue_msg(1) if $self->state eq 'normal';
my $dxchan;
my $ncall = $field[2];
my $ucall = $field[1];
+
+ eph_del_regex("^PC16.*$ncall.*$ucall");
+
if ($ncall eq $main::mycall) {
dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr');
return;
dbg("PCPROT: PC17 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr');
return;
}
+
+ my $uref = Route::User::get($ucall);
+ unless ($uref) {
+ dbg("PCPROT: Route::User $ucall not in config") if isdbg('chanerr');
+ return;
+ }
my $parent = Route::Node::get($ncall);
unless ($parent) {
dbg("PCPROT: Route::Node $ncall not in config") if isdbg('chanerr');
return;
- }
+ }
# input filter if required
return unless $self->in_filter_route($parent);
+
+ my @rout = $parent->del_user($uref);
+
+ if (eph_dup($line)) {
+ dbg("PCPROT: dup PC17 detected") if isdbg('chanerr');
+ return;
+ }
- my @rout = $parent->del_user($ucall);
$self->route_pc17($parent, @rout) if @rout;
return;
}
# first clear out any nodes on this dxchannel
my $parent = Route::Node::get($self->{call});
my @rout = $parent->del_nodes;
- $self->route_pc21(@rout, $parent);
+ $self->route_pc21(@rout, $parent) if @rout;
$self->send_local_config();
$self->send(pc20());
return; # we don't pass these on
my $conf = $field[$i+2];
my $ver = $field[$i+3];
next unless defined $here && defined $conf && is_callsign($call);
+
+ eph_del_regex("^PC(?:21\^$call|17\^[^\^]+\^$call)");
+
# check for sane parameters
$ver = 5000 if $ver eq '0000';
next if $ver < 5000; # only works with version 5 software
$user = DXUser->new($call);
$user->sort('A');
$user->priv(1); # I have relented and defaulted nodes
- $self->{priv} = 1; # to user RCMDs allowed
+ $user->lockout(1);
$user->homenode($call);
$user->node($call);
}
$user->put;
}
+ if (eph_dup($line)) {
+ dbg("PCPROT: dup PC19 detected") if isdbg('chanerr');
+ return;
+ }
+
$self->route_pc19(@rout) if @rout;
return;
}
$self->send_local_config();
$self->send(pc22());
$self->state('normal');
+ $self->{lastping} = 0;
return;
}
if ($pcno == 21) { # delete a cluster from the list
my $call = uc $field[1];
+
+ eph_del_regex("^PC1[79].*$call");
+
my @rout;
my $parent = Route::Node::get($self->{call});
unless ($parent) {
dbg("PCPROT: I WILL _NOT_ be disconnected!") if isdbg('chanerr');
return;
}
+
+ if (eph_dup($line)) {
+ dbg("PCPROT: dup PC21 detected") if isdbg('chanerr');
+ return;
+ }
+
$self->route_pc21(@rout) if @rout;
return;
}
if ($pcno == 22) {
$self->state('normal');
+ $self->{lastping} = 0;
return;
}
return;
}
- Log('DXProt', "Merge request for $field[3] spots and $field[4] WWV from $field[1]");
+ Log('DXProt', "Merge request for $field[3] spots and $field[4] WWV from $field[2]");
# spots
if ($field[3] > 0) {
if ($pcno == 39) { # incoming disconnect
if ($field[1] eq $self->{call}) {
$self->disconnect(1);
+ eph_del_regex("^PC(?:1[679]|21).*$field[1]");
} else {
dbg("PCPROT: came in on wrong channel") if isdbg('chanerr');
}
# my $ref = Route::get($call) || Route->new($call);
# return unless $self->in_filter_route($ref);
+ if ($field[3] eq $field[2]) {
+ dbg('PCPROT: invalid value') if isdbg('chanerr');
+ return;
+ }
+
# add this station to the user database, if required
my $user = DXUser->get_current($call);
$user = DXUser->new($call) if !$user;
my $to = $field[1];
my $from = $field[2];
my $flag = $field[3];
+
# is it for us?
if ($to eq $main::mycall) {
$dxchan->send($dxchan->msg('pingi', $from, $s, $ave))
} elsif ($dxchan->is_node) {
if ($tochan) {
- $tochan->{nopings} = $tochan->user->nopings || 2; # pump up the timer
+ my $nopings = $tochan->user->nopings || 2;
push @{$tochan->{pingtime}}, $t;
shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6;
- my $st;
- for (@{$tochan->{pingtime}}) {
- $st += $_;
+
+ # cope with a missed ping, this means you must set the pingint large enough
+ if ($t > $tochan->{pingint} && $t < 2 * $tochan->{pingint} ) {
+ $t -= $tochan->{pingint};
+ }
+
+ # calc smoothed RTT a la TCP
+ if (@{$tochan->{pingtime}} == 1) {
+ $tochan->{pingave} = $t;
+ } else {
+ $tochan->{pingave} = $tochan->{pingave} + (($t - $tochan->{pingave}) / 6);
}
- $tochan->{pingave} = $st / @{$tochan->{pingtime}};
+# my $st;
+# for (@{$tochan->{pingtime}}) {
+# $st += $_;
+# }
+# $tochan->{pingave} = $st / @{$tochan->{pingtime}};
+ $tochan->{nopings} = $nopings; # pump up the timer
}
}
}
}
}
} else {
+ if (eph_dup($line)) {
+ dbg("PCPROT: dup PC51 detected") if isdbg('chanerr');
+ return;
+ }
# route down an appropriate thingy
$self->route($to, $line);
}
my $t = time;
my @dxchan = DXChannel->get_all();
my $dxchan;
+ my $pc50s;
+ # send out a pc50 on EVERY channel all at once
+ if ($t >= $last_pc50 + $DXProt::pc50_interval) {
+ $pc50s = pc50($me, scalar DXChannel::get_all_users);
+ eph_dup($pc50s);
+ $last_pc50 = $t;
+ }
+
foreach $dxchan (@dxchan) {
next unless $dxchan->is_node();
next if $dxchan == $me;
-
- # send a pc50 out on this channel
- $dxchan->{pc50_t} = $main::systime unless exists $dxchan->{pc50_t};
- if ($t >= $dxchan->{pc50_t} + $DXProt::pc50_interval) {
- my $s = pc50($me, scalar DXChannel::get_all_users);
- eph_dup($s);
- $dxchan->send($s);
- $dxchan->{pc50_t} = $t;
- }
+ # send the pc50
+ $dxchan->send($pc50s) if $pc50s;
+
# send a ping out on this channel
if ($dxchan->{pingint} && $t >= $dxchan->{pingint} + $dxchan->{lastping}) {
if ($dxchan->{nopings} <= 0) {
($filter, $hops) = $self->{spotsfilter}->it(@_);
return unless $filter;
}
- send_prot_line($self, $filter, $hops, $isolate, $line)
+ send_prot_line($self, $filter, $hops, $isolate, $line);
}
sub send_prot_line
return unless $routeit;
}
if ($filter) {
- $self->send($routeit) if $routeit;
+ $self->send($routeit);
} else {
$self->send($routeit) unless $self->{isolate} || $isolate;
}
foreach $dxchan (@dxchan) {
next if $dxchan == $me;
next if $dxchan == $self && $self->is_node;
- my $routeit;
- my ($filter, $hops);
-
- $dxchan->announce($line, $self->{isolate}, $to, $target, $text, @_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq)
+ $dxchan->announce($line, $self->{isolate}, $to, $target, $text, @_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq);
}
}
my $isolate = shift;
my $to = shift;
my $target = shift;
+ my $text = shift;
my ($filter, $hops);
if ($self->{annfilter}) {
# create a list of all the nodes that are not connected to this connection
# and are not themselves isolated, this to make sure that isolated nodes
# don't appear outside of this node
- my @dxchan = grep { $_->call ne $main::mycall && $_->call ne $self->{call} } DXChannel::get_all_nodes();
+ my @dxchan = grep { $_->call ne $main::mycall && $_ != $self && !$_->{isolate} } DXChannel::get_all_nodes();
@localnodes = map { my $r = Route::Node::get($_->{call}); $r ? $r : () } @dxchan if @dxchan;
my @intcalls = map { $_->nodes } @localnodes if @localnodes;
my $ref = Route::Node::get($self->{call});
my $pc39flag = shift;
my $call = $self->call;
+ return if $self->{disconnecting}++;
+
unless ($pc39flag && $pc39flag == 1) {
$self->send_now("D", DXProt::pc39($main::mycall, $self->msg('disc1', "System Op")));
}
my $node = Route::Node::get($call);
my @rout;
if ($node) {
- @rout = $node->del_nodes; # at the next level
@rout = $node->del($main::routeroot);
}
for (; @_ && $no; $no--) {
my $r = shift;
- if ($self->{routefilter}) {
+ if (!$self->{isolate} && $self->{routefilter}) {
$filter = undef;
if ($r) {
($filter, $hops) = $self->{routefilter}->it($self->{call}, $self->{dxcc}, $self->{itu}, $self->{cq}, $r->call, $r->dxcc, $r->itu, $r->cq);
dbg("was sent a null value") if isdbg('chanerr');
}
} else {
- push @rin, $r;
+ push @rin, $r unless $self->{isolate} && $r->call ne $main::mycall;
}
}
if (@rin) {
$routeit = adjust_hops($self, $line); # adjust its hop count by node name
next unless $routeit;
}
- $self->send($routeit) if $self->{routefilter} || !$self->{isolate};
+ $self->send($routeit);
}
}
}
my $dxchan;
my $line;
- foreach $dxchan (@dxchan) {
- next if $dxchan == $self;
- next if $dxchan == $me;
- if ($dxchan->{routefilter} || !$self->{isolate}) {
- $dxchan->send_route($generate, @_)
- } else {
- dbg('DXPROT: isolated') if isdbg('chanerr');
+ unless ($self->{isolate}) {
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $self;
+ next if $dxchan == $me;
+ $dxchan->send_route($generate, @_);
}
}
}
sub eph_dup
{
my $s = shift;
+ my $r;
# chop the end off
$s =~ s/\^H\d\d?\^?\~?$//;
- return 1 if exists $eph{$s};
+ $r = 1 if exists $eph{$s}; # pump up the dup if it keeps circulating
$eph{$s} = $main::systime;
- return undef;
+ return $r;
+}
+
+sub eph_del_regex
+{
+ my $regex = shift;
+ my ($key, $val);
+ while (($key, $val) = each %eph) {
+ if ($key =~ m{$regex}) {
+ delete $eph{$key};
+ }
+ }
}
sub eph_clean
my ($key, $val);
while (($key, $val) = each %eph) {
- if ($main::systime - $val > 90) {
+ if ($main::systime - $val > 180) {
delete $eph{$key};
}
}