Prüft portierte Rufnummern, ob diese korrekt gerouted werden. Dazu ist ein Trunk zu einem anderen Telekommunikationsanbieter notwendig. Über dessen Trunk wird geprüft, ob die Rufnummer, am eigenen Asterisk-Server ankommen.
Find a file
Michael Rack aaedd5a520 MQTT-Integration hinzugefügt
- MQTT optional eingebaut (`--mqtt-server` aktiviert; Connect muss vor Teststart erfolgreich sein)
- MQTT-Publishes für Login/Originate/Events/Delay/Error sowie zusätzliche Lifecycle-Events ergänzt
- Topic-Template erweitert um Placeholder: `running_programm_pid`, `action`, `rc_id/RC_ID`, `rc_id_wts/RC_ID_wts`, `mqtt_client_id`, `mqtt_client_id_wts`
- Platzhalter unterstützen `{...}` und `${...}`; `*_wts` fügt `/` nur bei nicht-leerem Wert hinzu
- Payloads um Prozess-/Client-ID-Infos ergänzt
- README auf neuen MQTT- und CLI-Stand aktualisiert
2026-03-04 12:28:00 +01:00
.gitignore MQTT-Integration hinzugefügt 2026-03-04 12:28:00 +01:00
asterisk_reachcheck_ami.py MQTT-Integration hinzugefügt 2026-03-04 12:28:00 +01:00
README.md MQTT-Integration hinzugefügt 2026-03-04 12:28:00 +01:00

Asterisk Reachability Check via AMI

Python-Skript zur automatisierten Erreichbarkeitsprüfung von Rufnummern über Asterisk AMI.

Das Skript liest Rufnummernbereiche aus einer CSV-Datei, leitet daraus die tatsächlich zu wählende Zielrufnummer ab, startet die Calls sequentiell über AMI Originate und schreibt das Ergebnis in eine Output-CSV.

Die Erkennung einer erfolgreich erreichbaren Nummer erfolgt dabei über einen eingehenden Rückruf mit einer bestimmbaren Absenderrufnummer.

Funktionsweise

Für jede Zeile der Input-CSV werden die Felder Rufnummer Start und Rufnummer Ende ausgewertet.

Aus der Differenz wird die zu wählende Zielrufnummer abgeleitet:

  • Ende - Start <= 10 -> komplette Startnummer wählen
  • Ende - Start > 10 und <= 100 -> letzte Stelle der Startnummer entfernen
  • Ende - Start > 100 und <= 1000 -> letzte zwei Stellen der Startnummer entfernen
  • jeder andere Wert -> Zeile wird als invalid_input markiert

Beispiele:

  • +49865412345 bis +49865412345 -> wählt +49865412345
  • +49865412300 bis +49865412399 -> wählt +4986541230
  • +49865412000 bis +49865412999 -> wählt +498654120

Warum sequentiell?

Das Skript verarbeitet bewusst immer nur einen Call gleichzeitig.

Der Grund: Die erfolgreiche Erreichbarkeit wird über einen eingehenden Rückruf mit einer festen CLI erkannt. Bei parallelen Calls wäre die Zuordnung der Rückrufe zu den gerade geprüften Zielrufnummern nicht mehr eindeutig. Zudem haben die meisten Anbieter Call-Throttling und erlauben keine schnellen oder gleichzeitige Anrufe um SPAM-Anrufe zu bekämpfen.

Voraussetzungen

  • Python 3.9+
  • Asterisk mit aktiviertem AMI
  • Ein AMI-User mit Berechtigung für Login, Originate und Events
  • Ein funktionierender Dialplan-Context porting-check-outbound
  • Ein eingehender Context, der bei Rückrufen ein UserEvent(ReachcheckInbound,...) auslöst

Standardmäßig werden keine externen Python-Pakete benötigt.

Wenn MQTT genutzt werden soll, wird zusätzlich benötigt:

  • paho-mqtt (pip install --user paho-mqtt)

Dialplan

Outbound-Context

Der Context porting-check-outbound muss vorhanden sein und den eigentlichen Dial ausführen:

[porting-check-outbound]
exten => s,1,NoOp(Reachcheck ${RC_ID} -> ${TARGET})
  same => n,Set(__RC_ID=${RC_ID})
  same => n,Set(__TARGET=${TARGET})
  same => n,Set(__GATEWAY=${GATEWAY})
  same => n,Dial(SIP/${TARGET}@${GATEWAY}$,2)
  same => n,UserEvent(ReachcheckResult,RC_ID:${RC_ID},Target:${TARGET},DialStatus:${DIALSTATUS})
  same => n,Hangup()

Wichtig:

  • gewählt wird über SIP/${TARGET}@${GATEWAY}
  • das Klingeln ist auf 2 Sekunden begrenzt
  • nach dem Dial() wird DIALSTATUS per UserEvent() zurückgegeben

Inbound-Context

Der eingehende Rückruf muss anhand der CLI mit der raus gereufen werden erkannt werden:

[incoming-from-other-peer]
exten => _X.,1,NoOp(Inbound ${CALLERID(num)} -> ${EXTEN})
  same => n,Set(CID=+498654123456)
  same => n,GotoIf($["${CALLERID(num)}"="${CID}"]?hit:pass)
  same => n(hit),UserEvent(ReachcheckInbound,Caller:${CALLERID(num)},Exten:${EXTEN})
  same => n,Answer()
  same => n,Wait(1)
  same => n,Hangup()
  same => n(pass),Return()

Input-CSV

Das Skript kann CSV-Dateien mit oder ohne Header lesen.

Empfohlenes Format

Semikolon-getrennt mit Header:

Rufnummer Start;Rufnummer Ende
+49865412345;+49865412345
+49865412400;+49865412499
+49865413000;+49865413999

Unterstützte Formate

  • Trennzeichen werden automatisch erkannt: ;, ,, Tab oder |
  • UTF-8 und UTF-8 mit BOM werden unterstützt
  • Header werden automatisch erkannt
  • Falls kein Header vorhanden ist, werden einfach die ersten zwei Spalten verwendet

Hinweise zu Rufnummern

Unterstützt:

  • 49865412345
  • +49865412345

Nicht empfohlen:

  • +49 8654 12345
  • +49-8654-12345

Für die Differenzberechnung werden nur die Ziffern ausgewertet. Ein führendes + bleibt in der gewählten Zielrufnummer erhalten.

Output-CSV

Die Ergebnisdatei wird als Semikolon-CSV geschrieben und enthält pro Zeile unter anderem:

  • rownum
  • start_raw
  • end_raw
  • target
  • rule
  • numbersCount
  • rc_id
  • originate_response
  • originate_message
  • outbound_dialstatus
  • hangup_cause
  • hangup_cause_txt
  • inbound_seen
  • inbound_cli
  • inbound_exten
  • final_status
  • ts_start
  • ts_outbound_result
  • ts_end

Typische final_status-Werte

  • reachable
  • not_reachable:NOANSWER
  • not_reachable:BUSY
  • not_reachable:CHANUNAVAIL
  • not_reachable:CONGESTION
  • ambiguous_answer_without_callback
  • timeout_no_outbound_result
  • invalid_input:unsupported_numbersCount_...
  • originate_error:...
  • originate_rejected:...

Konfiguration

Die meisten Einstellungen können über Umgebungsvariablen oder CLI-Parameter gesetzt werden.

Umgebungsvariablen

export AMI_HOST=127.0.0.1
export AMI_PORT=5038
export AMI_USERNAME=meinamiuser
export AMI_SECRET=meinpasswort
export CALLBACK_CLI=+498654123456
export ORIGINATE_CHANNEL='Local/s@porting-check-outbound'
export MQTT_SERVER=127.0.0.1
export MQTT_PORT=1883
export MQTT_TOPIC_PREFIX='number_port_reachable_checker/{mqtt_client_id_wts}{running_programm_pid}/{rc_id_wts}{action}'

Wichtige Default-Werte

  • AMI_HOST=127.0.0.1
  • AMI_PORT=5038
  • ORIGINATE_CHANNEL=Local/s@porting-check-outbound
  • CALLBACK_CLI=+498654123456
  • POST_RESULT_WAIT_SEC=12
  • CALL_OVERALL_TIMEOUT_SEC=45
  • INTER_CALL_DELAY_SEC=0.5
  • ORIGINATE_TIMEOUT_MS=30000
  • MQTT_SERVER= (leer = MQTT aus)
  • MQTT_PORT=1883
  • MQTT_TOPIC_PREFIX=number_port_reachable_checker/{mqtt_client_id_wts}{running_programm_pid}/{rc_id_wts}{action}

Aufruf

Einfacher Start über Umgebungsvariablen

python3 asterisk_reachcheck_ami.py input.csv output.csv

Start mit expliziten Parametern

python3 asterisk_reachcheck_ami.py input.csv output.csv \
  --host 127.0.0.1 \
  --port 5038 \
  --username meinamiuser \
  --secret meinpasswort \
  --callback-cli +498654123456 \
  --originate-channel 'Local/s@porting-check-outbound'

Debug-Ausgabe aktivieren

DEBUG=1 python3 asterisk_reachcheck_ami.py input.csv output.csv

Start mit MQTT

python3 asterisk_reachcheck_ami.py input.csv output.csv \
  --mqtt-server 127.0.0.1 \
  --mqtt-port 1883 \
  --mqtt-topic-prefix 'number_port_reachable_checker/{mqtt_client_id_wts}{running_programm_pid}/{rc_id_wts}{action}'
  --mqtt-client-id "client-$(hostname)-$(date +%s)"

Hinweis:

  • Wenn --mqtt-server gesetzt ist, muss die Verbindung zum MQTT-Broker vor Teststart erfolgreich sein.
  • Ohne --mqtt-server bleibt MQTT komplett deaktiviert.

CLI-Parameter

usage: asterisk_reachcheck_ami.py [-h] [--host HOST] [--port PORT]
                                        [--username USERNAME] [--secret SECRET]
                                        [--callback-cli CALLBACK_CLI]
                                        [--originate-channel ORIGINATE_CHANNEL]
                                        [--originate-context ORIGINATE_CONTEXT]
                                        [--originate-exten ORIGINATE_EXTEN]
                                        [--originate-priority ORIGINATE_PRIORITY]
                                        [--originate-timeout-ms ORIGINATE_TIMEOUT_MS]
                                        [--originate-callerid ORIGINATE_CALLERID]
                                        [--post-result-wait POST_RESULT_WAIT]
                                        [--overall-timeout OVERALL_TIMEOUT]
                                        [--inter-call-delay INTER_CALL_DELAY]
                                        [--mqtt-server MQTT_SERVER]
                                        [--mqtt-port MQTT_PORT]
                                        [--mqtt-username MQTT_USERNAME]
                                        [--mqtt-password MQTT_PASSWORD]
                                        [--mqtt-client-id MQTT_CLIENT_ID]
                                        [--mqtt-qos {0,1,2}]
                                        [--mqtt-retain]
                                        [--mqtt-topic-prefix MQTT_TOPIC_PREFIX]
                                        [--mqtt-connect-timeout MQTT_CONNECT_TIMEOUT]
                                        input_csv output_csv

MQTT-Topics und Actions

Topic-Prefix ist frei konfigurierbar über --mqtt-topic-prefix.

Default:

  • number_port_reachable_checker/{mqtt_client_id_wts}{running_programm_pid}/{rc_id_wts}{action}

Platzhalter:

  • {running_programm_pid}: PID des laufenden Prozesses
  • {action}: Aktion
  • {rc_id} oder {RC_ID}: RC-ID, falls vorhanden
  • {rc_id_wts} oder {RC_ID_wts}: RC-ID mit / am Ende; nur wenn RC_ID != ''
  • {mqtt_client_id}: RAW-Wert aus --mqtt-client-id (kann leer sein)
  • {mqtt_client_id_wts}: wie mqtt_client_id, aber mit / am Ende; nur wenn mqtt_client_id != ''

Zusätzlich unterstützt das Skript auch ${...}-Syntax, z. B. ${mqtt_client_id_wts}${action}.

Beispiel mit --mqtt-client-id checker-0001:

  • ${mqtt_client_id} -> checker-0001
  • ${mqtt_client_id_wts} -> checker-0001/

Beispiel mit RC_ID=abc-123:

  • ${RC_ID} -> abc-123
  • ${RC_ID_wts} -> abc-123/

Actions:

  • login
  • login_succeeded
  • originate
  • originate_action_response
  • event_reachCheckInbound
  • event_dialEnd
  • delay
  • error
  • startup
  • event_reachCheckResult
  • call_result
  • stopping
  • finished
  • shutdown

Beispielablauf

  1. Skript liest eine Zeile aus der Input-CSV
  2. Zielrufnummer wird aus Start/Ende abgeleitet
  3. Das Skript startet einen Originate auf Local/s@porting-check-outbound
  4. Der Dialplan wählt SIP/${TARGET}@${GATEWAY}
  5. Asterisk sendet UserEvent(ReachcheckResult,...)
  6. Optional trifft ein eingehender Rückruf mit CLI +498654123456 ein
  7. Das Skript schreibt sofort eine Ergebniszeile in die Output-CSV
  8. Danach wird die nächste Zeile verarbeitet

Typische Stolperfallen

1. AMI verbindet, aber Login funktioniert nicht

Prüfen:

  • Host, Port, Username, Secret korrekt?
  • AMI in manager.conf aktiviert?
  • Firewall offen?
  • Benutzer darf Events empfangen?

2. Calls starten, aber es kommt nie reachable

Prüfen:

  • wird im Inbound-Context wirklich UserEvent(ReachcheckInbound,...) ausgelöst?
  • stimmt die erwartete CLI +498654123456 exakt?
  • kommt der Rückruf über den richtigen Peer/Context herein?
  • ist die Wartezeit --post-result-wait lang genug?

3. DialStatus=ANSWER, aber kein Rückruf

Dann setzt das Skript den Status auf:

  • ambiguous_answer_without_callback

Das bedeutet: Der Outbound-Vorgang wurde als beantwortet gesehen, aber der erwartete Rückruf wurde nicht erkannt.

4. Eingabedaten werden als invalid_input markiert

Dann passt meist die Differenzregel nicht. Unterstützt werden nur:

  • 1 bis 10
  • 11 bis 100
  • 101 bis 1000

Sicherheitshinweis

AMI erlaubt aktive Steuerung von Asterisk. Das Skript sollte nur mit einem AMI-Benutzer betrieben werden, der die wirklich benötigten Rechte hat, und nach Möglichkeit nur über ein internes Netz oder 127.0.0.1 erreichbar sein.

Lizenz

Interne Nutzung / projektspezifisch.