Compare commits

...

2 Commits

Author SHA1 Message Date
c49daadc5f Updates now that we have updated our_address() to differentiate public/mailer advertised addresses with all our addresses
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-15 09:35:35 +10:00
b59317871a Added tool to list node addresses and the address we use,
Fix address edit which reactivated the address,
Fix correct icons when using address merge,
We only advertise validated addresses and use validated addresses for routing,
Show our address on known AKA screen
2025-04-15 09:32:50 +10:00
12 changed files with 159 additions and 29 deletions

View File

@ -0,0 +1,64 @@
<?php
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use App\Models\Address;
class AddressCheckNode extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'debug:address:check:nodes'
.' {ftn? : FTN}'
.' {--N|node : Node Order}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check all addresses we use for nodes';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle(): int
{
$ao = NULL;
if ($this->argument('ftn')) {
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao) {
$this->error('FTN not found: ' .$this->argument('ftn'));
return self::FAILURE;
}
$this->info('our address:'.our_address($ao)->ftn);
return self::SUCCESS;
}
$this->table(['System','Node','Ours'],
our_nodes($ao ? $ao->domain : NULL)
->sortBy(fn($item)=>$this->option('node')
? sprintf('%s:%s',$item->system->name,$item->domain->name)
: sprintf('%s',$item->domain->name))
->map(fn($item)=>
[
'System'=>$item->system->name,
'Node'=>$item->ftn.' '.($item->echoareas->count() ? '^' : '').($item->fileareas->count() ? '*' : ''),
'Ours'=>our_address($item)?->ftn,
]));
return self::SUCCESS;
}
}

View File

@ -25,10 +25,11 @@ class ZoneCheck extends Command
$this->warn('Zone: '.$zo->zone_id); $this->warn('Zone: '.$zo->zone_id);
$this->info(sprintf('- Our address(es): %s',our_address($do)->pluck('ftn4d')->join(','))); $this->info(sprintf('- Our address(es): %s',our_address($do)->pluck('ftn4d')->join(',')));
$this->table(['id','ftn','role','parent','children','downlinks','uplink','send from','region_id','system','notes'], $this->table(['id','region_id','ftn','role','parent','children','downlinks','uplink','send from','system','notes'],
$zo->addresses()->FTN()->active()->with(['system','nodes_hub'])->get()->transform(function($item) { $zo->addresses()->FTN()->active()->with(['system','nodes_hub'])->get()->transform(function($item) {
return [ return [
'id'=>$item->id, 'id'=>$item->id,
'region_id'=>$item->region_id,
'ftn'=>$item->ftn4d, 'ftn'=>$item->ftn4d,
'role'=>$item->role_name, 'role'=>$item->role_name,
'parent'=>$item->parent()?->ftn4d, 'parent'=>$item->parent()?->ftn4d,
@ -36,7 +37,6 @@ class ZoneCheck extends Command
'downlinks'=>$item->downlinks()->count(), 'downlinks'=>$item->downlinks()->count(),
'uplink'=>($x=$item->uplink())?->ftn4d, 'uplink'=>($x=$item->uplink())?->ftn4d,
'send from'=>$x ? our_address($item->uplink())?->ftn4d : '', 'send from'=>$x ? our_address($item->uplink())?->ftn4d : '',
'region_id'=>$item->region_id,
'system'=>$item->system->name, 'system'=>$item->system->name,
'notes'=>$item->isRoleOverride() ? 'Role Override' : '', 'notes'=>$item->isRoleOverride() ? 'Role Override' : '',
]; ];

View File

@ -98,6 +98,9 @@ class SystemController extends Controller
$oo = Address::findOrNew($request->validated('submit')); $oo = Address::findOrNew($request->validated('submit'));
$oo->zone_id = $request->validated('zone_id'); $oo->zone_id = $request->validated('zone_id');
$oo->security = $request->validated('security'); $oo->security = $request->validated('security');
// Only change to active if it is new
if (! $oo->exists)
$oo->active = TRUE; $oo->active = TRUE;
switch ($request->validated('action')) { switch ($request->validated('action')) {

View File

@ -194,7 +194,7 @@ class AddressIdle implements ShouldQueue
return collect(); return collect();
$age = Carbon::now()->subDays($days)->endOfDay(); $age = Carbon::now()->subDays($days)->endOfDay();
$ours = our_address($do)->pluck('ftn'); $ours = our_address($do,FALSE)->pluck('ftn');
return Address::FTN() return Address::FTN()
->ActiveFTN() ->ActiveFTN()

View File

@ -60,7 +60,7 @@ class MessageProcess implements ShouldQueue
$this->mo = unserialize(utf8_decode($this->mo)); $this->mo = unserialize(utf8_decode($this->mo));
// Load our details // Load our details
$ftns = our_address(); $ftns = our_address(NULL,FALSE);
// If we are a netmail // If we are a netmail
if ($this->mo instanceof Netmail) { if ($this->mo instanceof Netmail) {

View File

@ -115,12 +115,12 @@ class NodelistImport implements ShouldQueue
} }
$file_crc = (int)$matches[4]; $file_crc = (int)$matches[4];
$do = Domain::where('name',strtolower($matches[1] ?: $this->domain))->single(); $do = Domain::where('name',strtolower($this->domain ?: $matches[1]))->single();
if (! $do) { if (! $do) {
Log::error(sprintf('%s:! Domain not found [%s].',static::LOGKEY,strtolower($matches[1] ?: $this->domain))); Log::error(sprintf('%s:! Domain not found [%s].',static::LOGKEY,strtolower($matches[1] ?: $this->domain)));
throw new \Exception('Nodelist Domain not found: '.$this->file); throw new \Exception('Nodelist Domain not found: '.($this->domain ?: $matches[1]));
} }
$date = Carbon::createFromFormat('D, M d, Y H:i',$matches[2].'0:00'); $date = Carbon::createFromFormat('D, M d, Y H:i',$matches[2].'0:00');
@ -143,6 +143,8 @@ class NodelistImport implements ShouldQueue
elseif ($no->addresses->count()) { elseif ($no->addresses->count()) {
Log::error(sprintf('%s:! Nodelist [%s] for [%s] has existing records [%d]',self::LOGKEY,$date,$do->name,$no->addresses->count())); Log::error(sprintf('%s:! Nodelist [%s] for [%s] has existing records [%d]',self::LOGKEY,$date,$do->name,$no->addresses->count()));
// This can occur when a nodelist doesnt change, but is sent out anyway
// @todo Need to immediately fail this job
return; return;
} }
@ -291,7 +293,7 @@ class NodelistImport implements ShouldQueue
Log::info(sprintf('%s:- Processing existing address [%s] (%d)',self::LOGKEY,$ao->ftn,$ao->id)); Log::info(sprintf('%s:- Processing existing address [%s] (%d)',self::LOGKEY,$ao->ftn,$ao->id));
// If the address is linked to a user's system, or our system, we'll not process it any further // If the address is linked to a user's system, or our system, we'll not process it any further
if (our_address()->contains($ao->id)) { if (our_address(NULL,FALSE)->contains($ao->id)) {
Log::info(sprintf('%s:! Limiting update to an address belonging to me',self::LOGKEY)); Log::info(sprintf('%s:! Limiting update to an address belonging to me',self::LOGKEY));
$protect = TRUE; $protect = TRUE;
@ -563,7 +565,7 @@ class NodelistImport implements ShouldQueue
->filter(fn($item)=>(! $item->point_id)) ->filter(fn($item)=>(! $item->point_id))
->pluck('id') ->pluck('id')
->diff($no->addresses->pluck('id')) ->diff($no->addresses->pluck('id'))
->diff(our_address($do)->pluck('id')) ->diff(our_address($do,FALSE)->pluck('id'))
->diff(our_nodes($do)->pluck('id')); ->diff(our_nodes($do)->pluck('id'));
$remove = Address::whereIn('id',$remove)->get(); $remove = Address::whereIn('id',$remove)->get();

View File

@ -89,7 +89,7 @@ class PacketProcess implements ShouldQueue
} }
// Check the packet is to our address, if not we'll reject it. // Check the packet is to our address, if not we'll reject it.
if (! our_address($pkt->tftn->zone->domain)->contains($pkt->tftn)) { if (! our_address($pkt->tftn->zone->domain,FALSE)->contains($pkt->tftn)) {
Log::error(sprintf('%s:! Packet [%s] is not to our address? [%s]',self::LOGKEY,$this->filename,$pkt->tftn->ftn)); Log::error(sprintf('%s:! Packet [%s] is not to our address? [%s]',self::LOGKEY,$this->filename,$pkt->tftn->ftn));
// @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename)); // @todo Notification::route('netmail',$pkt->fftn)->notify(new UnexpectedPacketToUs($this->filename));
@ -123,7 +123,7 @@ class PacketProcess implements ShouldQueue
Log::info(sprintf('%s:- Netmail from [%s] to [%s]',self::LOGKEY,$msg->fftn->ftn,$msg->tftn?->ftn ?: $msg->set_tftn)); Log::info(sprintf('%s:- Netmail from [%s] to [%s]',self::LOGKEY,$msg->fftn->ftn,$msg->tftn?->ftn ?: $msg->set_tftn));
// If we dont have a destination, we need to bounce it, if we would be the parent of the address // If we dont have a destination, we need to bounce it, if we would be the parent of the address
if ((! $msg->tftn) && our_address()->contains(Address::newFTN($msg->set_tftn)?->parent())) { if ((! $msg->tftn) && our_address(NULL,FALSE)->contains(Address::newFTN($msg->set_tftn)?->parent())) {
Log::alert(sprintf('%s:! Netmail destination [%s] doesnt exist, bouncing back to [%s]',self::LOGKEY,$msg->set_tftn,$pkt->fftn->ftn)); Log::alert(sprintf('%s:! Netmail destination [%s] doesnt exist, bouncing back to [%s]',self::LOGKEY,$msg->set_tftn,$pkt->fftn->ftn));
Notification::route('netmail',$msg->fftn)->notify(new NetmailNoDestination($msg)); Notification::route('netmail',$msg->fftn)->notify(new NetmailNoDestination($msg));

View File

@ -764,7 +764,7 @@ class Address extends Model
public function getFTNAttribute(): string public function getFTNAttribute(): string
{ {
if (! $this->relationLoaded('zone')) if (! $this->relationLoaded('zone'))
$this->load(['zone:id,domain_id,zone_id','zone.domain:domains.id,name']); $this->load(['zone:id,domain_id,active,zone_id','zone.domain:domains.id,name,active']);
return sprintf('%s@%s',$this->getFTN4DAttribute(),$this->zone->domain->name); return sprintf('%s@%s',$this->getFTN4DAttribute(),$this->zone->domain->name);
} }
@ -1024,7 +1024,7 @@ class Address extends Model
public function downlinks(): Collection public function downlinks(): Collection
{ {
// We have no session data for this address, (and its not our address), by definition it has no children // We have no session data for this address, (and its not our address), by definition it has no children
if (! $this->is_hosted && (! our_address()->pluck('id')->contains($this->id))) if (! $this->is_hosted && (! our_address(NULL,FALSE)->pluck('id')->contains($this->id)))
return new Collection; return new Collection;
// If this system is not marked to default route for this address // If this system is not marked to default route for this address

View File

@ -184,12 +184,14 @@ final class Echomail extends Model implements Packet
Log::debug(sprintf('%s:^ Message [%d] from point address is [%d]',self::LOGKEY,$model->id,$model->fftn->point_id)); Log::debug(sprintf('%s:^ Message [%d] from point address is [%d]',self::LOGKEY,$model->id,$model->fftn->point_id));
// Make sure our sender is first in the path // Make sure our sender is first in the path
// @todo we need to capture the path for mail directly from points so we dont re-export to it.
if (($model->fftn->point_id === 0) && (! $model->isFlagSet(Message::FLAG_LOCAL)) && (! $path->contains($model->fftn_id))) { if (($model->fftn->point_id === 0) && (! $model->isFlagSet(Message::FLAG_LOCAL)) && (! $path->contains($model->fftn_id))) {
Log::alert(sprintf('%s:? Echomail adding sender to start of PATH [%s].',self::LOGKEY,$model->fftn_id)); Log::alert(sprintf('%s:? Echomail adding sender to start of PATH [%s].',self::LOGKEY,$model->fftn_id));
$path->prepend($model->fftn_id); $path->prepend($model->fftn_id);
} }
// Make sure our pktsrc is last in the path // Make sure our pktsrc is last in the path
// @todo mail directly from points may have a blank path, so we need to make one up.
if ($model->set->has('set_sender') && (! $path->contains($model->set->get('set_sender')->id)) && ($model->set->get('set_sender')->point_id === 0)) { if ($model->set->has('set_sender') && (! $path->contains($model->set->get('set_sender')->id)) && ($model->set->get('set_sender')->point_id === 0)) {
Log::alert(sprintf('%s:? Echomail adding pktsrc to end of PATH [%s].',self::LOGKEY,$model->set->get('set_sender')->ftn)); Log::alert(sprintf('%s:? Echomail adding pktsrc to end of PATH [%s].',self::LOGKEY,$model->set->get('set_sender')->ftn));
$path->push($model->set->get('set_sender')->id); $path->push($model->set->get('set_sender')->id);
@ -257,7 +259,7 @@ final class Echomail extends Model implements Packet
->addresses ->addresses
->filter(function($item) use ($model) { return $model->echoarea->can_read($item->security); }) ->filter(function($item) use ($model) { return $model->echoarea->can_read($item->security); })
->pluck('id') ->pluck('id')
->diff(our_address($model->fftn->zone->domain)->pluck('id')) ->diff(our_address($model->fftn->zone->domain,FALSE)->pluck('id'))
->diff($seenby); ->diff($seenby);
if ($exportto->count()) { if ($exportto->count()) {

View File

@ -84,14 +84,27 @@ if (! function_exists('hexstr')) {
/** /**
* Return our addresses. * Return our addresses.
*
* We have two address types:
* + a) address that we advertise to receive mail and use to package up mail (Public)
* + b) address that we accept and process mail
*
* a is a subset of b, where b might have addresses we used in the past but no longer (but mail still comes in, and we
* dont want to send out mail with those addresses anymore)
*
* Public addresses are when validated is set to true
* When determining our public addresses for a specific Address (when Address::class is passed), we only return addresses
* when security is not null
*
* If domain provided, limit the list to those within the domain - returning a Collection::class * If domain provided, limit the list to those within the domain - returning a Collection::class
* If address provided, return our address that would be used for the provided address - return Address::class * If address provided, return our address that would be used for the provided address - return Address::class
* *
* @param Domain|Address|null $o - Domain or Address * @param Domain|Address|null $o Domain or Address
* @param bool $public Return only public addresses
* @return Collection|Address|NULL * @return Collection|Address|NULL
* @throws Exception * @throws Exception
*/ */
function our_address(Domain|Address $o=NULL): Collection|Address|NULL function our_address(Domain|Address $o=NULL,bool $public=TRUE): Collection|Address|NULL
{ {
if (! Config::has('setup')) if (! Config::has('setup'))
Config::set('setup',Setup::findOrFail(config('app.id'))); Config::set('setup',Setup::findOrFail(config('app.id')));
@ -99,7 +112,7 @@ function our_address(Domain|Address $o=NULL): Collection|Address|NULL
$so = Config::get('setup'); $so = Config::get('setup');
$so->loadMissing([ $so->loadMissing([
'system:id,name,sysop,location', 'system:id,name,sysop,location',
'system.akas:addresses.id,addresses.zone_id,region_id,host_id,node_id,point_id,addresses.system_id,addresses.active,role', 'system.akas:addresses.id,addresses.zone_id,region_id,host_id,node_id,point_id,addresses.system_id,addresses.active,role,validated,security',
'system.akas.zone:id,domain_id,zone_id', 'system.akas.zone:id,domain_id,zone_id',
'system.akas.zone.domain:id,name', 'system.akas.zone.domain:id,name',
]); ]);
@ -117,20 +130,22 @@ function our_address(Domain|Address $o=NULL): Collection|Address|NULL
// Looking for addresses in the same domain, and if fido.strict, addresses that have a higher role (ie: uplink) // Looking for addresses in the same domain, and if fido.strict, addresses that have a higher role (ie: uplink)
case Address::class: case Address::class:
$filter = $so->system->akas $filter = $so->system->akas
->filter(fn($item)=>$item->zone->domain_id === $o->zone->domain_id) ->filter(fn($item)=>((! $public) || ($public && $item->validated)) && ($item->zone->domain_id === $o->zone->domain_id))
->sortBy('role_id'); ->sortBy('role_id');
// If we are looking for a specific address, and there is only 1 result, return it, otherwise return what we have
if (config('fido.strict') && ($x=$filter->filter(fn($item)=>$item->role_id <= $o->role_id)->sortBy('role_id'))->count()) if (config('fido.strict') && ($x=$filter->filter(fn($item)=>$item->role_id <= $o->role_id)->sortBy('role_id'))->count())
$filter = $x; $filter = $x;
return $filter->count() ? $filter->last()->unsetRelation('nodes_hub') : NULL; return $filter->count()
? ($filter->filter(fn($item)=>$item->region_id === $o->region_id)->count()
? $filter->last()
: $filter->first())->unsetRelation('nodes_hub')
: NULL;
// Addresses in this domain // Addresses in this domain
case Domain::class: case Domain::class:
return $so->system->akas return $so->system->akas
->filter(fn($item)=>$item->zone->domain_id === $o->id) ->filter(fn($item)=>((! $public) || ($public && $item->validated)) && ($item->zone->domain_id === $o->id));
->sortBy('role_id');
// We shouldnt get here // We shouldnt get here
default: default:
@ -140,7 +155,7 @@ function our_address(Domain|Address $o=NULL): Collection|Address|NULL
function our_hostname(Address $o): string function our_hostname(Address $o): string
{ {
$our = our_address($o->domain)->first(); $our = our_address($o->domain,FALSE)->first();
$ourhostname = $our->system->address; $ourhostname = $our->system->address;
switch ($our->role_id) { switch ($our->role_id) {

View File

@ -24,7 +24,7 @@ $user->load(['systems.akas.zone.domain.echoareas','systems.akas.echoareas']);
<div class="col-12"> <div class="col-12">
@if(($x=$user @if(($x=$user
->addresses() ->addresses()
->diff(our_address()) ->diff(our_address(NULL,FALSE))
->filter(fn($item)=>($item->point_id === 0) && ($item->zone->domain->isManaged())))->count()) ->filter(fn($item)=>($item->point_id === 0) && ($item->zone->domain->isManaged())))->count())
<h2>Hub Details for your nets</h2> <h2>Hub Details for your nets</h2>

View File

@ -112,21 +112,37 @@
@if($o->addresses->count()) @if($o->addresses->count())
<p>This system has the following addresses assigned to it:</p> <p>This system has the following addresses assigned to it:</p>
<table class="table monotable"> <table class="table monotable w-100" id="address_table">
<thead> <thead>
<tr> <tr>
<th>Address</th> <th>Address</th>
<th>Active</th> <th>Active</th>
<th>@if($o->setup)Parent @else Uplink @endif</th>
<th>Security</th> <th>Security</th>
<th colspan="2">Role</th> <th>Role</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach ($o->addresses as $oo) @foreach ($o->addresses as $oo)
<tr> <tr @class(['text-secondary'=>$oo->trashed() || (! $oo->active_domain),'inactive'=>(! $oo->trashed()) && (! $oo->active)])>
<td @class(['trashed'=>$oo->trashed(),'inactive'=>(! $oo->trashed()) && (! $oo->active)])>{{ $oo->ftn }}<span class="float-end"><data value="{{ $oo->id }}:{{ $oo->validated ? 1 : 0 }}" class="validated"><i title="@if($oo->validated)Mail flowing @else Mail held @endif" @class(['bi','bi-activity'=>$oo->validated,'bi-radioactive'=>(! $oo->validated)])></i></data></span></td> <td>{{ $oo->ftn }}<span class="float-end"><data value="{{ $oo->id }}:{{ $oo->validated ? 1 : 0 }}" class="validated"><i title="@if($oo->validated)Mail flowing @else Mail held @endif" @class(['bi','bi-activity'=>$oo->validated,'bi-radioactive'=>(! $oo->validated)])></i></data></span></td>
<td>{{ $oo->active ? 'YES' : 'NO' }}</td> <td>-{{ $oo->active ? 'Active' : 'Not Active' }}-</td>
<td>
@if($o->setup)
{{ ($oo->validated && $x=$oo->parent()) ? $x->ftn : '' }}
@else
@if($x=our_address($oo))
<!-- @todo To implement, if we enable address overriding -->
@if(true)
Auto <small class="text-secondary">[{{ $x->ftn4d }}]</small>
@else
<small>[Override]</small>
@endif
@endif
@endif
</td>
<td class="text-end">{{ $oo->security }}</td> <td class="text-end">{{ $oo->security }}</td>
<td>{{ $oo->role_name }}</td> <td>{{ $oo->role_name }}</td>
<td class="nowrap actions"> <td class="nowrap actions">
@ -614,13 +630,16 @@
@endsection @endsection
@section('page-css') @section('page-css')
@css('datatables')
<style> <style>
span.btn.btn-danger a { span.btn.btn-danger a {
color: white color: white
} }
</style> </style>
@endsection @endsection
@section('page-scripts') @section('page-scripts')
@js('datatables')
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
if (window.location.hash) { if (window.location.hash) {
@ -664,6 +683,31 @@
@endcan @endcan
@endif @endif
$('#address_table').DataTable({
paging: true,
pageLength: 25,
searching: true,
ordering: true,
order: [[1,'asc']],
conditionalPaging: {
style: 'fade',
speed: 500 // optional
},
rowGroup: {
dataSrc: [1],
},
columnDefs: [
{
targets: [1],
visible: false,
},
{
targets: [5],
orderable: false
}
],
});
$('data.validated').on('click',function(item) { $('data.validated').on('click',function(item) {
that = $(this); that = $(this);
var values = item.delegateTarget.value.split(':'); var values = item.delegateTarget.value.split(':');