Setting Up a Hub-and-Spoke AllStarLink Network

This is a practical walkthrough for linking multiple AllStarLink repeater nodes through a single public hub node using a hub-and-spoke topology. The goal is to have two (or more) repeater nodes connect privately to a central hub, with the hub being the only publicly-listed node. Audio from any repeater is distributed to all connected nodes.

Configuring the AllStarLink -> Repeater interface will be covered in another article which will be linked here when complete.

This was written based on a real working deployment connecting two 220 MHz repeaters (W3PGH and W3EXW) for the NHARC 220 MHz repeater network, running ASL3 3.8.3 on Raspberry Pi hardware with a Debian-hosted hub.


Topology

[W3PGH repeater Pi] ──┐
node 1998 (private) ├──► [Hub node 29370] ◄── public connections
[W3EXW repeater Pi] ──┘ pa220.nharc.org
node 1999 (private)

  • Hub node: Public-facing, no RF, runs on a remote Debian server. This is the only node listed in the AllStar directory.
  • Spoke nodes: Private/unlisted, each running on a Raspberry Pi connected directly to a repeater. They initiate a persistent outbound connection to the hub at startup.

Prerequisites

  • Two or more Raspberry Pis running ASL3, each already configured and working with their respective repeaters
  • A remote server (VPS or dedicated) with Debian 12, root access, and ASL3 installed
  • UDP port 4569 open/reachable on the hub server (IAX2)
  • ASL3 version 3.8.3 or later on all nodes — earlier versions have a critical bug where rpt cmd, rpt fun, and startup_macro silently fail due to an ao2_container_dup assertion error in app_rpt.so

Upgrade to ASL3 3.8.3

As of this writing, 3.8.3 is in the ASL3 beta repo. Enable it on each machine:

sudo nano /etc/apt/sources.list.d/allstarlink.list

Uncomment the beta line:

deb [signed-by=/etc/apt/keyrings/allstarlink.gpg] https://repo.allstarlink.org/public bookworm main beta

Then upgrade:

sudo apt update
sudo apt install asl3-asterisk asl3-asterisk-modules asl3-asterisk-config
sudo systemctl restart asterisk

Do this on all three machines before proceeding.


Node Number Plan

Repurpose your existing registered node number for the hub. The spoke nodes use private (unregistered) node numbers in the 1000–1999 range — these are never routed through the AllStar network and are invisible to the outside world.

RoleNodeLocation
Hub29370pa220.nharc.org (Debian server)
W3PGH spoke1998Raspberry Pi at repeater site
W3EXW spoke1999Raspberry Pi at repeater site

If you’re repurposing a node number from one of the Pi nodes (as we did here — 29370 originally lived on the W3PGH Pi), log into allstarlink.org and update the server IP for that node to point to your hub server before proceeding.


Hub Node Configuration (Debian server)

/etc/asterisk/rpt.conf

Add or replace the node stanza. The hub has no RF interface — rxchannel=dahdi/pseudo and duplex=0.

[29370]
rxchannel = dahdi/pseudo
duplex = 0
idtime = 0
politeid = 0
idrecording =
idtalkover =
callsign =
hangtime = 1000
althangtime = 4000
totime = 180000
accountcode = RADIO
linktolink = no
nounkeyct = 1
holdofftelem = 1
telemdefault = 1
telemdynamic = 1
lnkactenable = 1

In the [nodes] section, add entries for both spokes:

[nodes]
29370 = [email protected]/29370,NONE
1998 = iax2/[email protected]/1998,NONE
1999 = iax2/[email protected]/1999,NONE

/etc/asterisk/iax.conf

Add a peer account the spokes will authenticate against. The host=dynamic means the hub doesn’t need to know the spokes’ IPs in advance.

[asl]
type = friend
auth = md5
secret = YourSharedSecret
host = dynamic
disallow = all
allow = ulaw
context = radio-secure
requirecalltoken = no

Replace YourSharedSecret with a password of your choosing. You’ll use the same value on the spoke nodes.

/etc/asterisk/extensions.conf

Ensure the radio-secure context routes to app_rpt:

[radio-secure]
exten => _XXXX!,1,Set(NODENUM=${CALLERID(num)})
same => n,rpt(${EXTEN})

The default ASL3 extensions.conf already has a suitable radio-secure context — verify it exists and handles incoming connections correctly.

Firewall

Open UDP 4569 for IAX2:

# ufw
ufw allow 4569/udp

# or iptables
iptables -A INPUT -p udp –dport 4569 -j ACCEPT


Spoke Node Configuration (Raspberry Pis)

The configuration is identical for each spoke — just swap the node number. These steps show node 1998 (W3PGH); repeat for 1999 (W3EXW).

/etc/asterisk/rpt.conf

In the [nodes] section, add your local node and the hub:

[nodes]
1998 = [email protected]/1998,NONE
29370 = [email protected]/29370,NONE

In the node stanza, key settings are duplex=2, public=0 (unlisted), and startup_macro to auto-connect at boot. Also uncomment the *813 permanent connect command which is required for startup_macro to work:

813 = ilink,13 ; Permanently connect specified link — transceive

Then in your [1998](node-main) stanza:

[1998](node-main)
rxchannel = Radio/1998 ; keep whatever was working before
duplex = 2
idtime = 0
politeid = 0
idrecording =
idtalkover =
callsign =
public = 0
startup_macro = pppppppppppppppppppp*81329370

The p characters are 500ms pauses each — 20 of them gives a 10-second delay before attempting the connection, which ensures the network stack is fully up before IAX2 tries to dial out. *813 is the permanent transceive connect command, followed by the hub node number.

/etc/asterisk/iax.conf

Add a peer definition pointing to the hub:

[pa220]
type = friend
auth = md5
secret = YourSharedSecret
host = pa220.nharc.org
disallow = all
allow = ulaw
context = radio-secure
requirecalltoken = no

Use the same YourSharedSecret as on the hub.


Startup and Verification

Start the hub first, then restart each spoke. The spokes will initiate the connection at startup via startup_macro.

# On the hub
systemctl restart asterisk

# On each spoke Pi
sudo systemctl restart asterisk

After about 15–20 seconds, verify both sides:

# On the hub
asterisk -rx “rpt lstats 29370”

# On a spoke
sudo asterisk -rx “rpt lstats 1998”

A working connection looks like:

NODE PEER RECONNECTS DIRECTION CONNECT TIME CONNECT STATE
—- —- ———- ——— ———— ————-
1998 66.207.128.40 0 IN 00:00:03:722 ESTABLISHED
1999 71.245.177.59 0 IN 00:00:03:763 ESTABLISHED

Both spokes should show ESTABLISHED. The hub should show both spokes connected IN; each spoke should show the hub connected OUT.


Expanding to Additional Repeaters

Adding a third (or fourth) repeater to the network is straightforward:

  1. Assign it a private node number (e.g., 2000)
  2. Configure it identically to the existing spokes, pointing startup_macro at the hub
  3. Add a [nodes] entry on the hub for the new node
  4. No changes needed on existing spokes — the hub handles distribution

Troubleshooting Notes

rpt cmd / rpt fun crashes with ao2_container_dup assertion — this is a bug in ASL3 prior to 3.8.3. Upgrade to 3.8.3 from the beta repo.

startup_macro silently does nothing — same root cause as above. Also verify that the *813 ilink command is uncommented in rpt.conf; it’s commented out by default in ASL3 and startup_macro won’t fire without it.

Packets reach the hub but IAX2 drops them with “Silently dropping frame without existent call number” — usually an auth mismatch between the [asl] peer on the hub and the [pa220] peer on the spoke. Verify the secret= values match exactly on both sides.

Node shows in rpt localnodes but rpt stats returns nothing — the node loaded but isn’t fully active, usually because the USB radio interface isn’t coming up. Check sudo asterisk -rx "radio show settings" and verify the USB device is present with lsusb.

DNS lookup for hub node returns wrong IP — if you repurposed an existing node number, update the server IP in your allstarlink.org account. Verify with dig 29370.nodes.allstarlink.org and dig pa220.nharc.org — they should resolve to the same address.

W3EXW Echolink Setup

Introduction

I’ve been working on setting up an Echolink node for the North Hills Amateur Radio club W3EXW repeater on 147.090MHz in the Pittsburgh area. This blog post is a record primarily for myself in case I need to do this again (or anyone else in the club if I accidentally touch the finals on my TS-820s without discharging them first) as well as hopefully some help for anyone else searching on the web trying to the same thing with the same hardware.

Bill Of materials

Wiring the URI to the BCM-144

The URI connects with a DB25 and the BCM-144 has a D-SUB DB15 data port on the back. The wire connections between these two is as follows:

URI Pin #BCM Pin #URI Pin DescriptionBCM-144 Pin Description
28GPIO1 – General Purpose input or outputEXT_PTT – External Push to Talk
810COR_DET – receive (carrier operated relay) detectCOS/COS – Squelch output
94MIC_IN – Direct, low level input to CM119Audio Output 500mV
105LEFT_OUTEXT_MIC – Audio input (5kΩ)
1915GND – GroundGND – Ground
GNDGNDGroundGround

Note that Pin 10 on my URI is listed as LEFT_OUT, but the documentation on DMK’s website lists it as GPIO8. It is possible I have an older or different version of the URI than is documented on their page.

Configuring SvxLink

The SvxLink documentation for the svxlink.conf was followed but I will call out a few configuration options I had to discover by looking at the code (or in some cases trial and error):

/dev/hidraw0

In order to use the URI for carrier detection and PTT, direct access needs to be granted to the /dev/hidraw0 device (this device may be different on your system if you have more than one USB Human Interface Device). By default, a user account will not have filesystem level access to this file, even if in the input group (as your svxlink linux account should be). To remedy this create a file called /etc/udev/rules.d/50-hid.rules and add the following line to it:

KERNEL==”hidraw*”, GROUP=”input”, MODE=”0666″

Then either reboot or enter the command “sudo udevadm trigger” to grant permission.

Rx1

In the [Rx1] block of the svxlink.conf file, you should set your AUDIO_DEV device to point to your alsa sound card provided by the URI. It will most likely be alsa:plughw:2 (0 being built in audio device and 1 being the Raspberry PI’s HDMI audio device) but you can validate this by running the “aplay -l” command to display sound devices. Look for :

card 2: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0

I modified the following settings:

ParameterValueDescription
AUDIO_DEValsa:plughw:2URI Sound Card interface from alsa sound subsystem
SQL_DETHIDRAWUse URI Human Interface Raw device for squelch open detection
HID_DEVICE/dev/hidraw0URI device name (see note above for permissions)
HID_SQL_PINVOL_DNThis was trial and error to see which pin HID considered to be the squelch detection pin. Default option was VOL_UP

Tx1

In [Tx1] I modified the following settings:

ParameterValueDescription
AUDIO_DEValsa:plughw:2URI Sound Card interface from alsa sound subsystem
PTT_TYPEHidrawUse URI Human Interface Raw device to trigger PTT on the transceiver (note: note sure why the case difference between the SQL_DET option in [Rx1] but that is how the SvxLink documentation lists it)
HID_DEVICE/dev/hidraw0URI device name (see note in Rx1 section for permissions)
HID_PTT_PIN!GPIO1The Pin used to trigger PTT. Note that the ! in front of the pin label inverts the behavior, which is necessary for the URI.

The URI has Pin 1 defined as the PTT pin, but I was unable to figure out how to reference this pin using SvxLink’s HID_PTT_PIN parameter. Switching to use GPIO1 however worked fine once I figured out that the open/close behavior is inverted.

ModuleEchoLink.conf

I set the USE_GSM_ONLY=1 since this is a low powered RaspberryPi and I do not want it switching to the more resource hungry Speex codec if another SvxLink client connects.
Other settings were configured as per SvxLink documentation for the Echolink account or left as defaults for now.