- 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
|
||
|---|---|---|
| .gitignore | ||
| asterisk_reachcheck_ami.py | ||
| README.md | ||
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ählenEnde - Start > 10 und <= 100-> letzte Stelle der Startnummer entfernenEnde - Start > 100 und <= 1000-> letzte zwei Stellen der Startnummer entfernen- jeder andere Wert -> Zeile wird als
invalid_inputmarkiert
Beispiele:
+49865412345bis+49865412345-> wählt+49865412345+49865412300bis+49865412399-> wählt+4986541230+49865412000bis+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,OriginateundEvents - 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()wirdDIALSTATUSperUserEvent()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:
rownumstart_rawend_rawtargetrulenumbersCountrc_idoriginate_responseoriginate_messageoutbound_dialstatushangup_causehangup_cause_txtinbound_seeninbound_cliinbound_extenfinal_statusts_startts_outbound_resultts_end
Typische final_status-Werte
reachablenot_reachable:NOANSWERnot_reachable:BUSYnot_reachable:CHANUNAVAILnot_reachable:CONGESTIONambiguous_answer_without_callbacktimeout_no_outbound_resultinvalid_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.1AMI_PORT=5038ORIGINATE_CHANNEL=Local/s@porting-check-outboundCALLBACK_CLI=+498654123456POST_RESULT_WAIT_SEC=12CALL_OVERALL_TIMEOUT_SEC=45INTER_CALL_DELAY_SEC=0.5ORIGINATE_TIMEOUT_MS=30000MQTT_SERVER=(leer = MQTT aus)MQTT_PORT=1883MQTT_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-servergesetzt ist, muss die Verbindung zum MQTT-Broker vor Teststart erfolgreich sein. - Ohne
--mqtt-serverbleibt 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 wennRC_ID != ''{mqtt_client_id}: RAW-Wert aus--mqtt-client-id(kann leer sein){mqtt_client_id_wts}: wiemqtt_client_id, aber mit/am Ende; nur wennmqtt_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:
loginlogin_succeededoriginateoriginate_action_responseevent_reachCheckInboundevent_dialEnddelayerrorstartupevent_reachCheckResultcall_resultstoppingfinishedshutdown
Beispielablauf
- Skript liest eine Zeile aus der Input-CSV
- Zielrufnummer wird aus Start/Ende abgeleitet
- Das Skript startet einen
OriginateaufLocal/s@porting-check-outbound - Der Dialplan wählt
SIP/${TARGET}@${GATEWAY} - Asterisk sendet
UserEvent(ReachcheckResult,...) - Optional trifft ein eingehender Rückruf mit CLI
+498654123456ein - Das Skript schreibt sofort eine Ergebniszeile in die Output-CSV
- 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.confaktiviert? - 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
+498654123456exakt? - kommt der Rückruf über den richtigen Peer/Context herein?
- ist die Wartezeit
--post-result-waitlang 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:
1bis1011bis100101bis1000
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.