now working to 1st order OK with two nodes.
[spider.git] / perl / DXProt.pm
index a7a82a7b77486b12e11c68d8e4f858ab97f42350..ba958cfad0418bad5deac0c279c5ea9f5c620527 100644 (file)
@@ -199,6 +199,19 @@ sub init
 {
        do "$main::data/hop_table.pl" if -e "$main::data/hop_table.pl";
        confess $@ if $@;
+
+       my $user = DXUser->get($main::mycall);
+       $DXProt::myprot_version += $main::version*100;
+       $main::me = DXProt->SUPER::alloc($main::mycall, 0, $user); 
+       $main::me->{here} = 1;
+       $main::me->{state} = "indifferent";
+       $main::me->{sort} = 'S';    # S for spider
+       $main::me->{priv} = 9;
+       $main::me->{metric} = 0;
+       $main::me->{pingave} = 0;
+       $main::me->{registered} = 1;
+       $main::me->{version} = $main::version;
+       $main::me->{build} = $main::build;
 }
 
 #
@@ -212,8 +225,12 @@ sub new
        # add this node to the table, the values get filled in later
        my $pkg = shift;
        my $call = shift;
-       $main::routeroot->add($call, '5000', Route::here(1)) if $call ne $main::mycall;
 
+       my $uref = Route::Node::get($call) || Route::Node->new($call);
+       $uref->here(1);
+       $uref->conf(0);
+       $uref->version(5000);
+       $main::routeroot->link_node($uref, $self);
        return $self;
 }
 
@@ -427,7 +444,7 @@ sub handle_10
        if ($ref = Route::get($to)) {
                $vref = Route::Node::get($via) if $via;
                $vref = undef unless $vref && grep $to eq $_, $vref->users;
-               $ref->dxchan->talk($from, $to, $vref ? $via : undef, $_[3], $_[6]);
+               $ref->bestdxchan->talk($from, $to, $vref ? $via : undef, $_[3], $_[6]);
                return;
        }
 
@@ -436,7 +453,7 @@ sub handle_10
        $ref = Route::get($from);
        $vref = $ref = Route::Node::get($_[6]) unless $ref; 
        if ($ref) {
-               $dxchan = $ref->dxchan;
+               $dxchan = $ref->bestdxchan;
                $dxchan->talk($main::mycall, $from, $vref ? $vref->call : undef, $dxchan->msg('talknh', $to) );
        }
 }
@@ -555,7 +572,7 @@ sub handle_11
                        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 $dxchan = $node->bestdxchan;
                                if ($dxchan && $dxchan->is_clx) {
                                        route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
                                } else {
@@ -565,7 +582,7 @@ sub handle_11
                                        $to = $_[7];
                                        $node = Route::Node::get($to);
                                        if ($node) {
-                                               $dxchan = $node->dxchan;
+                                               $dxchan = $node->bestdxchan;
                                                if ($dxchan && $dxchan->is_clx) {
                                                        route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
                                                } else {
@@ -649,7 +666,7 @@ sub handle_12
                        if ($call) {
                                my $ref = Route::get($call);
                                if ($ref) {
-                                       $dxchan = $ref->dxchan;
+                                       $dxchan = $ref->bestdxchan;
                                        $dxchan->talk($_[1], $call, undef, $_[3], $_[5]) if $dxchan != $self;
                                        return;
                                }
@@ -671,10 +688,6 @@ sub handle_16
        my $line = shift;
        my $origin = shift;
 
-       if (eph_dup($line)) {
-               dbg("PCPROT: dup PC16 detected") if isdbg('chanerr');
-               return;
-       }
 
        # general checks
        my $dxchan;
@@ -686,83 +699,34 @@ sub handle_16
                dbg("PCPROT: don't send users to $self->{call}") if isdbg('chanerr');
                return;
        }
+
        # is it me?
        if ($ncall eq $main::mycall) {
                dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr');
                return;
        }
-       my $parent = Route::Node::get($ncall); 
 
-       # if there is a parent, proceed, otherwise if there is a latent PC19 in the PC19list, 
-       # fix it up in the routing tables and issue it forth before the PC16
-       unless ($parent) {
-               my $nl = $pc19list{$ncall};
-
-               if ($nl && @_ > 3) { # 3 because of the hop count!
-
-                       # this is a new (remembered) node, now attach it to me if it isn't in filtered
-                       # and we haven't disallowed it
-                       my $user = DXUser->get_current($ncall);
-                       if (!$user) {
-                               $user = DXUser->new($ncall);
-                               $user->sort('A');
-                               $user->priv(1); # I have relented and defaulted nodes
-                               $user->lockout(1);
-                               $user->homenode($ncall);
-                               $user->node($ncall);
-                       }
+       # do we believe this call? 
+       unless ($ncall eq $self->{call} || $self->is_believed($ncall)) {
+               dbg("PCPROT: We don't believe $ncall on $self->{call}");
+               return;
+       }
 
-                       my $wantpc19 = $user->wantroutepc19;
-                       if ($wantpc19 || !defined $wantpc19) {
-                               my $new = Route->new($ncall); # throw away
-                               if ($self->in_filter_route($new)) {
-                                       my @nrout;
-                                       for (@$nl) {
-                                               $parent = Route::Node::get($_->[0]);
-                                               $dxchan = $parent->dxchan if $parent;
-                                               if ($dxchan && $dxchan ne $self) {
-                                                       dbg("PCPROT: PC19 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr');
-                                                       $parent = undef;
-                                               }
-                                               if ($parent) {
-                                                       my $r = $parent->add($ncall, $_->[1], $_->[2]);
-                                                       push @nrout, $r unless @nrout;
-                                               }
-                                       }
-                                       $user->wantroutepc19(1) unless defined $wantpc19; # for now we work on the basis that pc16 = real route 
-                                       $user->lastin($main::systime) unless DXChannel->get($ncall);
-                                       $user->put;
-                                               
-                                       # route the pc19 - this will cause 'stuttering PC19s' for a while
-                                       $self->route_pc19($origin, $line, @nrout) if @nrout ;
-                                       $parent = Route::Node::get($ncall);
-                                       unless ($parent) {
-                                               dbg("PCPROT: lost $ncall after sending PC19 for it?");
-                                               return;
-                                       }
-                               } else {
-                                       return;
-                               }
-                               delete $pc19list{$ncall};
-                       }
-               } else {
-                       dbg("PCPROT: Node $ncall not in config") if isdbg('chanerr');
-                       return;
-               }
-       } else {
-                               
-               $dxchan = $parent->dxchan;
-               if ($dxchan && $dxchan ne $self) {
-                       dbg("PCPROT: PC16 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr');
-                       return;
-               }
+       my $node = Route::Node::get($ncall);
+       unless ($node) {
+               dbg("PCPROT: Node $ncall not in config") if isdbg('chanerr');
+               return;
+       }
 
-               # input filter if required
-               return unless $self->in_filter_route($parent);
+       # dedupe only that which we potentially process
+       if (eph_dup($line)) {
+               dbg("PCPROT: dup PC16 detected") if isdbg('chanerr');
+               return;
        }
 
        my $i;
        my @rout;
+       my @new;
        for ($i = 2; $i < $#_; $i++) {
                my ($call, $conf, $here) = $_[$i] =~ /^(\S+) (\S) (\d)/o;
                next unless $call && $conf && defined $here && is_callsign($call);
@@ -779,32 +743,24 @@ sub handle_16
                        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) {
-                       my $au = $r->addparent($parent);                                        
-                       if ($r->flags != $flags) {
-                               $r->flags($flags);
-                               $au = $r;
-                       }
-                       push @rout, $r if $au;
-               } else {
-                       push @rout, $parent->add_user($call, $flags);
-               }
-               
+
+               $r = Route::User::get($call) || Route::User->new($call);
+               $r->here($here);
+               $r->conf($conf);
+               $node->lastseen($main::systime);
+
+               push @new, $node->add_user($r);
                                
                # add this station to the user database, if required
                $call =~ s/-\d+$//o;    # remove ssid for users
                my $user = DXUser->get_current($call);
                $user = DXUser->new($call) if !$user;
-               $user->homenode($parent->call) if !$user->homenode;
-               $user->node($parent->call);
+               $user->homenode($node->call) if !$user->homenode;
+               $user->node($node->call);
                $user->lastin($main::systime) unless DXChannel->get($call);
                $user->put;
        }
-       $self->route_pc16($origin, $line, $parent, @rout) if @rout;
+       $self->route_pc16($origin, $line, $node, @new) if @new;
 }
                
 # remove a user
@@ -830,36 +786,38 @@ sub handle_17
                return;
        }
 
+       # do we believe this call? 
+       unless ($ncall eq $self->{call} || $self->is_believed($ncall)) {
+               dbg("PCPROT: We don't believe $ncall on $self->{call}");
+               return;
+       }
+
        my $uref = Route::User::get($ucall);
        unless ($uref) {
                dbg("PCPROT: Route::User $ucall not in config") if isdbg('chanerr');
        }
-       my $parent = Route::Node::get($ncall);
-       unless ($parent) {
+       my $node = Route::Node::get($ncall);
+       unless ($node) {
                dbg("PCPROT: Route::Node $ncall not in config") if isdbg('chanerr');
        }                       
 
-       $dxchan = $parent->dxchan if $parent;
-       if ($dxchan && $dxchan ne $self) {
-               dbg("PCPROT: PC17 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr');
-               return;
-       }
-
-       # input filter if required and then remove user if present
-       if ($parent) {
-#              return unless $self->in_filter_route($parent);  
-               $parent->del_user($uref) if $uref;
-       } else {
-               $parent = Route->new($ncall);  # throw away
+       return unless $node && $uref;
+       
+       my @rout;
+       my @new;
+       if ($self->in_filter_route($node)) {
+               
+               if (eph_dup($line)) {
+                       dbg("PCPROT: dup PC17 detected") if isdbg('chanerr');
+                       return;
+               }
+               push @new, $node->del_user($uref);
        }
 
-       if (eph_dup($line)) {
-               dbg("PCPROT: dup PC17 detected") if isdbg('chanerr');
-               return;
-       }
+       $self->route_pc17($origin, $line, $node, $uref) if @new;
 
-       $uref = Route->new($ucall) unless $uref; # throw away
-       $self->route_pc17($origin, $line, $parent, $uref);
+       # get rid of orphaned users;
+       $_->delete for @new;
 }
                
 # link request
@@ -887,11 +845,23 @@ sub handle_18
                $self->version($_[2] / 100) if $_[2] && $_[2] =~ /^\d+$/;
                $self->user->version($self->version);
        }
+       $self->newroute( $_[1] =~ /NewRoute/ );
 
        # first clear out any nodes on this dxchannel
-       my $parent = Route::Node::get($self->{call});
-       my @rout = $parent->del_nodes;
-       $self->route_pc21($origin, $line, @rout, $parent) if @rout;
+       my $node = Route::Node::get($self->{call}) ;
+       my @rout;
+       foreach my $n ($node->nodes) {
+               next if $n eq $main::mycall;
+               next if $n eq $self->{call};
+               my $nref = Route::Node::get($n);
+               push @rout, $node->remove_route($nref, $self) if $nref;
+       } 
+       $self->route_pc21($origin, $line, @rout) if @rout;
+       for (@rout) {
+               $_->delete;
+       };
+       
+       # send the new config
        $self->send_local_config();
        $self->send(pc20());
 }
@@ -923,26 +893,8 @@ sub handle_19
                return;
        }
 
-       # if the origin isn't the same as the INTERFACE, then reparent, creating nodes as necessary
-       if ($origin ne $self->call) {
-               my $op = Route::Node::get($origin);
-               unless ($op) {
-                       $op = $parent->add($origin, 5000, Route::here(1));
-                       my $user = DXUser->get_current($origin);
-                       if (!$user) {
-                               $user = DXUser->new($origin);
-                               $user->sort('S');
-                               $user->priv(1);         # I have relented and defaulted nodes
-                               $user->lockout(1);
-                               $user->homenode($origin);
-                               $user->node($origin);
-                               $user->wantroutepc19(1);
-                       }
-                       $user->put;
-               }
-               $parent = $op;
-       }
-
+       my @new;
+       
        # parse the PC19
        for ($i = 1; $i < $#_-1; $i += 4) {
                my $here = $_[$i];
@@ -951,21 +903,20 @@ sub handle_19
                my $ver = $_[$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
                next if length $call < 3; # min 3 letter callsigns
                next if $call eq $main::mycall;
 
-               # check that this PC19 isn't trying to alter the wrong dxchan
-               my $dxchan = DXChannel->get($call);
-               if ($dxchan && $dxchan != $self) {
-                       dbg("PCPROT: PC19 from $self->{call} trying to alter wrong locally connected $call, ignored!") if isdbg('chanerr');
+               # do we believe this call? 
+               unless ($call eq $self->{call} || $self->is_believed($call)) {
+                       dbg("PCPROT: We don't believe $call on $self->{call}");
                        next;
                }
 
+               eph_del_regex("^PC(?:21\\^$call|17\\^[^\\^]+\\^$call)");
+                               
                # add this station to the user database, if required (don't remove SSID from nodes)
                my $user = DXUser->get_current($call);
                if (!$user) {
@@ -976,43 +927,17 @@ sub handle_19
                        $user->homenode($call);
                        $user->node($call);
                }
+               $user->wantroutepc19(1) unless defined $user->wantroutepc19;
 
-               my $r = Route::Node::get($call);
-               my $flags = Route::here($here)|Route::conf($conf);
-
-               # modify the routing table if it is in it, otherwise store it in the pc19list for now
-               if ($r) {
-                       my $ar;
-                       if ($call ne $parent->call) {
-                               if ($self->in_filter_route($r)) {
-                                       $ar = $parent->add($call, $ver, $flags);
-                                       push @rout, $ar if $ar;
-                               } else {
-                                       next;
-                               }
-                       }
-                       if ($r->version ne $ver || $r->flags != $flags) {
-                               $r->version($ver);
-                               $r->flags($flags);
-                               push @rout, $r unless $ar;
-                       }
-               } else {
+               my $r = Route::Node::get($call) || Route::Node->new($call);
+               $r->here($here);
+               $r->conf($conf);
+               $r->version($ver);
+               $r->lastseen($main::systime);
 
-                       # if he is directly connected or allowed then add him, otherwise store him up for later
-                       if ($call eq $self->{call} || $user->wantroutepc19) {
-                               my $new = Route->new($call); # throw away
-                               if ($self->in_filter_route($new)) {
-                                       my $ar = $parent->add($call, $ver, $flags);
-                                       $user->wantroutepc19(1) unless defined $user->wantroutepc19;
-                                       push @rout, $ar if $ar;
-                               } else {
-                                       next;
-                               }
-                       } else {
-                               $pc19list{$call} = [] unless exists $pc19list{$call};
-                               my $nl = $pc19list{$call};
-                               push @{$pc19list{$call}}, [$self->{call}, $ver, $flags] unless grep $_->[0] eq $self->{call}, @$nl;
-                       }
+               if ($self->in_filter_route($r)) {
+                       push @new, $parent->link_node($r, $self);
+                       push @rout, $r;
                }
 
                # unbusy and stop and outgoing mail (ie if somehow we receive another PC19 without a disconnect)
@@ -1023,8 +948,9 @@ sub handle_19
                $user->put;
        }
 
-
-       $self->route_pc19($origin, $line, @rout) if @rout;
+       # route out new nodes to legacy nodes
+       $self->route_pc19($origin, $line, @new) if @new;
+       $self->route_pc59('A', 0, $self->{call}, @rout) if @rout;
 }
                
 # send local configuration
@@ -1049,6 +975,13 @@ sub handle_21
        my $origin = shift;
        my $call = uc $_[1];
 
+       return if $call eq $main::mycall;  # don't allow malicious buggers to disconnect me (or ignore loops)!
+
+       unless ($call eq $self->{call} || $self->is_believed($call)) {
+               dbg("PCPROT: We don't believe $call on $self->{call}");
+               return;
+       }
+
        eph_del_regex("^PC1[679].*$call");
                        
        # if I get a PC21 from the same callsign as self then treat it
@@ -1058,43 +991,31 @@ sub handle_21
                return;
        }
 
-       # check to see if we are in the pc19list, if we are then don't bother with any of
-       # this routing table manipulation, just remove it from the list and dump it
        my @rout;
-       if (my $nl = $pc19list{$call}) {
-               $pc19list{$call} = [ grep {$_->[0] ne $self->{call}} @$nl ];
-               delete $pc19list{$call} unless @{$pc19list{$call}};
-       } else {
-                               
-               my $parent = Route::Node::get($self->{call});
-               unless ($parent) {
-                       dbg("DXPROT: my parent $self->{call} has disappeared");
-                       $self->disconnect;
-                       return;
-               }
-               if ($call ne $main::mycall) { # don't allow malicious buggers to disconnect me!
-                       my $node = Route::Node::get($call);
-                       if ($node) {
-                                               
-                               my $dxchan = DXChannel->get($call);
-                               if ($dxchan && $dxchan != $self) {
-                                       dbg("PCPROT: PC21 from $self->{call} trying to alter locally connected $call, ignored!") if isdbg('chanerr');
-                                       return;
-                               }
-                                               
-                               # input filter it
-                               return unless $self->in_filter_route($node);
-                                               
-                               # routing objects
-                               push @rout, $node->del($parent);
-                       }
-               } else {
-                       dbg("PCPROT: I WILL _NOT_ be disconnected!") if isdbg('chanerr');
-                       return;
-               }
+       my @new;
+       my $parent = Route::Node::get($self->{call});
+       unless ($parent) {
+               dbg("DXPROT: my parent $self->{call} has disappeared");
+               $self->disconnect;
+               return;
        }
+       $parent->lastseen;
 
-       $self->route_pc21($origin, $line, @rout) if @rout;
+       my $node = Route::Node::get($call);
+       if ($node) {
+               $node->lastseen($main::systime);
+               
+               # input filter it
+               return unless $self->in_filter_route($node);
+               push @rout, $node;
+               push @new, $node->link_node($parent, $self);
+       }
+
+       $self->route_pc21($origin, $line, @new) if @new;
+       $self->route_pc59('D', 0, $self->{call}, @rout) if @rout;
+
+       # get rid of orphaned nodes;
+       $_->delete for @new;
 }
                
 
@@ -1124,7 +1045,10 @@ sub handle_23
                }
        }
 
-       return if $rspfcheck and !$self->rspfcheck(1, $_[8], $_[7]);
+       # only do a rspf check on PC23 (not 27)
+       if ($pcno == 23) {
+               return if $rspfcheck and !$self->rspfcheck(1, $_[8], $_[7])
+       }
 
        # do some de-duping
        my $d = cltounix($_[1], sprintf("%02d18Z", $_[2]));
@@ -1495,6 +1419,169 @@ sub handle_51
        }
 }
 
+# New style routing handler
+sub handle_59
+{
+       my $self = shift;
+       my $pcno = shift;
+       my $line = shift;
+       my $origin = shift;
+
+       my ($sort, $hextime, $ncall) = @_[1,2,3];
+       if ($ncall eq $main::mycall) {
+               dbg("PCPROT: ignoring PC59 for me") if isdbg('chan');
+               return;
+       }
+
+       # do this once for filtering with a throwaway routing entry if a new node
+       my $fnode = Route::Node::get($ncall) || Route::new($ncall);
+       return unless $self->in_filter_route($fnode);
+
+       return if eph_dup($line);
+
+       # mark myself as NewRoute if I get a PC59
+       $self->{newroute} = 1 if $ncall eq $self->{call};
+
+       # now do it properly for actions
+       my $node = Route::Node::get($ncall) || Route::Node->new($ncall);
+       $node->newroute(1);
+
+       # find each of the entries (or create new ones)
+       my @refs;
+       for my $ent (@_[4..$#_]) {
+               next if $ent =~ /^H\d+$/;
+               
+               my ($esort, $ehere, $ecall) = unpack "A A A*", $ent;
+               my $ref;
+
+               next unless $esort && defined $ehere && $ecall;
+               
+               # create user, if required
+               my $user = DXUser->get_current($ecall);
+               unless ($user) {
+                       $user = DXUser->new($ecall);
+                       $user->sort();
+                       $user->priv(1);         # I have relented and defaulted nodes
+                       $user->lockout(1);
+                       $user->homenode($ncall);
+                       $user->node($ncall);
+               }
+               if ($esort eq 'U') {
+                       $ref = Route::User::get($ecall);
+                       unless ($ref) {
+                               # create user, if required
+                               my $user = DXUser->get_current($ecall);
+                               unless ($user) {
+                                       $user = DXUser->new($ecall);
+                                       $user->sort('U');
+                                       $user->homenode($ncall);
+                                       $user->node($ncall);
+                                       $user->put;
+                               }
+                               $ref = Route::User->new($ecall, 0); 
+                       }
+               } elsif ($esort eq 'N') {
+                       $ref = Route::Node::get($ecall);
+                       unless ($ref) {
+                               # create user, if required
+                               my $user = DXUser->get_current($ecall);
+                               unless ($user) {
+                                       $user = DXUser->new($ecall);
+                                       $user->priv(1);         # I have relented and defaulted nodes
+                                       $user->lockout(1);
+                                       $user->sort('A');
+                                       $user->homenode($ncall);
+                                       $user->node($ncall);
+                                       $user->put;
+                               }
+                               $ref = Route::Node->new($ecall, 0); 
+                       } 
+               } else {
+                       dbg("DXPROT: unknown entity type '$esort' on $ecall for node $ncall") if isdbg('chan');
+                       next;
+               }
+               $ref->here($ehere);             # might as well set this here
+               $ref->lastseen($main::systime);
+               push @refs, $ref;
+       }
+
+       # if it is a delete, disconnect all the entries mentioned
+       # from this node (which is a parent in this context).
+       my @delnode;
+       my @deluser;
+       if ($sort eq 'D') {
+               for my $ref (@refs) {
+                       next if $ref->call eq $ncall;
+                       next if $ref->call eq $main::mycall;
+                       if ($ref->isa('Route::Node')) {
+                               push @delnode, $node->unlink_node($ref, $self);
+                       } elsif ($ref->isa('Route::User')) {
+                               push @deluser, $node->del_user($ref);
+                       }
+               }
+       }
+
+       # if it is an add, connect all the entries
+       my @addnode;
+       my @adduser;
+       if ($sort eq 'A') {
+               for my $ref (@refs) {
+                       next if $ref->call eq $ncall;
+                       next if $ref->call eq $main::mycall;
+                       if ($ref->isa('Route::Node')) {
+                               my $new = $node->link_node($ref, $self);
+                               push @addnode, $new if $new;
+                       } elsif ($ref->isa('Route::User')) {
+                               push @adduser, $node->del_user($ref);
+                       }
+               }
+       }
+
+       # if it is a configure, unlink all the nodes and users that 
+       # are not in @refs but are in the node, then add all the
+       # nodes and users that are @refs but not in the node.
+       #
+       if ($sort eq 'C') {
+               my @dn;
+               my @du;
+               my @an;
+               my @au;
+               for my $r (map {Route::Node::get($_)} $node->nodes) {
+                       next unless $r;
+                       next if $r->call eq $ncall;
+                       next if $r->call eq $main::mycall;
+                       push @dn, $r unless grep $_->call eq $r->call, @refs;
+               }
+               for my $r (map {Route::User::get($_)} $node->users) {
+                       next unless $r;
+                       push @du, $r unless grep $_->call eq $r->call, @refs;
+               }
+               for my $r (@refs) {
+                       next unless $r;
+                       next if $r->call eq $ncall;
+                       next if $r->call eq $main::mycall;
+                       if ($r->isa('Route::Node')) {
+                               push @an, $r unless grep $r->call eq $_, $node->nodes;
+                       } elsif ($r->isa('Route::User')) {
+                               push @au, $r unless grep $r->call eq $_, $node->users;
+                       }
+               }
+               push @delnode, $node->unlink_node($_, $self) for @dn;
+               push @deluser, $node->del_user($_) for @du;
+               push @addnode, $node->link_node($_, $self) for @an;
+               push @adduser, $node->add_user($_) for @au;
+       }
+
+       $self->route_pc21($origin, $line, @delnode) if @delnode;
+       $self->route_pc19($origin, $line, @addnode) if @addnode;
+       $self->route_pc17($origin, $line, @deluser) if @deluser;
+       $self->route_pc16($origin, $line, @adduser) if @adduser;
+       
+       $self->route_pc59($sort, $hextime, $ncall, @refs) if @refs;
+       $_->delete for @delnode, @deluser;
+}
+       
+
 # dunno but route it
 sub handle_75
 {
@@ -1703,20 +1790,8 @@ sub send_wwv_spot
        my $line = shift;
        my @dxchan = DXChannel->get_all();
        my $dxchan;
-       my ($wwv_dxcc, $wwv_itu, $wwv_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
-       my @dxcc = Prefix::extract($_[6]);
-       if (@dxcc > 0) {
-               $wwv_dxcc = $dxcc[1]->dxcc;
-               $wwv_itu = $dxcc[1]->itu;
-               $wwv_cq = $dxcc[1]->cq;                                         
-       }
-       @dxcc = Prefix::extract($_[7]);
-       if (@dxcc > 0) {
-               $org_dxcc = $dxcc[1]->dxcc;
-               $org_itu = $dxcc[1]->itu;
-               $org_cq = $dxcc[1]->cq;                                         
-       }
-       
+       my @dxcc = ((Prefix::cty_data($_[6]))[0..2], (Prefix::cty_data($_[7]))[0..2]);
+
        # send it if it isn't the except list and isn't isolated and still has a hop count
        # taking into account filtering and so on
        foreach $dxchan (@dxchan) {
@@ -1725,7 +1800,7 @@ sub send_wwv_spot
                my $routeit;
                my ($filter, $hops);
 
-               $dxchan->wwv($line, $self->{isolate}, @_, $self->{call}, $wwv_dxcc, $wwv_itu, $wwv_cq, $org_dxcc, $org_itu, $org_cq);
+               $dxchan->wwv($line, $self->{isolate}, @_, $self->{call}, @dxcc);
        }
 }
 
@@ -1749,19 +1824,7 @@ sub send_wcy_spot
        my $line = shift;
        my @dxchan = DXChannel->get_all();
        my $dxchan;
-       my ($wcy_dxcc, $wcy_itu, $wcy_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
-       my @dxcc = Prefix::extract($_[10]);
-       if (@dxcc > 0) {
-               $wcy_dxcc = $dxcc[1]->dxcc;
-               $wcy_itu = $dxcc[1]->itu;
-               $wcy_cq = $dxcc[1]->cq;                                         
-       }
-       @dxcc = Prefix::extract($_[11]);
-       if (@dxcc > 0) {
-               $org_dxcc = $dxcc[1]->dxcc;
-               $org_itu = $dxcc[1]->itu;
-               $org_cq = $dxcc[1]->cq;                                         
-       }
+       my @dxcc = ((Prefix::cty_data($_[10]))[0..2], (Prefix::cty_data($_[11]))[0..2]);
        
        # send it if it isn't the except list and isn't isolated and still has a hop count
        # taking into account filtering and so on
@@ -1769,7 +1832,7 @@ sub send_wcy_spot
                next if $dxchan == $main::me;
                next if $dxchan == $self;
 
-               $dxchan->wcy($line, $self->{isolate}, @_, $self->{call}, $wcy_dxcc, $wcy_itu, $wcy_cq, $org_dxcc, $org_itu, $org_cq);
+               $dxchan->wcy($line, $self->{isolate}, @_, $self->{call}, @dxcc);
        }
 }
 
@@ -1813,28 +1876,13 @@ sub send_announce
 
 
        # obtain country codes etc 
-       my ($ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
-       my ($ann_state, $org_state) = ("", "");
-       my @dxcc = Prefix::extract($_[0]);
-       if (@dxcc > 0) {
-               $ann_dxcc = $dxcc[1]->dxcc;
-               $ann_itu = $dxcc[1]->itu;
-               $ann_cq = $dxcc[1]->cq;                                         
-               $ann_state = $dxcc[1]->state;
-       }
-       @dxcc = Prefix::extract($_[4]);
-       if (@dxcc > 0) {
-               $org_dxcc = $dxcc[1]->dxcc;
-               $org_itu = $dxcc[1]->itu;
-               $org_cq = $dxcc[1]->cq;                                         
-               $org_state = $dxcc[1]->state;
-       }
-
+       my @a = Prefix::cty_data($_[0]);
+       my @b = Prefix::cty_data($_[4]);
        if ($self->{inannfilter}) {
                my ($filter, $hops) = 
                        $self->{inannfilter}->it(@_, $self->{call}, 
-                                                                        $ann_dxcc, $ann_itu, $ann_cq,
-                                                                        $org_dxcc, $org_itu, $org_cq, $ann_state, $org_state);
+                                                                        @a[0..2],
+                                                                        @b[0..2], $a[3], $b[3]);
                unless ($filter) {
                        dbg("PCPROT: Rejected by input announce filter") if isdbg('chanerr');
                        return;
@@ -1853,7 +1901,8 @@ sub send_announce
        foreach $dxchan (@dxchan) {
                next if $dxchan == $main::me;
                next if $dxchan == $self && $self->is_node;
-               $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},
+                                                 @a[0..2], @b[0..2]);
        }
 }
 
@@ -1883,28 +1932,13 @@ sub send_chat
        }
        
        # obtain country codes etc 
-       my ($ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
-       my ($ann_state, $org_state) = ("", "");
-       my @dxcc = Prefix::extract($_[0]);
-       if (@dxcc > 0) {
-               $ann_dxcc = $dxcc[1]->dxcc;
-               $ann_itu = $dxcc[1]->itu;
-               $ann_cq = $dxcc[1]->cq;                                         
-               $ann_state = $dxcc[1]->state;
-       }
-       @dxcc = Prefix::extract($_[4]);
-       if (@dxcc > 0) {
-               $org_dxcc = $dxcc[1]->dxcc;
-               $org_itu = $dxcc[1]->itu;
-               $org_cq = $dxcc[1]->cq;                                         
-               $org_state = $dxcc[1]->state;
-       }
-
+       my @a = Prefix::cty_data($_[0]);
+       my @b = Prefix::cty_data($_[4]);
        if ($self->{inannfilter}) {
                my ($filter, $hops) = 
                        $self->{inannfilter}->it(@_, $self->{call}, 
-                                                                        $ann_dxcc, $ann_itu, $ann_cq,
-                                                                        $org_dxcc, $org_itu, $org_cq, $ann_state, $org_state);
+                                                                        @a[0..2],
+                                                                        @b[0..2], $a[3], $b[3]);
                unless ($filter) {
                        dbg("PCPROT: Rejected by input announce filter") if isdbg('chanerr');
                        return;
@@ -1934,7 +1968,8 @@ sub send_chat
                        }
                }
                
-               $dxchan->chat($is_ak1a ? $ak1a_line : $line, $self->{isolate}, $target, $_[1], $text, @_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq);
+               $dxchan->chat($is_ak1a ? $ak1a_line : $line, $self->{isolate}, $target, $_[1], 
+                                         $text, @_, $self->{call}, @a[0..2], @b[0..2]);
        }
 }
 
@@ -1970,42 +2005,50 @@ sub send_local_config
        my @remotenodes;
 
        dbg('DXProt::send_local_config') if isdbg('trace');
-       
-       # send our nodes
-       if ($self->{isolate}) {
-               @localnodes = ( $main::routeroot );
-               $self->send_route($main::mycall, \&pc19, 1, $main::routeroot);
+
+       if ($self->{newroute}) {
+               my @nodes = $self->{isolate} ? ($main::routeroot) : grep { $_->call ne $main::mycall && $_ != $self && !$_->{isolate} } DXChannel::get_all_nodes();
+               my @users = DXChannel::get_all_users();
+               @localnodes = map { Route::Node::get($_->{call}) } @nodes;
+               my @localusers = map { Route::User::get($_->{call}) } @users;
+               $self->send_route($main::mycall, \&pc59, @nodes+@users+4, 'C', 0, $main::mycall, $main::routeroot, @localnodes, @localusers);
        } else {
-               # 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
-
-               # send locally connected 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;
-               $self->send_route($main::mycall, \&pc19, scalar(@localnodes)+1, $main::routeroot, @localnodes);
-
-               my $node;
-               my @rawintcalls = map { $_->nodes } @localnodes if @localnodes;
-               my @intcalls;
-               for $node (@rawintcalls) {
-                       push @intcalls, $node unless grep $node eq $_, @intcalls; 
-               }
-               my $ref = Route::Node::get($self->{call});
-               my @rnodes = $ref->nodes;
-               for $node (@intcalls) {
-                       push @remotenodes, Route::Node::get($node) unless grep $node eq $_, @rnodes, @remotenodes;
-               }
-               $self->send_route($main::mycall, \&pc19, scalar(@remotenodes), @remotenodes);
-       }
-       
-       # get all the users connected on the above nodes and send them out
-       foreach $node ($main::routeroot, @localnodes, @remotenodes) {
-               if ($node) {
-                       my @rout = map {my $r = Route::User::get($_); $r ? ($r) : ()} $node->users;
-                       $self->send_route($main::mycall, \&pc16, 1, $node, @rout) if @rout && $self->user->wantsendpc16;
+               # send our nodes
+               if ($self->{isolate}) {
+                       @localnodes = ( $main::routeroot );
+                       $self->send_route($main::mycall, \&pc19, 1, $main::routeroot);
                } else {
-                       dbg("sent a null value") if isdbg('chanerr');
+                       # 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
+                       
+                       # send locally connected 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;
+                       $self->send_route($main::mycall, \&pc19, scalar(@localnodes)+1, $main::routeroot, @localnodes);
+                       
+                       my $node;
+                       my @rawintcalls = map { $_->nodes } @localnodes if @localnodes;
+                       my @intcalls;
+                       for $node (@rawintcalls) {
+                               push @intcalls, $node unless grep $node eq $_, @intcalls; 
+                       }
+                       my $ref = Route::Node::get($self->{call});
+                       my @rnodes = $ref->nodes;
+                       for $node (@intcalls) {
+                               push @remotenodes, Route::Node::get($node) unless grep $node eq $_, @rnodes, @remotenodes;
+                       }
+                       $self->send_route($main::mycall, \&pc19, scalar(@remotenodes), @remotenodes);
+               }
+               
+               # get all the users connected on the above nodes and send them out
+               foreach $node ($main::routeroot, @localnodes, @remotenodes) {
+                       if ($node) {
+                               my @rout = map {my $r = Route::User::get($_); $r ? ($r) : ()} $node->users;
+                               $self->send_route($main::mycall, \&pc16, 1, $node, @rout) if @rout && $self->user->wantsendpc16;
+                       } else {
+                               dbg("sent a null value") if isdbg('chanerr');
+                       }
                }
        }
 }
@@ -2028,7 +2071,7 @@ sub route
        my $dxchan = DXChannel->get($call);
        unless ($dxchan) {
                my $cl = Route::get($call);
-               $dxchan = $cl->dxchan if $cl;
+               $dxchan = $cl->bestdxchan if $cl;
                if (ref $dxchan) {
                        if (ref $self && $dxchan eq $self) {
                                dbg("PCPROT: Trying to route back to source, dropped") if isdbg('chanerr');
@@ -2204,7 +2247,7 @@ sub addrcmd
        $rcmds{$to} = $r;
        
        my $ref = Route::Node::get($to);
-       my $dxchan = $ref->dxchan;
+       my $dxchan = $ref->bestdxchan;
        if ($dxchan && $dxchan->is_clx) {
                route(undef, $to, pc84($main::mycall, $to, $self->{call}, $cmd));
        } else {
@@ -2231,28 +2274,18 @@ sub disconnect
        my $node = Route::Node::get($call);
        my @rout;
        if ($node) {
-               @rout = $node->del($main::routeroot);
-               
-               # and all my ephemera as well
+
+               # remove the route from this node and return a list
+               # of nodes that have become orphanned as a result. 
+               push @rout, $main::routeroot->remove_route($node, $self);
+
+               # remove all my ephemera as well
                for (@rout) {
                        my $c = $_->call;
                        eph_del_regex("^PC1[679].*$c");
                }
        }
        
-       # remove them from the pc19list as well
-       while (my ($k,$v) = each %pc19list) {
-               my @l = grep {$_->[0] ne $call} @{$pc19list{$k}};
-               if (@l) {
-                       $pc19list{$k} = \@l;
-               } else {
-                       delete $pc19list{$k};
-               }
-               
-               # and the ephemera
-               eph_del_regex("^PC1[679].*$k");
-       }
-
        # unbusy and stop and outgoing mail
        my $mref = DXMsg::get_busy($call);
        $mref->stop_msg($call) if $mref;
@@ -2260,8 +2293,12 @@ sub disconnect
        # broadcast to all other nodes that all the nodes connected to via me are gone
        unless ($pc39flag && $pc39flag == 2) {
                $self->route_pc21($main::mycall, undef, @rout) if @rout;
+               $self->route_pc59('D', 0, $main::mycall, $node);
        }
 
+       # delete all the unwanted nodes
+       $_->delete for @rout;
+       
        # remove outstanding pings
        delete $pings{$call};
        
@@ -2304,6 +2341,12 @@ sub send_route
        
        for (; @_ && $no; $no--) {
                my $r = shift;
+
+               # deal with non routing parameters
+               unless (ref $r && $r->isa('Route')) {
+                       push @rin, $r;
+                       next;
+               }
                
                if (!$self->{isolate} && $self->{routefilter}) {
                        $filter = undef;
@@ -2351,6 +2394,12 @@ sub broadcast_route
                        next if $dxchan == $main::me;
                        next unless $dxchan->isa('DXProt');
                        next if ($generate == \&pc16 || $generate==\&pc17) && !$dxchan->user->wantsendpc16;
+                       if ($dxchan->{newroute}) {
+                               next if ($generate == \&pc19 || $generate==\&pc21);
+                       } else {
+                               next if ($generate == \&pc19 || $generate==\&pc21) && !$dxchan->user->wantroutepc19;
+                               next if ($generate == \&pc59);
+                       }
  
                        $dxchan->send_route($origin, $generate, @_);
                }
@@ -2415,6 +2464,15 @@ sub route_pc50
        broadcast_route($self, $origin, \&pc50, $line, 1, @_);
 }
 
+sub route_pc59
+{
+       my $self = shift;
+       my $origin = shift;
+       my $line = shift;
+
+       broadcast_route($self, $origin, \&pc59, $line, scalar @_, @_);
+}
+
 sub in_filter_route
 {
        my $self = shift;