[Info] Asterisk pjsip und NAT - nicht so einfach, wie man glaubt

gehtdoch

Mitglied
Mitglied seit
3 Feb 2019
Beiträge
379
Punkte für Reaktionen
25
Punkte
28
Weil hier immer mal wieder Probleme mit NAT und Asterisk hochgekommen sind (beim einen gehts - beim anderen nicht), wollte ich der Problematik mal auf den Grund gehen.

Das Thema "beim einen gehts, beim anderen nicht" wird ja gerne oberflächlich am VoIP-Provider festgemacht, weil es "gute" VoIP-Provider gibt, wo es eben "geht" und scheinbar schlechte VoIP-Provider gibt, wo es eben nicht geht. Ziel meiner Untersuchungen war nun, im Detail zu analysieren, warum das so ist, was oberflächlich festgestellt wird und dann fälschlicherweise zur VoIP-Providerschelte hochstilisiert wird - was aber natürlich falsch ist - soviel kann ich schon vorab sagen.

Ich selbst konnte bei meinen Untersuchungen beide oben beschriebenen Beobachtungen nachstellen. Ohne zunächst in deep zu gehen, konnte ich oberflächlich folgendes feststellen:
Mit easybell praktisch keine feststellbaren Probleme (im Schnelltest) - mit Telekom All-IP -> nichts geht.

Weitere festgestellte Abhängigkeiten: Es kommt auf die Komplexität des eigenen Systems an (multihomed (= mehrere Devices / IP-Adressen auf dem System, auf dem Asterisk läuft) oder nicht) und es kommt auf das NAT-Gateway selbst an und es kommt auf die jeweilige transport-Konfiguration an (was nicht heißt, dass es noch mehr Abhängigkeiten geben könnte, auf die ich eben nicht gestoßen bin).

Also, let's go in deep.

SIP ist bereits abseits von NAT ein komplexes Protokoll, weil es im Protokoll selbst u.a. die Verbindungsdaten für verschiedene Netzwerkverbindungen definiert: einmal die SIP-Ebene und einmal die RTP-Ebene. Wenn Asterisk kein NAT machen muss, weil es die globale IP direkt verwenden kann (weil sie auf dem System verfügbar ist), ist das die sicherste und sauberste Variante und die Wahrscheinlichkeit, dass es hier zu Problemen kommt ist fast 0 (fast deshalb, weil man sich mit einem Portfilter immer noch ins Knie schießen kann - aber das ist dann nicht mehr das Problem von Asterisk).

SIP + NAT wird nun allerdings extrem komplex, weil Asterisk ja die globale Netzwerkverbindung (IP-Adresse und Port), die für ein ordentliches SIP-Protokoll zwingend benötigt wird, nicht kennt. Jetzt gibt es für dieses recht komplexe Problem diverse Lösungsansätze (aus meiner Sicht sind das alles üble Workarounds, die mal funktionieren oder auch nicht und im Einzelnen ziemlich stark vom Zusammenspiel SIP-Client und VoIP-Provider abhängen und was eben jeweils wie im Detail unterstützt wird). Ich werde auf die sämtlichen Möglichkeiten, die es da gibt, nicht eingehen, sondern nur die Variante beleuchten, die ohne all diese Workarounds auskommt und mit (weitgehend zumindest) Asterisk Boardmitteln nachgewiesenermaßen stabil funktioniert (habe ich seit geraumer Zeit nun selbst am Laufen in einem vergleichsweise hochkomplexen System).

Alle detaillierten Aussagen beziehen sich auf eine Asterisk 18.4 / pjsip und SIP over TLS - Konfiguration, auf Basis der Konfiguration hier.

Zusätzlich werden nun die folgenden Parameter verwendet (in der Transportkonfiguration):
Code:
external_media_address=[externalhostname] oder wo man es auch immer haben mag
external_signaling_address=[externalhostname]
local_net=192.168.0.0/16 ; (wenn man nur 192.168.0.0 verwendet, anosnten eben mit einer weiteren Zeile auch noch das 10.er Netz hinterlegen oder was man eben sonst noch alles nicht Natten will).

Außerdem den dnsmgr einschalten (in der /etc/asterisk/dnsmgr.conf)
Code:
[general]
enable=yes
refreshinterval=600 ; (ich habe den Wert hier so hoch gewählt, weil ich weiter unten im Falle eines Adresswechsels das sowieso selbst mitbekomme und dann direkt steuere)

Strategie für die beiden Parameter: das ist ein Hostname, der per DNS zur korrekten IP führt. Beim IP-Adresswechsel muss dann natürlich auch die Auflösung im DNS angepasst werden - sonst wird das nichts.
Das ganze kann man beschleunigen im Rahmen eines Adresswechsels mit
Code:
/usr/sbin/asterisk -x "dnsmgr refresh"
sleep 1
/usr/sbin/asterisk -x "pjsip send register *all"


Verändert wurde der Parameter
Code:
retry_interval=20 ; (im type=registration) - oder auch kürzer
Das ist nötig, damit Asterisk schneller den zugehörigen Transport durchstartet - anders bekommt er die Umsetzung der geänderten globalen IP-Adresse nicht auf die Reihe.

Code:
force_rport=no ; (im type=endpoint)
Normalerweise werden Antworten an die Source-IP-Adresse und den Source port zurückgeschickt (force_rport=yes)
Falls an den im Via-Header definierten Port geschickt werden soll, muss force_rport=no sein.

Code:
rtp_symmetric=yes ; (im type=endpoint)
Aus Sicht von Asterik: ausgehender RTP-Strom geht an die gleiche Adresse, von der Asterisk RTP empfängt. c= und m= im eingegangenen SDP werden ignoriert. Hier kann im beschriebenen Fall auch rtp_symmetric=no verwendet werden, da die nun enthaltenen Ports im SDP ja korrekt sind.


Neuer Parameter auf Grund Patch nobind.diff - siehe angehängtes zip-File in der Transport-Konfiguration
Code:
nobind=1
Bewirkt, dass die für externe Registrierung zum VoIP-Provider verwendete Transports keine Listener anlegen - ist in pjsip so vorgesehen - in Asterisk aber nicht durchgeschleift. Der Patch bewirkt also nichts anderes, als dass eine vorgesehene sehr sinnvolle Funktionalität aus pjsip auch tatsächlich genutzt werden kann in Asterisk: nämlich einen Transport zu definieren, der keinen zugehörigen Listener hat.

Ein ausgehender Register zu einem VoIP-Provider sollte auf jeden Fall immer einen eigenen Transport bekommen, weil im Rahmen des globalen IP-Adresswechsels (als Folge eines Routerrestarts z.B.) immer der Transport durchgestartet werden muss, was auch zu einem Verlust der von intern registrierten Telefonen führt / führen kann - welche dann so lange nicht erreichbar sind bzw. auch für ausgehende Calls nicht geutzt werden können, bis sie sich wieder von selbst irgendwann nach Ablauf der ReRegister-Zeit neu registriert haben. Das will man nicht unbedingt haben. Daher Transport Client und Transport Server grundsätzlich trennen!


Warum funktioniert es nun eigentlich im Detail zum VoIP-Provider A scheinbar out of the box - zum VoIP-Provider b aber nicht? Das liegt daran, dass Asterisk in Sachen NAT ziemlich buggy ist. Asterisk nimmt es mit der Anwendung der für NAT relevanten Anpassungen nicht so genau (= inkonsistent), was dazu führt, dass in den einzelnen Request / Responses teilweise ziemlicher Müll drin steht, was bei den VoIP-Providern, bei denen es "funktioniert" schlicht und ergreifend ignoriert wird, während es bei anderen VoIP-Providern eben nicht ignoriert wird und die daher abbrechen (gut so). Wie soll man denn z.B. einen abweichenden Mediaserver definieren können, wenn der VoIP-Provider einfach die Pakete an den SIP-Server zurückschickt (weil er eben einfach die Verbindungsdaten nimmt, die er von der Netzwerkschicht bekommen hat im Rahmen des SIP-Verkehrs)? Woher soll der VoIP-Provider wissen, was nun richtig und falsch ist, wenn's mal so, mal so geschickt wird? Rate mal mit Rosenthal ist nie gut.


Wo sind die Probleme im Detail ohne die NAT-Patches?

Im SDP während SIP-Handshake
/ im ersten INVITE des Handshakes ist noch alles ok - im Zweiten INVITE (als Folge des 407 Proxy Auth required) ist es dann kaputt (=> inkonsistent - mal so, mal so - man beachte die lokale IP!):
Code:
o=- 1629177632 1629177632 IN IP4 192.168.25.170
s=Asterisk
c=IN IP4 192.168.25.170
t=0 0
m=audio 10076 RTP/SAVP 9 8 0 98 113 114 101

Im VIA - Header als ACK auf den 407 im SIP-Handshake wird die falsche IP verwendet (die lokale statt globale - im initialen Invite wurde noch die korrekte gloable IP verwendet => inkonsistent).

Code:
Via: SIP/2.0/TLS 192.168.25.170:58045;rport;branch=z9hG4bKPjeff8d207-8d6e-442e-8f3a-39c6d96852eb;alias

Die rport-Einstellung hat bei mir im Übrigen nie gezogen - egal was parametrisiert wurde - die Antwort vom VoIP-Provider wurde nie weiter verwendet in den Folge-Requests / Responses (ich habe das auch nicht weiter analysiert - war mir zu blöd. Mein Ziel war und ist immer, dass im SIP und RTP-Protokoll konsistent die für den VoIP-Provider durchgängig korrekten Werte drin stehen, weil das die umfassendste und sicherste und sauberste Methode ist).
Ein weiteres essentielles Thema bei NAT ist, dass die vom NAT-Gateway nach außen verwendeten Ports für die gloable IP nicht zwangsweise die gleichen sein müssen, wie die von Asterisk verwendeten lokalen Ports und daher der von Asterisk im SIP- bzw. RTP-Protokoll hinterlegte Port vom real nach außen verwendeten Port abweichen kann. Das führt beim einen oder anderen VoIP-Provider auch zu Problemen, wenn sie es genauer nehmen (wie die Telekom All-IP z.B.).
Man muss daher darauf achten, dass das NAT-Gateway nicht die Ports verändert, die Asterisk ursprünglich verwendet hat, d.h., dass für die genattete IP der selbe Port verwendet wird wie für die interne IP verwendet wurde, so dass keine Differenzen zwischen SIP- / RTP-Protokoll und Netzwerkstack auftreten. Dies kann man mit einem Linuxrouter wie folgt erzwingen:

  1. Der Asterisk-Server bekommt eine dedizierte lokale IP, mit der er arbeiten muss, die ansonsten nicht nach außen verwendet wird. Dies stellt sicher, dass Asterisk exklusiv die für ihn vorgesehenen RTP-Ports garantiert immer zur Verfügung hat (weil kein anderes Programm diese IP verwendet und damit Ports "klauen" könnte).

    Diese wird im Transport wie folgt definiert (z.B.):
    Code:
    bind=192.168.25.170:0

  2. Man definiert einen nicht zu großen RTP-Portrange in Asterisk (rtp.conf) - je nachdem, wie viele outbound calls eben parallel nötig sind. Pro outbound call werden 3 Ports benötigt - 2x rtp und 1x rtcp.
    Code:
    rtpstart=11000
    rtpend=11030
    rtpchecksums=yes
    strictrtp=yes

  3. Mit iptables wird nun das NAT wie folgt definiert (bis 11031 wg rtcp!):
    Code:
    rtpportsAsterisk2='11000-11031'
    wan=[eigene gloable IP]
    tonlineRTP='217.0.0.0/13'
    iptables -w -t nat -I POSTROUTE -p udp -s 192.168.25.170 --sport $rtpportsAsterisk -d $tonlineRTP -j SNAT --to-source $wan:$rtpportsAsterisk2

    Warum nicht MASQUERADE als Ziel? Weil MASQUERADE das 1:1 Portmapping zumindest hier nicht garantieren wollte. Wichtig ist auch bei SNAT, dass man den zu verwendenden Portbereich für die globale IP erzwingt (--to-source $wan:$rtpportsAsterisk2)! Falls das auch nicht zum Ziel führen sollte, muss man die Regel wirklich dediziert pro Port einstellen:

    Code:
    iptables -w -t nat -I postroute-tkom -p udp -s $natnet --sport $ersterRTPPort -d $tonlineRTP -j SNAT --to-source $wan:$ersterRTPPort
    iptables -w -t nat -I postroute-tkom -p udp -s $natnet --sport $zweiterRTPPort -d $tonlineRTP -j SNAT --to-source $wan:$zweiterRTPPort
    ...

Weiterer wichtiger Punkt für das NAT-Gateway: Die "Übersetzung" lokale IP <-> globale IP muss während der ganzen Verbindungszeit offengehalten werden - d.h., das Gateway darf die Verbindung nicht nach geraumer Zeit (timeout) kappen. Dies wird bei der vorgestellten Lösung out of the box erreicht, ohne weitere Einstellungen, da pjsip einen default TLS-Ping (PJSIP_TLS_KEEP_ALIVE_INTERVAL) mitbringt.


Wie funktioniert die genannte Konfiguration nun im Detail?
Das ist auf Basis der oben beschrieben Vorgehensweise jetzt eigentlich ganz einfach: es gibt keine Differenzen zwischen SIP und RTP-Protokoll und dem Netzwerkstack mehr - auch über das NAT-Gateway hinweg. Damit sieht für den VoIP-Provider alles so aus, wie wenn gar kein NAT vorliegen würde - also alles gut.

Die relevanten Patches habe ich angehängt. Wichtig ist, dass für den Patch nobind.diff das gesamte Asterisk / pjsip-Paket zwingend mit dem Compileschalter PJSIP_TLS_TRANSPORT_DONT_CREATE_LISTENER bzw. PJSIP_TCP_TRANSPORT_DONT_CREATE_LISTENER übersetzt werden muss - sonst wird das nichts (ist von pjsip so vorgesehen)! Ich gehe davon aus, dass das pjsip - Bundle verwendet wird (und nicht ein auf dem System vorhandenes pjsip unabhängig von Asterisk verwendet wird).
Erreicht wird dies praktisch, indem man die Umgebungsvariable CFLAGS vor der Konfiguration im Rahmen des Übersetzungsvorgangs bereits mit den entsprechenden Werten belegt - Achtung - sicherstellen, dass man nicht evtl. andere schon vorhandene CFLAGS überschreibt! Ich verwende das Sangoma spec-Paket und habe daher die Definition im spec-File wie folgt angepasst:
Code:
%build
%ifarch x86_64
%define makeflags OPT=-fPIC
%else
%define makeflags OPT=
%endif
echo %{version}%{?_without_optimizations:-debug} > .version

./bootstrap.sh
CFLAGS='-DENABLE_SRTP_AES_256 -DPJSIP_TLS_TRANSPORT_DONT_CREATE_LISTENER -DPJSIP_TCP_TRANSPORT_DONT_CREATE_LISTENER' ./configure --libdir=%{_libdir} --with-jansson-bundled
....
Weitre Inforamtionen zu den beiden NAT-Patches gibt es hier.

Ein weiterer derzeit noch wichtiger Patch. Betrifft die Asterik Versionen 18.4 und 16.18. Sollte dann mit den Folgeversionen gefixt sein.

Zudem - mittlerweile sind Asterisk-Kollegen wohl dazu übergegangen, dass Patches, welche von Personen eingereicht werden, die ihnen nicht vollständig bekannt sind (Name, Adresse, Telefonnummer, ...), einfach wieder entfernt werden. 73433a5.diff war mal in ASTERISK-29241 enthalten und von Florian Floimair eingereicht worden. Aber selber darum kümmern tun sie sich auch nicht. Da gibt es leider eine ganze Menge davon. Irgendwie haben die ein gespanntes Verhältnis zur Community bzw. überhaupt zum Thema Anerkennen von Problemen überhaupt und Fixen in Folge (sie müssen ja nicht die vorgeschlagenen übernehmen, wenn sie bessere haben - dagegen spricht nichts - aber es sollte wenigstens bereinigt werden). Wie es aussieht zu wenig Personal. Daher kommt es zu derartigen dezentralen Patchorgien, was für niemand witzig ist.

Ich hoffe, dass es dem einen oder anderen (bei der Analyse und Lösung) hilft, wenn er im Zusammenhang mit NAT auf Probleme stößt.

Update 22.06.2021: Formatierungsfix, Gatewaytimeout ergänzt, Eindeutigkeit VoIP-Provider gesetzt.
 

Anhänge

  • patches.zip
    3 KB · Aufrufe: 8
Zuletzt bearbeitet:
  • Like
Reaktionen: Macro und rmh
Ein paar Tipps zur Aufmachung:
  • Hinter f8d207-8d6e-442e-8f3a-39c6d96852eb wurde das Tag „code“ nicht geschlossen. Daher ist der Rest mit der Liste etwas durcheinander.
  • Bei den Wörtern „Provider“ bzw. „ISP“ denke ich immer an den lokalen Internet-Anbieter. Ich vermute Du meinst den Telefon-Anbieter. Also für einen Begriff entscheiden: Provider oder ISP oder Anbieter und anfangs einmal kurz ausschreiben, also Telefon-Anbieter oder VoIP/SIP-Anbieter.
  • Extrem komplex, recht komplex … bei den Steigerungen nochmals schauen, ob der Satz nicht auch ohne Steigerung verstanden wird, also nur „komplex“ schreiben (oder wenn möglich, den Leser selbst auf diese Bewertung kommen lassen).
  • Am Anfang das Szenario hervorheben – ja, machst Du am Ende mit dem Verweis auf den anderen Post, aber eben am Ende. Du beschreibt einen Asterisk-Aufbau, der als VoIP/SIP-Client zu einem öffentlichen Telefon-Anbieter verbindet. Es ist kein eigener öffentlicher Asterisk-Server und dessen NAT-Probleme.
Patches, welche von Personen eingereicht werden, die ihnen nicht vollständig bekannten sind [werden] einfach wieder entfernt.
Als Hintergrund-Information: Sangoma bzw. Digium braucht eine Lizenz, um Deine Software-Contribution auch in anderen wilden Lizenz-Modellen weitervertreiben zu dürfen, also deren kommerzielle Angebote machen zu können. Daher verlangt Sangoma für jede Contribution (auch im Bug-Tracker) eine solche vorher unterschriebene Lizenz. Aber in diesem speziellen Fall solltest Du beim Asterisk-Team nachfragen, weil ich die Löschung des Patches in der Bug-Tracker-Historie nicht sehe. Aufgrund eines Missverständnisses gab es bei Florian ein Lizenz-Problem. Vielleicht sind deswegen Patches von ihm im Bug-Tracker automatisch verschwunden.
Selber [um die Contributions] kümmern tun sie sich auch nicht.
Auch hier als Hintergrund-Information: Das Asterisk-Team arbeitet rein Motivations-basiert. Das bedeutet, dass jemand eine Motivation haben muss, um sich durch alles durchzukämpfen bzw. den Berg zu erklimmen: Bug-Report, Patch nach Styleguide, Patch für alle aktiven Branches, Commit-Text nach Styleguide, Code-Review nachbessern. So wird man wegen jedem Schreibfehler gefoppt und muss den nachbessern anstatt dass der Kommentator es schnell selbst macht.

Ist einem dieser Berg zu hoch, ist es die Änderung nicht wert. Diese Denkweise ist natürlich Mumpitz, weil dadurch allein schon nicht-funktionalen Anforderungen hinten runter fallen und das Produkt am Ende nur Käse ist. Das zweite Problem ist, dass niemand mehr in der Asterisk-Spähre existiert bzw. bei der Umstellung auf das PJ-Project gab, der die Zielgruppe Heim-Anwender im Blick hatte. Vereinfacht geht das Asterisk-Team davon aus, dass eine Asterisk-Installation genau eine öffentliche IP-Adresse hat. Selbst neben der IPv4 eine weitere IPv6-Adresse erforderte anfangs eine eigene weitere Asterisk-Instanz. Dadurch werden die Fehler bereits im Bug-Tracker falsch (zu niedrig) klassifiziert. Viele der Bugs im Tracker sind für „uns“ aber Show-Stopper.

Und dann hast Du das Problem, dass Spezialitäten wie transport-orientierte Transport oder gar TLS/SSL keinen Maintainer haben und es rein Community-basiert gepflegt wird. Und hier dann eben der komplette Berg. Wenn Du Zeit und Lust hast, schnapp Dir ein konkretes Thema und treibe es dann bis zum Schluss. Ich habe Dich jetzt noch nicht auf Gerrit gesehen. Ich weiß, das ist eine steile Lernkurve, an dessen Ende dann unnützes Wissen steht. Aber pick Dir irgendwas kleines raus, vielleicht von einem anderen Ticket und trage es selbst den Berg hoch. Einmal den Berg erklommen, ist der Weg immer der selbe und damit nur noch zeitaufwendig aber wenigstens nicht mehr kompliziert.
Die rport-Einstellung hat bei mir im Übrigen nie gezogen [,denn] die Antwort vom [Telefon-Anbieter] wurde nie weiter verwendet.
Das kann Asterisk als Client nicht. Leider. Aktuell ist das gedacht für Asterisk als Server, damit dessen VoIP/SIP-Clients damit vielleicht doch ihre IP-Adressen in SIP und SDP anpassen. In chan_sip wurde damit auch implizit die symmetric_response eingeschaltet. In chan_pjsip sind die beiden Dinge nun entkoppelt.
vom NAT-Gateway nach außen verwendeten Ports [müssen] nicht zwangsweise die gleichen sein, wie die von Asterisk verwendeten lokalen Ports
Ja, das ist ein Problem. Hier muss man schauen, dass niemand im Heimnetz den Port wegklaut. Das ist eines der Probleme, die man eigentlich nie lösen kann. Das NAT-Gateway hat noch eine andere, zweite Dimension: Zeit, denn alle Gateways machen irgendwann den Port zu, wenn keine Daten liefen und kein statisches Port-Forwarding definiert war. Darin unterscheiden dann die Telefon-Anbieter: Viele machen von sich aus symmetric_response, der Port wird damit egal. Viele refreshen die Verbindung von sich aus, damit wird das Binding egal.
 
Zuletzt bearbeitet:
Danke sonyKatze für Deine Hinweise bzw. das Korrekturlesen! Ich habe die wichtigsten Punkte aufgenommen / ergänzt / korrigiert. Ab und zu habe ich eben Bewertungen drin - die, wie bei jeder Bewertung, immer subjektiver Natur sind. Ein beliebiger Leser hat natürlich selbstverständlich das Recht, hier eine unterschiedliche Wahrnehmung zu haben.
 
Holen Sie sich 3CX - völlig kostenlos!
Verbinden Sie Ihr Team und Ihre Kunden Telefonie Livechat Videokonferenzen

Gehostet oder selbst-verwaltet. Für bis zu 10 Nutzer dauerhaft kostenlos. Keine Kreditkartendetails erforderlich. Ohne Risiko testen.

3CX
Für diese E-Mail-Adresse besteht bereits ein 3CX-Konto. Sie werden zum Kundenportal weitergeleitet, wo Sie sich anmelden oder Ihr Passwort zurücksetzen können, falls Sie dieses vergessen haben.