├── .boring ├── INSTALL ├── LICENSE ├── MANIFEST.in ├── README ├── TODO ├── build_inplace ├── config.ini.sample ├── debian ├── changelog ├── compat ├── control ├── copyright ├── mediaproxy-common.docs ├── mediaproxy-common.install ├── mediaproxy-common.lintian-overrides ├── mediaproxy-dispatcher.docs ├── mediaproxy-dispatcher.install ├── mediaproxy-dispatcher.manpages ├── mediaproxy-dispatcher.service ├── mediaproxy-relay.docs ├── mediaproxy-relay.install ├── mediaproxy-relay.manpages ├── mediaproxy-relay.service ├── mediaproxy-web-sessions.apache2 ├── mediaproxy-web-sessions.conf ├── mediaproxy-web-sessions.docs ├── mediaproxy-web-sessions.install ├── mediaproxy-web-sessions.preinst ├── rules └── source │ └── format ├── diagrams ├── mediaproxy-diagram.graffle └── mediaproxy-diagram.png ├── doc └── man │ ├── media-dispatcher.1 │ └── media-relay.1 ├── media-dispatcher ├── media-relay ├── mediaproxy ├── __init__.py ├── configuration │ ├── __init__.py │ └── datatypes.py ├── dispatcher.py ├── headers.py ├── interfaces │ ├── __init__.py │ ├── accounting │ │ ├── __init__.py │ │ ├── database.py │ │ └── radius.py │ ├── opensips.py │ └── system │ │ ├── __init__.py │ │ └── _conntrack.c ├── iputils.py ├── mediacontrol.py ├── relay.py ├── scheduler.py ├── sipthor.py └── tls.py ├── radius └── dictionary ├── setup.py ├── test ├── common.py ├── holdtest1.py ├── holdtest2.py ├── holdtest3.py ├── icetest1.py ├── icetest2.py ├── multitest ├── multitest1.py ├── multitest2.py ├── multitest3.py ├── multitest4.py ├── setuptest1.py ├── setuptest2.py ├── setuptest3.py ├── setuptest4.py ├── setuptest5.py ├── setuptest6.py ├── setuptest7.py ├── updatetest1.py ├── updatetest2.py ├── updatetest3.py ├── updatetest4.py ├── updatetest5.py └── updatetest6.py ├── tls ├── README ├── ca.pem ├── crl.pem ├── dispatcher.crt ├── dispatcher.key ├── relay.crt └── relay.key └── web ├── README ├── config └── media_sessions.conf.sample ├── images ├── 30 │ ├── Arris_TM722b.png │ ├── G580.png │ ├── Nokia810.png │ ├── Telephone.png │ ├── aastra.png │ ├── asterisk.png │ ├── audiocodes-mp124.png │ ├── avm-fritzbox-wlan.png │ ├── avm-fritzbox-wlan2.png │ ├── blink.png │ ├── budgetone100.png │ ├── cirpack.png │ ├── cisco-5380.png │ ├── cisco-7960.png │ ├── cisco-ata.png │ ├── cisco.png │ ├── copperjet16162p.png │ ├── draytek-vigor2600v.png │ ├── draytek-vigor2600vg.png │ ├── draytek-vigor2800g.png │ ├── draytek-vigor2900g.png │ ├── droid.png │ ├── eStara.png │ ├── ekiga.png │ ├── eyebeam.png │ ├── fring.png │ ├── genexis.png │ ├── handytone.png │ ├── hitachi-wip5000-2.png │ ├── hitachi-wip5000-3.png │ ├── hitachi-wip5000.png │ ├── innomedia-mta5000.png │ ├── ipDialog.png │ ├── ipkall.png │ ├── linksys-pap2-vert.png │ ├── linksys-pap2.png │ ├── messenger.png │ ├── nimbuzz.png │ ├── nokia.png │ ├── session.png │ ├── siemens-3610.png │ ├── sip-communicator.png │ ├── sipps.png │ ├── sjphone.png │ ├── snom100.png │ ├── snom200.png │ ├── snom320-front.png │ ├── snom320-left.png │ ├── snom320.png │ ├── snom360-front.png │ ├── snom360-left.png │ ├── snom360.png │ ├── spa2000.png │ ├── teles.png │ ├── unknown.png │ ├── unknown3.png │ ├── vizufon.png │ ├── vizufon2.png │ ├── webstarepx2203.png │ ├── xten.png │ ├── zoep.png │ └── zyxel-p2000.png └── PoweredbyAGProjects.gif ├── library ├── media_sessions.php └── phone_images.php └── media_sessions.phtml /.boring: -------------------------------------------------------------------------------- 1 | # 2 | # Boring file regular expressions 3 | # 4 | 5 | ~$ 6 | \# 7 | (^|/)\.DS_Store$ 8 | (^|/)Thumbs\.db$ 9 | (^|/)core(\.[0-9]+)?$ 10 | \.(pyc|pyo|o|so|orig|rej|prof|bak|BAK|tmp|wpr|wpu|swp|swo|komodoproject)$ 11 | 12 | (^|/)\.idea($|/) 13 | (^|/)\.komodotools($|/) 14 | (^|/)_darcs($|/) 15 | 16 | ^MANIFEST$ 17 | ^config\.ini$ 18 | ^build($|/) 19 | ^dist($|/) 20 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | 2 | MediaProxy installation procedure 3 | --------------------------------- 4 | 5 | Copyright (c) 2008-2020 AG Projects 6 | http://ag-projects.com 7 | 8 | Authors: Ruud Klaver, Dan Pascu, Saul Ibarra 9 | 10 | Home page: http://mediaproxy.ag-projects.com 11 | 12 | For the list of changes between revisions please consult debian/changelog 13 | 14 | For information about the MediaProxy architecture, configuring and running 15 | the dispatcher and the relay, as well as details about the supported 16 | features and how to use them, please consult the README file. 17 | 18 | 19 | Prerequisites 20 | ------------- 21 | 22 | In order to build and install, MediaProxy has the following requirements: 23 | 24 | - Linux (at least 2.6.18) with the following features compiled in: 25 | - netfilter support 26 | - connection tracking support 27 | - connection tracking netlink interface 28 | - connection tracking event notification API 29 | - netfilter "NOTRACK" target support 30 | - netfilter "CONNMARK" target support 31 | - netfilter "connmark" match support 32 | - IPv4 connection tracking support 33 | - IP tables support 34 | - IP tables Full NAT support 35 | Distribution provided kernel images should normally provide of all these 36 | features as modules. The Debian kernel images have all these features 37 | available and can be used out of the box. 38 | - libnetfilter-conntrack 39 | Most of the Linux distributions separate a library package into runtime 40 | and development packages. To build MediaProxy, the development version 41 | is needed (it usually has a -dev suffix in the package name). 42 | - iptables 43 | To build MediaProxy, the development version is needed (usually has a -dev 44 | suffix in the package name). For running the development package is not 45 | needed, only plain iptables is enough. 46 | - Python3 47 | http://python.org 48 | - Twisted framework 49 | http://twistedmatrix.com 50 | - python3-zope.interface 51 | http://zope.org 52 | - python3-application 53 | http://download.ag-projects.com/others/ 54 | - GNU-TLS 55 | http://www.gnu.org/software/gnutls 56 | - python3-gnutls (at least 3.0.0) 57 | http://download.ag-projects.com/others/ 58 | 59 | For the database accounting module: 60 | 61 | - SQLObject 62 | http://sqlobject.org 63 | 64 | For the RADIUS accounting module: 65 | 66 | - pyrad 67 | https://pypi.org/project/pyrad/ 68 | 69 | 70 | Installation 71 | ------------ 72 | 73 | For people running Debian or Ubuntu on an i386 or amd64 architecture there are 74 | official public repositories provided by AG Projects. 75 | 76 | Modify your /etc/apt/sources.list depending on the distribution you are using, 77 | check here for the appropriate lines: 78 | 79 | http://mediaproxy.ag-projects.com/wiki/InstallationGuide 80 | 81 | Install the AG Projects debian software signing key: 82 | 83 | wget http://download.ag-projects.com/agp-debian-gpg.key 84 | apt-key add agp-debian-gpg.key 85 | 86 | After that, run: 87 | 88 | apt-get update 89 | apt-get install mediaproxy-dispatcher mediaproxy-relay mediaproxy-web-sessions 90 | 91 | to install all the packages, or you can install only the packages you actually 92 | need on that specific system. 93 | 94 | In case you want to build your own, please look below to Packaging section. 95 | 96 | 97 | Installing from source 98 | ---------------------- 99 | 100 | When installing from source, first make sure the above mentioned prerequisites 101 | are installed. If the distribution you are running has them already packaged, 102 | you should install the distribution provided packages, else you'll have to 103 | install them from source. If you install them as packages, make sure that you 104 | also install the development versions for python and libnetfilter-conntrack in 105 | order to be able to build MediaProxy. If you have to install something from 106 | source, please consult the installation instructions for that specific package 107 | in order to find out how to install it. For python packages there is a simple 108 | method to install them by running easy_install (make sure to run them as root): 109 | 110 | easy_install twisted 111 | easy_install zope.interface 112 | easy_install sqlobject 113 | easy_install pyrad 114 | 115 | python3-application and python3-gnutls can be downloaded from 116 | 117 | http://download.ag-projects.com/others/ 118 | 119 | 120 | An alternative method to install the python packages is to download, unpack 121 | and run (as root): 122 | 123 | ./setup.py build; ./setup.py install 124 | 125 | for each of them in the directories where they were unpacked. 126 | 127 | It should be noted that this only needs to be done for the packages that are 128 | not provided already by your distribution, otherwise it is recommended to use 129 | the distribution provided packages unless they do not meet the minimum version 130 | requirements mentioned above or if they exhibit problems at runtime. 131 | 132 | After all the prerequisites are installed, MediaProxy can be installed either 133 | as a system wide package or in a standalone directory. 134 | 135 | 1. To install it as a system wide package, run (as root): 136 | 137 | ./setup.py build 138 | ./setup.py install 139 | 140 | in the directory where you unpacked MediaProxy. 141 | 142 | 2. To install in a standalone directory, unpack MediaProxy to the directory 143 | where you want it placed. Then change to that directory and run: 144 | 145 | On Debian you can install the build dependencies with: 146 | 147 | sudo apt install python3-all-dev libnetfilter-conntrack-dev libiptc-dev 148 | 149 | Then run: 150 | 151 | ./build_inplace 152 | 153 | After this MediaProxy components can be run from that directory. 154 | 155 | In both cases, you can use the Debian startup scripts in the Debian 156 | subdirectory, mediaproxy-dispatcher.init and mediaproxy-relay.init as 157 | examples to create your own startup scripts for your distribution. 158 | 159 | 160 | Packaging 161 | --------- 162 | 163 | The MediaProxy source already includes the necessary files to build Debian 164 | packages. They should probably also work without changes for Ubuntu, though 165 | they have not been tested with it. 166 | 167 | To build Debian/Ubuntu packages, you can do the following (this is known to 168 | work with Debian testing and unstable and should work without changes in 169 | Ubuntu 8.04 Hardy as well, though they were not tested there): 170 | 171 | apt-get update 172 | apt-get install devscripts cdbs debhelper python3-all-dev \ 173 | iptables-dev libnetfilter-conntrack-dev python3-application \ 174 | python3-gnutls python3-twisted python3-zope.interface \ 175 | python3-pyrad python3-sqlobject 176 | 177 | then unpack MediaProxy and in the directory where it was unpacked run: 178 | 179 | debuild -us -uc 180 | 181 | You can safely ignore the pgp signing error at the end of the build process, 182 | that is only because you do not have the pgp key for the person who is listed 183 | as maintainer for the package. The packages are build fine even if they are 184 | not signed. 185 | 186 | After building them, you can find the .deb packages in the parent directory, 187 | from where you can install them using dpkg: 188 | 189 | cd ../ 190 | dpkg -i mediaproxy-*.deb 191 | 192 | or you can install just the ones you need on that particular system. 193 | Please note that mediaproxy-dispatcher and mediaproxy-relay both depend on 194 | mediaproxy-common so you have to install it too along with either of them. 195 | 196 | 197 | Configuration file 198 | ------------------ 199 | 200 | The configuration file is named config.ini and a config.ini.sample file is 201 | provided in the source. You can copy config.ini.sample to config.ini and 202 | modify it to suit your needs. The sample configuration file is commented and 203 | self-explanatory. Both the dispatcher and the relay read their configuration 204 | from the same file but from different sections. If either of them is not 205 | installed on a given system, its specific sections are ignored, so you only 206 | need to configure the sections for the installed component(s). 207 | 208 | MediaProxy will look for both a local configuration file, which is placed in 209 | the same directory as the media-relay and media-dispather scripts, and a 210 | system configuration file which is placed in /etc/mediaproxy/ 211 | 212 | Even though a local configuration file can be used in any case, it only makes 213 | sense to be used in the standalone installation case, where MediaProxy lives 214 | in its own directory and there is a reason to contain all the MediaProxy 215 | related files to a single directory. 216 | 217 | For system wide installations, where the media-relay and media-dispatcher 218 | scripts reside in /usr/bin or /usr/local/bin, it makes little sense to 219 | place a local configuration file there, so in this case using the system 220 | configuration file in /etc/mediaproxy/config.ini is recommended. 221 | 222 | When both configuration files are present, both will be read and the settings 223 | in the local configuration will override the ones in the system configuration. 224 | 225 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2007-2024 AG Projects http://ag-projects.com 2 | 3 | License GPL-2 4 | 5 | This package is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU General Public License 7 | version 2, as published by the Free Software Foundation. 8 | 9 | This package is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public 15 | License along with this package; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | For a copy of the license see https://www.gnu.org/licenses/gpl-2.0.html 19 | 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include INSTALL 3 | include README 4 | include TODO 5 | include MANIFEST.in 6 | include build_inplace 7 | 8 | include debian/changelog 9 | include debian/compat 10 | include debian/control 11 | include debian/copyright 12 | include debian/rules 13 | include debian/source/format 14 | include debian/*.apache2 15 | include debian/*.conf 16 | include debian/*.docs 17 | include debian/*.install 18 | include debian/*.lintian-overrides 19 | include debian/*.manpages 20 | include debian/*.preinst 21 | include debian/*.service 22 | 23 | recursive-include test *.py multitest 24 | 25 | graft doc 26 | graft radius 27 | graft tls 28 | graft web 29 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | + Make speed measurement inexpensive by using an iptables rule counter 2 | + Filter the result of the sessions command and return at most 50 entries 3 | ordered by some criteria (like most recent first). Also add a new command 4 | in the relay to report back all active sessions (only their call_id) to 5 | allow the dispatcher to purge obsolete sessions when a relay reconnects 6 | with it, as the sessions command will no longer return all sessions and 7 | is also very expensive as it returns a lot more information than 8 | necessary, including stream counters which are very expensive to gather. 9 | Define some ordering rules for the selection here and also define what 10 | fields to search with the filter that is provided (from/to/callid...) 11 | + Add a new command to the dispatcher to terminate a session. This should 12 | be available for any session that has a dialog_id and should allow the 13 | media sessions web interface to terminate a dialog by clicking a button 14 | In result the dispatcher should trigger dialog termination. 15 | 16 | - Lawful intercept for RTP media 17 | - Selection of mediaproxy based on proximity 18 | - Add ability to relay to restart on the fly without losing the sessions 19 | - Minimize interruption caused by a dispatcher restart 20 | - Make traffic calculation more efficient 21 | - Only log connection state transitions to avoid too much logging in case 22 | the dispatcher is down and the relay can't reconnect. 23 | - Allow the NAT address to change during a call to accomodate ISPs that 24 | dynamically allocate IP addresses and change them at regular intervals. 25 | This has to work even without any signaling support, based on the SSRC 26 | field in the RTP media stream. For protection, switching the NAT address 27 | should only happen if the old NAT address didn't send anything for a while, 28 | while the new NAT has sent a stream with the same SSRC field. 29 | -------------------------------------------------------------------------------- /build_inplace: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python3 setup.py build_ext --inplace "$@" 4 | test -d build && python3 setup.py clean 5 | 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: mediaproxy 2 | Section: net 3 | Priority: optional 4 | Maintainer: Adrian Georgescu 5 | Uploaders: Tijmen de Mes 6 | Build-Depends: debhelper (>= 11), dh-apache2, dh-python, python3-all-dev, libnetfilter-conntrack-dev, libiptc-dev 7 | Standards-Version: 4.5.0 8 | 9 | Package: mediaproxy-common 10 | Architecture: any 11 | Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends}, 12 | iptables, 13 | python3-application, 14 | python3-gnutls, 15 | python3-pyrad, 16 | python3-sqlobject, 17 | python3-mysqldb, 18 | python3-systemd, 19 | python3-twisted, 20 | python3-zope.interface 21 | Description: MediaProxy common files 22 | MediaProxy is a distributed far end NAT traversal solution for media streams 23 | of SIP calls. MediaProxy has a dispatcher running on the same host as the 24 | OpenSIPS SIP proxy and multiple media relays distributed over the network. 25 | The media relays work by manipulating conntrack rules in the Linux kernel to 26 | create paths that forward the media streams between the 2 SIP user agents 27 | participating in the call. Because it avoids copying stream data between 28 | kernel and user space like other implementations, MediaProxy can handle many 29 | more media streams at a time, being limited only by the network interface 30 | bandwidth and the Linux kernel network layer processing speed. 31 | . 32 | MediaProxy features secure encrypted communication between the dispatcher 33 | and the relays, advanced accounting capabilities using multiple backends, 34 | support for any combination of audio and video streams, realtime statistics, 35 | T.38 fax support as well as automatic load balancing and redundancy among 36 | the active relays. 37 | . 38 | This package includes files common to all MediaProxy packages. 39 | 40 | Package: mediaproxy-dispatcher 41 | Architecture: all 42 | Depends: ${python3:Depends}, ${misc:Depends}, mediaproxy-common 43 | Description: MediaProxy dispatcher for OpenSIPS 44 | MediaProxy is a distributed far end NAT traversal solution for media streams 45 | of SIP calls. MediaProxy has a dispatcher running on the same host as the 46 | OpenSIPS SIP proxy and multiple media relays distributed over the network. 47 | The media relays work by manipulating conntrack rules in the Linux kernel to 48 | create paths that forward the media streams between the 2 SIP user agents 49 | participating in the call. Because it avoids copying stream data between 50 | kernel and user space like other implementations, MediaProxy can handle many 51 | more media streams at a time, being limited only by the network interface 52 | bandwidth and the Linux kernel network layer processing speed. 53 | . 54 | MediaProxy features secure encrypted communication between the dispatcher 55 | and the relays, advanced accounting capabilities using multiple backends, 56 | support for any combination of audio and video streams, realtime statistics, 57 | T.38 fax support as well as automatic load balancing and redundancy among 58 | the active relays. 59 | . 60 | This package provides the MediaProxy dispatcher. 61 | 62 | Package: mediaproxy-relay 63 | Architecture: all 64 | Depends: ${python3:Depends}, ${misc:Depends}, mediaproxy-common 65 | Description: MediaProxy relay for OpenSIPS 66 | MediaProxy is a distributed far end NAT traversal solution for media streams 67 | of SIP calls. MediaProxy has a dispatcher running on the same host as the 68 | OpenSIPS SIP proxy and multiple media relays distributed over the network. 69 | The media relays work by manipulating conntrack rules in the Linux kernel to 70 | create paths that forward the media streams between the 2 SIP user agents 71 | participating in the call. Because it avoids copying stream data between 72 | kernel and user space like other implementations, MediaProxy can handle many 73 | more media streams at a time, being limited only by the network interface 74 | bandwidth and the Linux kernel network layer processing speed. 75 | . 76 | MediaProxy features secure encrypted communication between the dispatcher 77 | and the relays, advanced accounting capabilities using multiple backends, 78 | support for any combination of audio and video streams, realtime statistics, 79 | T.38 fax support as well as automatic load balancing and redundancy among 80 | the active relays. 81 | . 82 | This package provides the MediaProxy relay. 83 | 84 | Package: mediaproxy-web-sessions 85 | Architecture: all 86 | Depends: ${misc:Depends}, libapache2-mod-php 87 | Description: MediaProxy sessions web view 88 | MediaProxy is a distributed far end NAT traversal solution for media streams 89 | of SIP calls. MediaProxy has a dispatcher running on the same host as the 90 | OpenSIPS SIP proxy and multiple media relays distributed over the network. 91 | The media relays work by manipulating conntrack rules in the Linux kernel to 92 | create paths that forward the media streams between the 2 SIP user agents 93 | participating in the call. Because it avoids copying stream data between 94 | kernel and user space like other implementations, MediaProxy can handle many 95 | more media streams at a time, being limited only by the network interface 96 | bandwidth and the Linux kernel network layer processing speed. 97 | . 98 | MediaProxy features secure encrypted communication between the dispatcher 99 | and the relays, advanced accounting capabilities using multiple backends, 100 | support for any combination of audio and video streams, realtime statistics, 101 | T.38 fax support as well as automatic load balancing and redundancy among 102 | the active relays. 103 | . 104 | This package provides a simple web page to display active media sessions. 105 | 106 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Copyright (C) 2007-2020 AG Projects http://ag-projects.com 2 | 3 | License GPL-2 4 | 5 | This package is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU General Public License 7 | version 2, as published by the Free Software Foundation. 8 | 9 | This package is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public 15 | License along with this package; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | On Debian systems, the complete text of the GNU General Public License can 19 | be found in `/usr/share/common-licenses/GPL-2'. 20 | 21 | -------------------------------------------------------------------------------- /debian/mediaproxy-common.docs: -------------------------------------------------------------------------------- 1 | README 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-common.install: -------------------------------------------------------------------------------- 1 | etc 2 | usr/lib 3 | tls usr/share/doc/mediaproxy-common 4 | -------------------------------------------------------------------------------- /debian/mediaproxy-common.lintian-overrides: -------------------------------------------------------------------------------- 1 | # This file may contain passwords. 2 | mediaproxy-common: non-standard-file-perm etc/mediaproxy/config.ini 0600 != 0644 3 | -------------------------------------------------------------------------------- /debian/mediaproxy-dispatcher.docs: -------------------------------------------------------------------------------- 1 | README 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-dispatcher.install: -------------------------------------------------------------------------------- 1 | usr/bin/media-dispatcher 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-dispatcher.manpages: -------------------------------------------------------------------------------- 1 | doc/man/media-dispatcher.1 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-dispatcher.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MediaProxy dispatcher 3 | Documentation=man:media-dispatcher 4 | After=network.target mysqld.service mariadb.service 5 | 6 | [Service] 7 | Type=simple 8 | RuntimeDirectory=mediaproxy 9 | RuntimeDirectoryMode=755 10 | RuntimeDirectoryPreserve=yes 11 | Environment=PYTHONUNBUFFERED=yes 12 | ExecStart=/usr/bin/media-dispatcher --systemd 13 | Restart=on-abnormal 14 | LimitCORE=infinity 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /debian/mediaproxy-relay.docs: -------------------------------------------------------------------------------- 1 | README 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-relay.install: -------------------------------------------------------------------------------- 1 | usr/bin/media-relay 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-relay.manpages: -------------------------------------------------------------------------------- 1 | doc/man/media-relay.1 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-relay.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MediaProxy relay 3 | Documentation=man:media-relay 4 | After=network.target nss-lookup.target thor-eventserver.service 5 | 6 | [Service] 7 | Type=simple 8 | Environment=PYTHONUNBUFFERED=yes 9 | ExecStart=/usr/bin/media-relay --systemd 10 | ExecReload=/bin/kill -HUP $MAINPID 11 | Restart=on-abnormal 12 | LimitCORE=infinity 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /debian/mediaproxy-web-sessions.apache2: -------------------------------------------------------------------------------- 1 | conf debian/mediaproxy-web-sessions.conf 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-web-sessions.conf: -------------------------------------------------------------------------------- 1 | 2 | Alias /mediasessions /usr/share/mediaproxy/web 3 | 4 | Options None 5 | AllowOverride None 6 | DirectoryIndex media_sessions.phtml 7 | Require all granted 8 | 9 | -------------------------------------------------------------------------------- /debian/mediaproxy-web-sessions.docs: -------------------------------------------------------------------------------- 1 | web/README 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-web-sessions.install: -------------------------------------------------------------------------------- 1 | web usr/share/mediaproxy 2 | -------------------------------------------------------------------------------- /debian/mediaproxy-web-sessions.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ "$1" = "upgrade" ] && [ -n "$2" ] && dpkg --compare-versions "$2" le-nl "2.6.4~"; then 6 | # we are upgrading from a version that is older than 2.6.4 ($2 is the old version) 7 | test -d /etc/mediaproxy/web/media_sessions.conf && rm -rf /etc/mediaproxy/web/media_sessions.conf || true 8 | rm -rf /etc/apache2/conf.d/mediaproxy_sessions 9 | fi 10 | 11 | #DEBHELPER# 12 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ --with python3,apache2 --buildsystem=pybuild 5 | 6 | override_dh_python3: 7 | dh_python3 --shebang=/usr/bin/python3 8 | 9 | override_dh_clean: 10 | dh_clean 11 | rm -rf build dist MANIFEST 12 | 13 | override_dh_auto_install: 14 | dh_auto_install 15 | mv debian/tmp/etc/mediaproxy/config.ini.sample debian/tmp/etc/mediaproxy/config.ini 16 | 17 | override_dh_install: 18 | dh_install 19 | install -D -m 644 web/config/media_sessions.conf.sample debian/mediaproxy-web-sessions/etc/mediaproxy/web/media_sessions.conf 20 | 21 | override_dh_installsystemd: 22 | dh_installsystemd --no-start 23 | 24 | override_dh_fixperms: 25 | dh_fixperms 26 | chmod 600 debian/mediaproxy-common/etc/mediaproxy/config.ini 27 | 28 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /diagrams/mediaproxy-diagram.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/diagrams/mediaproxy-diagram.graffle -------------------------------------------------------------------------------- /diagrams/mediaproxy-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/diagrams/mediaproxy-diagram.png -------------------------------------------------------------------------------- /doc/man/media-dispatcher.1: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" First parameter, NAME, should be all caps 3 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 4 | .\" other parameters are allowed: see man(7), man(1) 5 | .\" Please adjust this date whenever revising the manpage. 6 | .\" 7 | .\" Some roff macros, for reference: 8 | .\" .nh disable hyphenation 9 | .\" .hy enable hyphenation 10 | .\" .ad l left justify 11 | .\" .ad b justify to both left and right margins 12 | .\" .nf disable filling 13 | .\" .fi enable filling 14 | .\" .br insert line break 15 | .\" .sp insert n+1 empty lines 16 | .\" for manpage-specific macros, see man(7) 17 | .TH "media-dispatcher" 1 "Aug 21, 2019" "AG Projects" "SIP Communication Software" 18 | .SH NAME 19 | media-dispatcher - The MediaProxy dispatcher 20 | .SH SYNOPSIS 21 | .B media-dispatcher 22 | .RI [ options ] 23 | .SH DESCRIPTION 24 | .PP 25 | .\" TeX users may be more comfortable with the \fB\fP and 26 | .\" \fI\fP escape sequences to invode bold face and italics, 27 | .\" respectively. 28 | MediaProxy is a distributed far end NAT traversal solution for media streams 29 | of SIP calls. MediaProxy has a dispatcher running on the same host with the 30 | OpenSIPS proxy and multiple media relays distributed over the network. 31 | .PP 32 | \fBmedia\-dispatcher\fP is to be run on a system running the OpenSIPS proxy 33 | that is configured to use MediaProxy as a media relay. The dispatcher will 34 | take care of distributing the media sessions over all the available relays 35 | that have joined the dispatcher, as well as doing accounting upon receiving 36 | session statistics from the relay when a session ends. The dispatcher also 37 | offers a management interface that can be interogated from a frontend that 38 | wishes to display statistics about media sessions. Upon receiving such a 39 | request, the dispatcher will interrogate in turn all the available media 40 | relays for the appropriate information, returning it back to the caller. 41 | .PP 42 | The MediaProxy dispatcher features secure encrypted communication with the 43 | relays, advanced accounting capabilities using multiple backends as well as 44 | automatic load balancing among the active relays. The management interface 45 | can also use encrypted communications if needed. 46 | .SH OPTIONS 47 | .TP 48 | .B \-h, \-\-help 49 | Show summary of options. 50 | .TP 51 | .B \-\-version 52 | Show program version. 53 | .TP 54 | .B \-\-systemd 55 | Run as a systemd simple service and log to journal. 56 | .TP 57 | .B \-\-no\-fork 58 | Run in the foreground and log to terminal (for debugging). 59 | .TP 60 | .B \-\-config\-dir=PATH 61 | Set the configuration directory (default is \fB/etc/mediaproxy\fP). 62 | .TP 63 | .B \-\-runtime\-dir=PATH 64 | Set the runtime directory (default is \fB/run/mediaproxy\fP). 65 | .TP 66 | .B \-\-debug 67 | Enable verbose logging. 68 | .TP 69 | .B \-\-debug\-memory 70 | Enable memory debugging. 71 | .SH SEE ALSO 72 | .BR media-relay (1), 73 | .PP 74 | For more information, please refer to the MediaProxy page which can be 75 | found at http://ag-projects.com/MediaProxy.html 76 | .SH AUTHOR 77 | Dan Pascu . 78 | -------------------------------------------------------------------------------- /doc/man/media-relay.1: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" First parameter, NAME, should be all caps 3 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 4 | .\" other parameters are allowed: see man(7), man(1) 5 | .\" Please adjust this date whenever revising the manpage. 6 | .\" 7 | .\" Some roff macros, for reference: 8 | .\" .nh disable hyphenation 9 | .\" .hy enable hyphenation 10 | .\" .ad l left justify 11 | .\" .ad b justify to both left and right margins 12 | .\" .nf disable filling 13 | .\" .fi enable filling 14 | .\" .br insert line break 15 | .\" .sp insert n+1 empty lines 16 | .\" for manpage-specific macros, see man(7) 17 | .TH "media-relay" 1 "Aug 21, 2019" "AG Projects" "SIP Communication Software" 18 | .SH NAME 19 | media-relay - The MediaProxy relay 20 | .SH SYNOPSIS 21 | .B media-relay 22 | .RI [ options ] 23 | .SH DESCRIPTION 24 | .PP 25 | .\" TeX users may be more comfortable with the \fB\fP and 26 | .\" \fI\fP escape sequences to invode bold face and italics, 27 | .\" respectively. 28 | MediaProxy is a distributed far end NAT traversal solution for media streams 29 | of SIP calls. MediaProxy has a dispatcher running on the same host with the 30 | OpenSIPS proxy and multiple media relays distributed over the network. 31 | .PP 32 | \fBmedia\-relay\fP is to be run on a system that wishes to participate in media 33 | relaying. It can be run on the same host as the dispatcher, or on multiple 34 | remote hosts. The relay takes care of establishing a path between devices 35 | behind NAT and forwards the media streams between them. To do so, the relay 36 | requires a Linux kernel, version 2.6.18 or newer compiled with support for 37 | connection tracking, the connection tracking event notification API and the 38 | netfilter "NOTRACK" target. 39 | .PP 40 | The MediaProxy relay features secure encrypted communication with the 41 | dispatcher and has gracefull shutdown capabilities, where it refuses to 42 | accept new sessions and shuts down after all the existing sessions are done. 43 | .SH OPTIONS 44 | .TP 45 | .B \-h, \-\-help 46 | Show summary of options. 47 | .TP 48 | .B \-\-version 49 | Show program version. 50 | .TP 51 | .B \-\-systemd 52 | Run as a systemd simple service and log to journal. 53 | .TP 54 | .B \-\-no\-fork 55 | Run in the foreground and log to terminal (for debugging). 56 | .TP 57 | .B \-\-config\-dir=PATH 58 | Set the configuration directory (default is \fB/etc/mediaproxy\fP). 59 | .TP 60 | .B \-\-runtime\-dir=PATH 61 | Set the runtime directory (default is \fB/run/mediaproxy\fP). 62 | .TP 63 | .B \-\-debug 64 | Enable verbose logging. 65 | .TP 66 | .B \-\-debug\-memory 67 | Enable memory debugging. 68 | .SH SEE ALSO 69 | .BR media-dispatcher (1), 70 | .PP 71 | For more information, please refer to the MediaProxy page which can be 72 | found at http://ag-projects.com/MediaProxy.html 73 | .SH AUTHOR 74 | Dan Pascu . 75 | -------------------------------------------------------------------------------- /media-dispatcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | 4 | if __name__ == '__main__': 5 | import mediaproxy 6 | import sys 7 | 8 | from application import log 9 | from application.process import process, ProcessError 10 | from argparse import ArgumentParser 11 | 12 | name = 'media-dispatcher' 13 | fullname = 'MediaProxy Dispatcher' 14 | description = 'MediaProxy Dispatcher component' 15 | 16 | process.configuration.user_directory = None 17 | process.configuration.subdirectory = mediaproxy.mediaproxy_subdirectory 18 | process.runtime.subdirectory = mediaproxy.mediaproxy_subdirectory 19 | 20 | parser = ArgumentParser(usage='%(prog)s [options]') 21 | parser.add_argument('--version', action='version', version='%(prog)s {}'.format(mediaproxy.__version__)) 22 | parser.add_argument('--systemd', action='store_true', help='run as a systemd simple service and log to journal') 23 | parser.add_argument('--no-fork', action='store_false', dest='fork', help='run in the foreground and log to the terminal') 24 | parser.add_argument('--config-dir', dest='config_directory', default=None, help='the configuration directory ({})'.format(process.configuration.system_directory), metavar='PATH') 25 | parser.add_argument('--runtime-dir', dest='runtime_directory', default=None, help='the runtime directory ({})'.format(process.runtime.directory), metavar='PATH') 26 | parser.add_argument('--debug', action='store_true', help='enable verbose logging') 27 | parser.add_argument('--debug-memory', action='store_true', help='enable memory debugging') 28 | 29 | options = parser.parse_args() 30 | 31 | log.Formatter.prefix_format = '{record.levelname:<8s} ' 32 | 33 | if options.config_directory is not None: 34 | process.configuration.local_directory = options.config_directory 35 | if options.runtime_directory is not None: 36 | process.runtime.directory = options.runtime_directory 37 | 38 | try: 39 | process.runtime.create_directory() 40 | except ProcessError as e: 41 | log.critical('Cannot start %s: %s' % (fullname, e)) 42 | sys.exit(1) 43 | 44 | if options.systemd: 45 | from systemd.journal import JournalHandler 46 | log.set_handler(JournalHandler(SYSLOG_IDENTIFIER=name)) 47 | log.capture_output() 48 | elif options.fork: 49 | try: 50 | process.daemonize(pidfile='{}.pid'.format(name)) 51 | except ProcessError as e: 52 | log.critical('Cannot start %s: %s' % (fullname, e)) 53 | sys.exit(1) 54 | log.use_syslog(name) 55 | 56 | log.info('Starting %s %s' % (fullname, mediaproxy.__version__)) 57 | 58 | from mediaproxy.dispatcher import Dispatcher 59 | from mediaproxy.configuration import DispatcherConfig 60 | 61 | log.level.current = log.level.DEBUG if options.debug else DispatcherConfig.log_level 62 | 63 | if options.debug_memory: 64 | from application.debug.memory import memory_dump 65 | 66 | try: 67 | dispatcher = Dispatcher() 68 | except Exception as e: 69 | log.critical('Failed to create %s: %s' % (fullname, e)) 70 | if type(e) is not RuntimeError: 71 | log.exception() 72 | sys.exit(1) 73 | 74 | dispatcher.run() 75 | 76 | if options.debug_memory: 77 | memory_dump() 78 | -------------------------------------------------------------------------------- /media-relay: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | 4 | if __name__ == '__main__': 5 | import mediaproxy 6 | import errno 7 | import sys 8 | import subprocess 9 | 10 | from application import log 11 | from application.process import process, ProcessError 12 | from application.version import Version 13 | from argparse import ArgumentParser 14 | 15 | IP_FORWARD_FILE = '/proc/sys/net/ipv4/ip_forward' 16 | CONNTRACK_ACCT_FILE = '/proc/sys/net/netfilter/nf_conntrack_acct' 17 | KERNEL_VERSION_FILE = '/proc/sys/kernel/osrelease' 18 | 19 | name = 'media-relay' 20 | fullname = 'MediaProxy Relay' 21 | description = 'MediaProxy Relay component' 22 | 23 | process.configuration.user_directory = None 24 | process.configuration.subdirectory = mediaproxy.mediaproxy_subdirectory 25 | process.runtime.subdirectory = mediaproxy.mediaproxy_subdirectory 26 | 27 | parser = ArgumentParser(usage='%(prog)s [options]') 28 | parser.add_argument('--version', action='version', version='%(prog)s {}'.format(mediaproxy.__version__)) 29 | parser.add_argument('--systemd', action='store_true', help='run as a systemd simple service and log to journal') 30 | parser.add_argument('--no-fork', action='store_false', dest='fork', help='run in the foreground and log to the terminal') 31 | parser.add_argument('--config-dir', dest='config_directory', default=None, help='the configuration directory ({})'.format(process.configuration.system_directory), metavar='PATH') 32 | parser.add_argument('--runtime-dir', dest='runtime_directory', default=None, help='the runtime directory ({})'.format(process.runtime.directory), metavar='PATH') 33 | parser.add_argument('--debug', action='store_true', help='enable verbose logging') 34 | parser.add_argument('--debug-memory', action='store_true', help='enable memory debugging') 35 | 36 | options = parser.parse_args() 37 | 38 | log.Formatter.prefix_format = '{record.levelname:<8s} ' 39 | 40 | if options.config_directory is not None: 41 | process.configuration.local_directory = options.config_directory 42 | 43 | if options.runtime_directory is not None: 44 | process.runtime.directory = options.runtime_directory 45 | 46 | if not sys.platform.startswith('linux'): 47 | log.critical('Cannot start %s. A Linux host is required for operation.' % fullname) 48 | sys.exit(1) 49 | 50 | try: 51 | kernel_version = Version.parse(open(KERNEL_VERSION_FILE).read().strip()) 52 | except (OSError, IOError, ValueError): 53 | log.critical('Could not determine Linux kernel version') 54 | sys.exit(1) 55 | 56 | if kernel_version < Version(2, 6, 18): 57 | log.critical('Linux kernel version 2.6.18 or newer is required to run the media relay') 58 | sys.exit(1) 59 | 60 | try: 61 | ip_forward = bool(int(open(IP_FORWARD_FILE).read())) 62 | except (OSError, IOError, ValueError): 63 | ip_forward = False 64 | 65 | if not ip_forward: 66 | log.critical('IP forwarding is not available or not enabled (check %s)' % IP_FORWARD_FILE) 67 | sys.exit(1) 68 | 69 | try: 70 | with open(CONNTRACK_ACCT_FILE, 'w') as acct_file: 71 | acct_file.write('1') 72 | except (IOError, OSError) as e: 73 | if e.errno != errno.ENOENT: 74 | log.critical('Could not enable conntrack rule counters (check %s): %s' % (CONNTRACK_ACCT_FILE, e)) 75 | sys.exit(1) 76 | 77 | if options.systemd: 78 | from systemd.journal import JournalHandler 79 | log.set_handler(JournalHandler(SYSLOG_IDENTIFIER=name)) 80 | log.capture_output() 81 | elif options.fork: 82 | try: 83 | process.daemonize(pidfile='{}.pid'.format(name)) 84 | except ProcessError as e: 85 | log.critical('Cannot start %s: %s' % (fullname, e)) 86 | sys.exit(1) 87 | log.use_syslog(name) 88 | 89 | log.info('Starting %s %s' % (fullname, mediaproxy.__version__)) 90 | 91 | try: 92 | process.wait_for_network(wait_time=10, wait_message='Waiting for network to become available...') 93 | except KeyboardInterrupt: 94 | sys.exit(0) 95 | except RuntimeError as e: 96 | log.critical('Cannot start %s: %s' % (fullname, e)) 97 | sys.exit(1) 98 | 99 | try: 100 | from mediaproxy.relay import MediaRelay 101 | from mediaproxy.configuration import RelayConfig 102 | log.level.current = log.level.DEBUG if options.debug else RelayConfig.log_level 103 | if options.debug_memory: 104 | from application.debug.memory import memory_dump 105 | relay = MediaRelay() 106 | except Exception as e: 107 | log.critical('Failed to create %s: %s' % (fullname, e)) 108 | if type(e) is not RuntimeError: 109 | log.exception() 110 | sys.exit(1) 111 | 112 | relay.run() 113 | 114 | if options.debug_memory: 115 | memory_dump() 116 | -------------------------------------------------------------------------------- /mediaproxy/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = '4.1.0' 3 | 4 | # mediaproxy configuration and runtime settings 5 | mediaproxy_subdirectory = 'mediaproxy' 6 | configuration_file = 'config.ini' 7 | -------------------------------------------------------------------------------- /mediaproxy/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from application import log 3 | from application.configuration import ConfigSection, ConfigSetting 4 | from application.configuration.datatypes import IPAddress, LogLevel, NetworkRangeList 5 | from application.system import host 6 | 7 | from mediaproxy import configuration_file 8 | from mediaproxy.configuration.datatypes import AccountingModuleList, DispatcherIPAddress, DispatcherAddressList, DispatcherManagementAddress, PortRange, PositiveInteger, SIPThorDomain, X509NameValidator 9 | 10 | 11 | class DispatcherConfig(ConfigSection): 12 | __cfgfile__ = configuration_file 13 | __section__ = 'Dispatcher' 14 | 15 | socket_path = 'dispatcher.sock' 16 | listen = ConfigSetting(type=DispatcherIPAddress, value=DispatcherIPAddress('any')) 17 | listen_management = ConfigSetting(type=DispatcherManagementAddress, value=DispatcherManagementAddress('any')) 18 | relay_timeout = 5 # How much to wait for an answer from a relay 19 | relay_recover_interval = 60 # How much to wait for an unresponsive relay to recover, before disconnecting it 20 | cleanup_dead_relays_after = 43200 # 12 hours 21 | cleanup_expired_sessions_after = 86400 # 24 hours 22 | management_use_tls = True 23 | accounting = ConfigSetting(type=AccountingModuleList, value=[]) 24 | passport = ConfigSetting(type=X509NameValidator, value=None) 25 | management_passport = ConfigSetting(type=X509NameValidator, value=None) 26 | log_level = ConfigSetting(type=LogLevel, value=log.level.INFO) 27 | 28 | 29 | class RelayConfig(ConfigSection): 30 | __cfgfile__ = configuration_file 31 | __section__ = 'Relay' 32 | 33 | relay_ip = ConfigSetting(type=IPAddress, value=host.default_ip) 34 | advertised_ip = ConfigSetting(type=IPAddress, value=None) 35 | auto_detect_interfaces = False 36 | stream_timeout = 90 37 | on_hold_timeout = 7200 38 | traffic_sampling_period = 15 39 | userspace_transmit_every = 1 40 | dispatchers = ConfigSetting(type=DispatcherAddressList, value=[]) 41 | port_range = PortRange('40000:60000') 42 | dns_check_interval = PositiveInteger(60) 43 | keepalive_interval = PositiveInteger(10) 44 | reconnect_delay = PositiveInteger(10) 45 | passport = ConfigSetting(type=X509NameValidator, value=None) 46 | routable_private_ranges = ConfigSetting(type=NetworkRangeList, value=[]) 47 | log_level = ConfigSetting(type=LogLevel, value=log.level.INFO) 48 | 49 | 50 | class OpenSIPSConfig(ConfigSection): 51 | __cfgfile__ = configuration_file 52 | __section__ = 'OpenSIPS' 53 | 54 | socket_path = '/run/opensips/socket' 55 | location_table = 'location' 56 | 57 | 58 | class RadiusConfig(ConfigSection): 59 | __cfgfile__ = configuration_file 60 | __section__ = 'Radius' 61 | 62 | config_file = '/etc/opensips/radius/client.conf' 63 | additional_dictionary = 'radius/dictionary' 64 | 65 | 66 | class DatabaseConfig(ConfigSection): 67 | __cfgfile__ = configuration_file 68 | __section__ = 'Database' 69 | 70 | dburi = '' 71 | sessions_table = 'media_sessions' 72 | callid_column = 'call_id' 73 | fromtag_column = 'from_tag' 74 | totag_column = 'to_tag' 75 | info_column = 'info' 76 | 77 | 78 | class TLSConfig(ConfigSection): 79 | __cfgfile__ = configuration_file 80 | __section__ = 'TLS' 81 | 82 | certs_path = 'tls' 83 | verify_interval = 300 84 | 85 | 86 | class ThorNetworkConfig(ConfigSection): 87 | __cfgfile__ = configuration_file 88 | __section__ = 'ThorNetwork' 89 | 90 | domain = ConfigSetting(type=SIPThorDomain, value=None) 91 | node_ip = host.default_ip 92 | -------------------------------------------------------------------------------- /mediaproxy/configuration/datatypes.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | 4 | from application.configuration.datatypes import IPAddress, NetworkAddress, StringList 5 | from gnutls import crypto 6 | 7 | 8 | class DispatcherIPAddress(NetworkAddress): 9 | default_port = 25060 10 | 11 | 12 | class DispatcherManagementAddress(NetworkAddress): 13 | default_port = 25061 14 | 15 | 16 | class AccountingModuleList(StringList): 17 | _valid_backends = {'database', 'radius'} 18 | 19 | def __new__(cls, value): 20 | proposed_backends = set(StringList.__new__(cls, value)) 21 | return list(proposed_backends & cls._valid_backends) 22 | 23 | 24 | class DispatcherAddress(tuple): 25 | default_port = 25060 26 | 27 | def __new__(cls, value): 28 | match = re.search(r"^(?P
.+?):(?P\d+)$", value) 29 | if match: 30 | address = str(match.group("address")) 31 | port = int(match.group("port")) 32 | else: 33 | address = value 34 | port = cls.default_port 35 | try: 36 | address = IPAddress(address) 37 | is_domain = False 38 | except ValueError: 39 | is_domain = True 40 | return tuple.__new__(cls, (address, port, is_domain)) 41 | 42 | 43 | class DispatcherAddressList(list): 44 | def __init__(cls, value): 45 | list.__init__(cls, (DispatcherAddress(dispatcher) for dispatcher in re.split(r'\s*,\s*|\s+', value))) 46 | 47 | 48 | class PortRange(object): 49 | """A port range in the form start:end with start and end being even numbers in the [1024, 65536] range""" 50 | def __init__(self, value): 51 | self.start, self.end = [int(p) for p in value.split(':', 1)] 52 | allowed = range(1024, 65537, 2) 53 | if not (self.start in allowed and self.end in allowed and self.start < self.end): 54 | raise ValueError("bad range: %r: ports must be even numbers in the range [1024, 65536] with start < end" % value) 55 | def __repr__(self): 56 | return "%s('%d:%d')" % (self.__class__.__name__, self.start, self.end) 57 | 58 | 59 | class PositiveInteger(int): 60 | def __new__(cls, value): 61 | instance = int.__new__(cls, value) 62 | if instance < 1: 63 | raise ValueError("value must be a positive integer") 64 | return instance 65 | 66 | 67 | class SIPThorDomain(str): 68 | """A SIP Thor domain name or the keyword None""" 69 | def __new__(cls, name): 70 | if name is None: 71 | return None 72 | elif not isinstance(name, str): 73 | raise TypeError("domain name must be a string, unicode or None") 74 | if name.lower() == 'none': 75 | return None 76 | return name 77 | 78 | 79 | class X509NameValidator(crypto.X509Name): 80 | def __new__(cls, dname): 81 | if dname.lower() == 'none': 82 | return None 83 | return crypto.X509Name.__new__(cls, dname) 84 | 85 | def __init__(self, dname): 86 | str.__init__(self) 87 | pairs = [x.replace('\,', ',') for x in re.split(r'(?8 -----------------') 43 | sql, constraints = MediaSessions.createTableSQL() 44 | statements = ';\n'.join([sql] + constraints) + ';' 45 | log.info(statements) 46 | log.info('----------------- >8 -----------------') 47 | # raise RuntimeError(str(e)) 48 | 49 | 50 | class Accounting(object): 51 | def __init__(self): 52 | self.handler = DatabaseAccounting() 53 | 54 | def start(self): 55 | self.handler.start() 56 | 57 | def do_accounting(self, stats, session): 58 | self.handler.put(stats) 59 | 60 | def stop(self): 61 | self.handler.stop() 62 | self.handler.join() 63 | 64 | 65 | class DatabaseAccounting(EventQueue): 66 | def __init__(self): 67 | EventQueue.__init__(self, self.do_accounting) 68 | 69 | def do_accounting(self, stats, session): 70 | sqlrepr = connection.sqlrepr 71 | names = ', '.join([DatabaseConfig.callid_column, DatabaseConfig.fromtag_column, DatabaseConfig.totag_column, DatabaseConfig.info_column]) 72 | values = ', '.join((sqlrepr(v) for v in [stats['call_id'], stats['from_tag'], stats['to_tag'], json.dumps(stats)])) 73 | q = 'INSERT INTO %s (%s) VALUES (%s)' % (DatabaseConfig.sessions_table, names, values) 74 | try: 75 | try: 76 | connection.query(q) 77 | except ProgrammingError as e: 78 | try: 79 | MediaSessions.createTable(ifNotExists=True) 80 | except OperationalError: 81 | raise e 82 | else: 83 | connection.query(q) 84 | except DatabaseError as e: 85 | log.error('failed to insert record into database: %s' % e) 86 | 87 | -------------------------------------------------------------------------------- /mediaproxy/interfaces/accounting/radius.py: -------------------------------------------------------------------------------- 1 | 2 | """Implementation of RADIUS accounting""" 3 | 4 | from application import log 5 | from application.process import process 6 | from application.python.queue import EventQueue 7 | 8 | import pyrad.client 9 | import pyrad.dictionary 10 | import os 11 | 12 | from mediaproxy.configuration import RadiusConfig 13 | 14 | 15 | try: 16 | from pyrad.dictfile import DictFile 17 | except ImportError: 18 | # helper class to make pyrad support the $INCLUDE statement in dictionary files 19 | class RadiusDictionaryFile(object): 20 | def __init__(self, base_file_name): 21 | self.file_names = [base_file_name] 22 | self.fd_stack = [open(base_file_name)] 23 | 24 | def readlines(self): 25 | while True: 26 | line = self.fd_stack[-1].readline() 27 | if line: 28 | if line.startswith('$INCLUDE'): 29 | file_name = line.rstrip('\n').split(None, 1)[1] 30 | if file_name not in self.file_names: 31 | self.file_names.append(file_name) 32 | self.fd_stack.append(open(file_name)) 33 | continue 34 | else: 35 | yield line 36 | else: 37 | self.fd_stack.pop() 38 | if len(self.fd_stack) == 0: 39 | return 40 | else: 41 | del DictFile 42 | class RadiusDictionaryFile(str): 43 | pass 44 | 45 | 46 | class Accounting(object): 47 | 48 | def __init__(self): 49 | self.handler = RadiusAccounting() 50 | 51 | def start(self): 52 | self.handler.start() 53 | 54 | def do_accounting(self, stats, session): 55 | self.handler.put(stats) 56 | 57 | def stop(self): 58 | self.handler.stop() 59 | self.handler.join() 60 | 61 | 62 | class RadiusAccounting(EventQueue, pyrad.client.Client): 63 | 64 | def __init__(self): 65 | main_config_file = process.configuration.file(RadiusConfig.config_file) 66 | log.info('Loading Radius configuration file %s' % main_config_file) 67 | if main_config_file is None: 68 | raise RuntimeError('Cannot find the radius configuration file: %r' % RadiusConfig.config_file) 69 | try: 70 | config = dict(line.rstrip('\n').split(None, 1) for line in open(main_config_file) if len(line.split(None, 1)) == 2 and not line.startswith('#')) 71 | secrets = dict(line.rstrip('\n').split(None, 1) for line in open(config['servers']) if len(line.split(None, 1)) == 2 and not line.startswith('#')) 72 | server = config['acctserver'] 73 | try: 74 | server, acctport = server.split(':') 75 | acctport = int(acctport) 76 | except ValueError: 77 | acctport = 1813 78 | 79 | log.info('Using RADIUS server at %s:%d' % (server, acctport)) 80 | secret = secrets[server] 81 | log.info("Using RADIUS dictionary file %s" % config['dictionary']) 82 | dicts = [RadiusDictionaryFile(config['dictionary'])] 83 | if RadiusConfig.additional_dictionary: 84 | log.info("Using additional RADIUS dictionary file %s" % RadiusConfig.additional_dictionary) 85 | additional_dictionary = process.configuration.file(RadiusConfig.additional_dictionary) 86 | if additional_dictionary: 87 | dicts.append(RadiusDictionaryFile(additional_dictionary)) 88 | else: 89 | log.error("Radius dictionary file %s cannot be loaded" % RadiusConfig.additional_dictionary) 90 | raddict = pyrad.dictionary.Dictionary(*dicts) 91 | timeout = int(config['radius_timeout']) 92 | retries = int(config['radius_retries']) 93 | except Exception: 94 | log.critical('cannot read the RADIUS configuration file %s' % RadiusConfig.config_file) 95 | raise 96 | pyrad.client.Client.__init__(self, server, 1812, acctport, 3799, secret, raddict) 97 | self.timeout = timeout 98 | self.retries = retries 99 | if 'bindaddr' in config and config['bindaddr'] != '*': 100 | self.bind((config['bindaddr'], 0)) 101 | EventQueue.__init__(self, self.do_accounting) 102 | 103 | def do_accounting(self, stats, session): 104 | username = session.username if session.username else 'mediaproxy@default' 105 | attrs = {} 106 | attrs['Acct-Status-Type'] = 'Update' 107 | attrs['User-Name'] = username 108 | attrs['Acct-Session-Id'] = stats['call_id'] 109 | attrs['Acct-Session-Time'] = stats['duration'] 110 | attrs['Acct-Input-Octets'] = sum(stream_stats['caller_bytes'] for stream_stats in stats['streams']) 111 | attrs['Acct-Output-Octets'] = sum(stream_stats['callee_bytes'] for stream_stats in stats['streams']) 112 | attrs['Sip-From-Tag'] = stats['from_tag'] 113 | attrs['Sip-To-Tag'] = stats['to_tag'] or '' 114 | attrs['NAS-IP-Address'] = stats['streams'][0]['caller_local'].split(':')[0] 115 | attrs['Sip-User-Agents'] = (stats['caller_ua'] + '+' + stats['callee_ua'])[:253] 116 | attrs['Sip-Applications'] = ', '.join(sorted(set(stream['media_type'] for stream in stats['streams'] if stream['start_time'] != stream['end_time'])))[:253] 117 | attrs['Media-Codecs'] = ', '.join(stream['caller_codec'] for stream in stats['streams'])[:253] 118 | if stats['timed_out'] and not stats.get('all_streams_ice', False): 119 | attrs['Media-Info'] = 'timeout' 120 | elif stats.get('all_streams_ice', False): 121 | attrs['Media-Info'] = 'ICE session' 122 | else: 123 | attrs['Media-Info'] = '' 124 | for stream in stats['streams']: 125 | if stream['post_dial_delay'] is not None: 126 | attrs['Acct-Delay-Time'] = int(stream['post_dial_delay']) 127 | break 128 | if isinstance(self.secret, str): 129 | scr_bn = self.secret.encode('utf-8') 130 | self.secret = scr_bn 131 | try: 132 | self.SendPacket(self.CreateAcctPacket(**attrs)) 133 | except Exception as e: 134 | log.error('Failed to send radius accounting record: %s' % e) 135 | -------------------------------------------------------------------------------- /mediaproxy/interfaces/opensips.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | import socket 4 | import urllib.parse 5 | 6 | from abc import ABCMeta, abstractmethod, abstractproperty 7 | from application import log 8 | from application.python.types import Singleton 9 | from application.process import process 10 | from application.system import unlink 11 | from random import getrandbits 12 | from twisted.internet import reactor, defer 13 | from twisted.internet.protocol import DatagramProtocol 14 | from twisted.python.failure import Failure 15 | 16 | from mediaproxy.configuration import OpenSIPSConfig 17 | 18 | 19 | class Error(Exception): 20 | pass 21 | 22 | 23 | class TimeoutError(Error): 24 | pass 25 | 26 | 27 | class OpenSIPSError(Error): 28 | pass 29 | 30 | 31 | class NegativeReplyError(OpenSIPSError): 32 | def __init__(self, code, message): 33 | super(NegativeReplyError, self).__init__(code, message) 34 | self.code = code 35 | self.message = message 36 | 37 | def __repr__(self): 38 | return '{0.__class__.__name__}({0.code!r}, {0.message!r})'.format(self) 39 | 40 | def __str__(self): 41 | return '[{0.code}] {0.message}'.format(self) 42 | 43 | 44 | class Request(object, metaclass=ABCMeta): 45 | method = abstractproperty() 46 | 47 | @abstractmethod 48 | def __init__(self, *args): 49 | self.id = '{:x}'.format(getrandbits(32)) 50 | self.args = list(args) 51 | self.deferred = defer.Deferred() 52 | 53 | @property 54 | def __data__(self): 55 | return dict(jsonrpc='2.0', id=self.id, method=self.method, params=self.args) 56 | 57 | @abstractmethod 58 | def process_response(self, response): 59 | raise NotImplementedError 60 | 61 | 62 | # noinspection PyAbstractClass 63 | class BooleanRequest(Request): 64 | """A request that returns True if successful, False otherwise""" 65 | def process_response(self, response): 66 | return not isinstance(response, Failure) 67 | 68 | 69 | class AddressReload(BooleanRequest): 70 | method = 'address_reload' 71 | 72 | def __init__(self): 73 | super(AddressReload, self).__init__() 74 | 75 | 76 | class DomainReload(BooleanRequest): 77 | method = 'domain_reload' 78 | 79 | def __init__(self): 80 | super(DomainReload, self).__init__() 81 | 82 | 83 | class EndDialog(BooleanRequest): 84 | method = 'dlg_end_dlg' 85 | 86 | def __init__(self, dialog_id): 87 | super(EndDialog, self).__init__(dialog_id) 88 | 89 | 90 | class RefreshWatchers(BooleanRequest): 91 | method = 'refresh_watchers' 92 | 93 | def __init__(self, account, refresh_type): 94 | super(RefreshWatchers, self).__init__('sip:{}'.format(account), 'presence', refresh_type) 95 | 96 | 97 | class UpdateSubscriptions(BooleanRequest): 98 | method = 'rls_update_subscriptions' 99 | 100 | def __init__(self, account): 101 | super(UpdateSubscriptions, self).__init__('sip:{}'.format(account)) 102 | 103 | 104 | class GetOnlineDevices(Request): 105 | method = 'ul_show_contact' 106 | 107 | def __init__(self, account): 108 | super(GetOnlineDevices, self).__init__(OpenSIPSConfig.location_table, account) 109 | 110 | def process_response(self, response): 111 | if isinstance(response, Failure): 112 | if response.type is NegativeReplyError and response.value.code == 404: 113 | return [] 114 | return response 115 | return [ContactData(contact) for contact in response['Contacts']] 116 | 117 | 118 | class ContactData(dict): 119 | __fields__ = {'contact', 'expires', 'received', 'user_agent'} 120 | 121 | def __init__(self, data): 122 | super(ContactData, self).__init__({key: value for key, value in ((key.lower().replace('-', '_'), value) for key, value in data.items()) if key in self.__fields__}) 123 | self.setdefault('user_agent', None) 124 | if 'received' in self: 125 | parsed_received = urllib.parse.parse_qs(self['received']) 126 | if 'target' in parsed_received: 127 | self['NAT_contact'] = parsed_received['target'][0] 128 | else: 129 | self['NAT_contact'] = self['received'] 130 | del self['received'] 131 | else: 132 | self['NAT_contact'] = self['contact'] 133 | 134 | 135 | class UNIXSocketProtocol(DatagramProtocol): 136 | noisy = False 137 | 138 | def datagramReceived(self, data, address): 139 | log.debug('Got MI response: {}'.format(data)) 140 | try: 141 | response = json.loads(data) 142 | except ValueError: 143 | code, _, message = data.partition(' ') 144 | try: 145 | code = int(code) 146 | except ValueError: 147 | log.error('MI response from OpenSIPS cannot be parsed (neither JSON nor status reply)') 148 | return 149 | # we got one of the 'code message' type of replies. This means either parsing error or internal error in OpenSIPS. 150 | # if we only have one request pending, we can associate the response with it, otherwise is impossible to tell to 151 | # which request the response corresponds. The failed request will fail with timeout later. 152 | if len(self.transport.requests) == 1: 153 | _, request = self.transport.requests.popitem() 154 | request.deferred.errback(Failure(NegativeReplyError(code, message))) 155 | log.error('MI request {.method} failed with: {} {}'.format(request, code, message)) 156 | else: 157 | log.error('Got MI status reply from OpenSIPS that cannot be associated with a request: {!r}'.format(data)) 158 | else: 159 | try: 160 | request_id = response['id'] 161 | except KeyError: 162 | log.error('MI JSON response from OpenSIPS lacks id field') 163 | return 164 | if request_id not in self.transport.requests: 165 | log.error('MI JSON response from OpenSIPS has unknown id: {!r}'.format(request_id)) 166 | return 167 | request = self.transport.requests.pop(request_id) 168 | if 'result' in response: 169 | request.deferred.callback(response['result']) 170 | elif 'error' in response: 171 | log.error('MI request {0.method} failed with: {1[error][code]} {1[error][message]}'.format(request, response)) 172 | request.deferred.errback(Failure(NegativeReplyError(response['error']['code'], response['error']['message']))) 173 | else: 174 | log.error('Invalid MI JSON response from OpenSIPS') 175 | request.deferred.errback(Failure(OpenSIPSError('Invalid MI JSON response from OpenSIPS'))) 176 | 177 | 178 | class UNIXSocketConnection(object): 179 | timeout = 3 180 | 181 | def __init__(self): 182 | socket_path = process.runtime.file('opensips.sock') 183 | unlink(socket_path) 184 | self.path = socket_path 185 | self.transport = reactor.listenUNIXDatagram(self.path, UNIXSocketProtocol()) 186 | self.transport.requests = {} 187 | reactor.addSystemEventTrigger('during', 'shutdown', self.close) 188 | 189 | def close(self): 190 | for request in list(self.transport.requests.values()): 191 | if not request.deferred.called: 192 | request.deferred.errback(Error('shutting down')) 193 | self.transport.requests.clear() 194 | self.transport.stopListening() 195 | unlink(self.path) 196 | 197 | def send(self, request): 198 | try: 199 | self.transport.write(json.dumps(request.__data__).encode(), OpenSIPSConfig.socket_path) 200 | except socket.error as e: 201 | log.error("cannot write request to %s: %s" % (OpenSIPSConfig.socket_path, e[1])) 202 | request.deferred.errback(Failure(Error("Cannot send MI request %s to OpenSIPS" % request.method))) 203 | else: 204 | self.transport.requests[request.id] = request 205 | request.deferred.addBoth(request.process_response) 206 | reactor.callLater(self.timeout, self._did_timeout, request) 207 | log.debug('Send MI request: {}'.format(request.__data__)) 208 | return request.deferred 209 | 210 | def _did_timeout(self, request): 211 | if not request.deferred.called: 212 | request.deferred.errback(Failure(TimeoutError("OpenSIPS command did timeout"))) 213 | self.transport.requests.pop(request.id) 214 | 215 | 216 | class ManagementInterface(object, metaclass=Singleton): 217 | def __init__(self): 218 | self.connection = UNIXSocketConnection() 219 | 220 | def reload_domains(self): 221 | return self.connection.send(DomainReload()) 222 | 223 | def reload_addresses(self): 224 | return self.connection.send(AddressReload()) 225 | 226 | def end_dialog(self, dialog_id): 227 | return self.connection.send(EndDialog(dialog_id)) 228 | 229 | def get_online_devices(self, account): 230 | return self.connection.send(GetOnlineDevices(account)) 231 | 232 | def refresh_watchers(self, account, refresh_type): 233 | return self.connection.send(RefreshWatchers(account, refresh_type)) 234 | 235 | def update_subscriptions(self, account): 236 | return self.connection.send(UpdateSubscriptions(account)) 237 | -------------------------------------------------------------------------------- /mediaproxy/interfaces/system/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | """Interfaces to interact with the underlying operating system""" 3 | 4 | -------------------------------------------------------------------------------- /mediaproxy/iputils.py: -------------------------------------------------------------------------------- 1 | 2 | """IP address utilities""" 3 | 4 | __all__ = ["is_routable_ip"] 5 | 6 | import socket 7 | import struct 8 | 9 | from application.configuration.datatypes import NetworkRangeList 10 | 11 | from mediaproxy.configuration import RelayConfig 12 | 13 | 14 | # Non routable network addresses (RFC 6890) 15 | # 16 | _non_routable_netlist = [ 17 | '0.0.0.0/8', 18 | '10.0.0.0/8', 19 | '100.64.0.0/10', 20 | '127.0.0.0/8', 21 | '169.254.0.0/16', 22 | '172.16.0.0/12', 23 | '192.0.0.0/24', 24 | '192.0.2.0/24', 25 | '192.88.99.0/24', 26 | '192.168.0.0/16', 27 | '198.18.0.0/15', 28 | '198.51.100.0/24', 29 | '203.0.113.0/24', 30 | '224.0.0.0/4', 31 | '240.0.0.0/4', 32 | '255.255.255.255/32' 33 | ] 34 | 35 | _non_routable_nets = NetworkRangeList(_non_routable_netlist) 36 | 37 | 38 | def is_routable_ip(ip): 39 | try: 40 | ip_addr = struct.unpack('!L', socket.inet_aton(ip))[0] 41 | except: 42 | return False 43 | for netbase, mask in RelayConfig.routable_private_ranges: 44 | if (ip_addr & mask) == netbase: 45 | return True 46 | for netbase, mask in _non_routable_nets: 47 | if (ip_addr & mask) == netbase: 48 | return False 49 | return True 50 | 51 | -------------------------------------------------------------------------------- /mediaproxy/scheduler.py: -------------------------------------------------------------------------------- 1 | 2 | """Schedule calls on the twisted reactor""" 3 | 4 | 5 | __all__ = ['RecurrentCall', 'KeepRunning'] 6 | 7 | 8 | from time import time 9 | 10 | 11 | class KeepRunning: 12 | """Return this class from a recurrent function to indicate that it should keep running""" 13 | pass 14 | 15 | class RecurrentCall(object): 16 | """Execute a function repeatedly at the given interval, until signaled to stop""" 17 | def __init__(self, period, func, *args, **kwargs): 18 | from twisted.internet import reactor 19 | self.func = func 20 | self.args = args 21 | self.kwargs = kwargs 22 | self.period = period 23 | self.now = None 24 | self.next = None 25 | self.callid = reactor.callLater(period, self) 26 | def __call__(self): 27 | from twisted.internet import reactor 28 | self.callid = None 29 | if self.now is None: 30 | self.now = time() 31 | self.next = self.now + self.period 32 | else: 33 | self.now, self.next = self.next, self.next + self.period 34 | result = self.func(*self.args, **self.kwargs) 35 | if result is KeepRunning: 36 | delay = max(self.next - time(), 0) 37 | self.callid = reactor.callLater(delay, self) 38 | def cancel(self): 39 | if self.callid is not None: 40 | try: 41 | self.callid.cancel() 42 | except ValueError: 43 | pass 44 | self.callid = None 45 | 46 | 47 | -------------------------------------------------------------------------------- /mediaproxy/sipthor.py: -------------------------------------------------------------------------------- 1 | 2 | """SIP Thor backend""" 3 | 4 | from application import log 5 | from application.python.queue import EventQueue 6 | from gnutls.interfaces.twisted import TLSContext 7 | from thor.entities import ThorEntities, GenericThorEntity 8 | from thor.eventservice import EventServiceClient, ThorEvent 9 | from thor.scheduler import RecurrentCall, KeepRunning 10 | from thor.tls import X509Credentials 11 | 12 | from mediaproxy import __version__ 13 | from mediaproxy.configuration import ThorNetworkConfig 14 | from mediaproxy.configuration.datatypes import DispatcherIPAddress 15 | from mediaproxy.relay import SRVMediaRelayBase 16 | 17 | 18 | if ThorNetworkConfig.domain is None: 19 | # SIP Thor is installed but disabled. Fake an ImportError to start in standalone media relay mode. 20 | log.warning('SIP Thor is installed but disabled from the configuration') 21 | raise ImportError('SIP Thor is disabled') 22 | 23 | 24 | # Tasks 25 | # 26 | class Task(object): 27 | def __init__(self, action, data=None, **kwargs): 28 | self.action = action 29 | self.data = data 30 | for name in kwargs: 31 | setattr(self, name, kwargs[name]) 32 | 33 | def __str__(self): 34 | return "%s %s" % (self.action, self.data) 35 | 36 | # noinspection PyAbstractClass 37 | class SIPThorMediaRelayBase(SRVMediaRelayBase, EventServiceClient): 38 | topics = ['Thor.Members'] 39 | 40 | def __init__(self): 41 | self.node = GenericThorEntity(ThorNetworkConfig.node_ip, ['media_relay'], version=__version__) 42 | self.presence_message = ThorEvent('Thor.Presence', self.node.id) 43 | self.shutdown_message = ThorEvent('Thor.Leave', self.node.id) 44 | self.sipthor_dispatchers = [] 45 | self.additional_dispatchers = [] 46 | credentials = X509Credentials(cert_name='relay') 47 | tls_context = TLSContext(credentials) 48 | EventServiceClient.__init__(self, ThorNetworkConfig.domain, tls_context) 49 | SRVMediaRelayBase.__init__(self) 50 | 51 | self.statistics_task = RecurrentCall(30, self._notify_statistics) 52 | self.statistics = {} 53 | self.task_queue = EventQueue(handler=self.handle_tasks, name='TaskHandler') 54 | self.task_queue.start() 55 | 56 | def handle_tasks(self, task): 57 | """Handle the Thor network events (node join, node leave, expirations and notifications)""" 58 | if not isinstance(task, Task): 59 | # This shouldn't happen as they are internal tasks, but log the error to catch programming mistakes. 60 | log.error("handle_task received a non-Task entity of type '%s' (ignored)" % str(type(task))) 61 | return 62 | try: 63 | handler = getattr(self, '_TH_%s' % task.action) 64 | except AttributeError: 65 | log.error("no handler for task '%s'" % task.action) 66 | return 67 | try: 68 | handler(task) 69 | except Exception: 70 | log.exception("captured unhandled exception while processing task: %s" % task) 71 | 72 | def _notify_statistics(self): 73 | """Periodic usage publication""" 74 | if self.disconnecting: 75 | return 76 | self.task_queue.put(Task('publish_statistics')) 77 | return KeepRunning 78 | 79 | def _TH_publish_statistics(self, task): 80 | pass 81 | 82 | def handle_event(self, event): 83 | if not self.shutting_down: 84 | sip_proxy_ips = [node.ip for node in ThorEntities(event.message, role='sip_proxy')] 85 | self.sipthor_dispatchers = [(ip, DispatcherIPAddress.default_port) for ip in sip_proxy_ips] 86 | self.update_dispatchers(self.sipthor_dispatchers + self.additional_dispatchers) 87 | 88 | def _cb_got_all(self, results): 89 | if not self.shutting_down: 90 | self.additional_dispatchers = [result[1] for result in results if result[0] and result[1] is not None] 91 | self.update_dispatchers(self.sipthor_dispatchers + self.additional_dispatchers) 92 | 93 | def _shutdown_done(self): 94 | EventServiceClient._shutdown(self) 95 | -------------------------------------------------------------------------------- /mediaproxy/tls.py: -------------------------------------------------------------------------------- 1 | 2 | """TLS support""" 3 | 4 | __all__ = ['X509Credentials'] 5 | 6 | import os 7 | import stat 8 | 9 | from application import log 10 | from application.process import process 11 | from gnutls import crypto 12 | from gnutls.interfaces import twisted 13 | 14 | from mediaproxy.configuration import TLSConfig 15 | 16 | 17 | class FileDescriptor(object): 18 | def __init__(self, name, type): 19 | certs_path = os.path.normpath(TLSConfig.certs_path) 20 | self.path = os.path.join(certs_path, name) 21 | self.klass = type 22 | self.timestamp = 0 23 | self.object = None 24 | def get(self): 25 | path = process.configuration.file(self.path) 26 | if path is None: 27 | raise RuntimeError('missing or unreadable file: %s' % self.path) 28 | mtime = os.stat(path)[stat.ST_MTIME] 29 | if self.timestamp < mtime: 30 | f = open(path) 31 | try: 32 | self.object = self.klass(f.read()) 33 | self.timestamp = mtime 34 | finally: 35 | f.close() 36 | return self.object 37 | 38 | 39 | class X509Entity(object): 40 | type = None 41 | 42 | def __init__(self, name_attr): 43 | self.name_attr = name_attr 44 | self.descriptors = {} 45 | 46 | def __get__(self, obj, type_=None): 47 | name = getattr(obj or type_, self.name_attr, None) 48 | if name is None: 49 | return None 50 | certs_path = os.path.normpath(TLSConfig.certs_path) 51 | log.debug("Loaded %s file from %s/%s" % (self.name_attr, certs_path, name)) 52 | descriptor = self.descriptors.setdefault(name, FileDescriptor(name, self.type)) 53 | return descriptor.get() 54 | 55 | def __set__(self, obj, value): 56 | raise AttributeError('cannot set attribute') 57 | 58 | def __delete__(self, obj): 59 | raise AttributeError('cannot delete attribute') 60 | 61 | 62 | class X509Certificate(X509Entity): 63 | type = crypto.X509Certificate 64 | 65 | 66 | class X509PrivateKey(X509Entity): 67 | type = crypto.X509PrivateKey 68 | 69 | 70 | class X509CRL(X509Entity): 71 | type = crypto.X509CRL 72 | 73 | 74 | class X509Credentials(twisted.X509Credentials): 75 | """SIPThor X509 credentials""" 76 | 77 | X509cert_name = None # will be defined by each instance 78 | X509key_name = None # will be defined by each instance 79 | X509ca_name = 'ca.pem' 80 | X509crl_name = 'crl.pem' 81 | 82 | X509cert = X509Certificate(name_attr='X509cert_name') 83 | X509key = X509PrivateKey(name_attr='X509key_name') 84 | X509ca = X509Certificate(name_attr='X509ca_name') 85 | X509crl = X509CRL(name_attr='X509crl_name') 86 | 87 | def __init__(self, cert_name): 88 | self.X509cert_name = '%s.crt' % cert_name 89 | self.X509key_name = '%s.key' % cert_name 90 | log.info("Loading TLS certificate passport for %s of %s (%s)" % (self.X509cert.subject.common_name, self.X509cert.subject.organization_unit, self.X509cert.subject.organization)) 91 | twisted.X509Credentials.__init__(self, self.X509cert, self.X509key, [self.X509ca], [self.X509crl]) 92 | self.verify_peer = True 93 | self.verify_period = TLSConfig.verify_interval 94 | -------------------------------------------------------------------------------- /radius/dictionary: -------------------------------------------------------------------------------- 1 | # 2 | # Radius dictionary for mediaproxy. 3 | # 4 | 5 | ### Acct-Status-Type Values ### 6 | VALUE Acct-Status-Type Update 3 # RFC2866, acc 7 | 8 | ### Attributes added by AG Projects ### 9 | ATTRIBUTE Sip-User-Agents 235 string # AG Projects 10 | ATTRIBUTE Sip-Applications 236 string # AG Projects 11 | ATTRIBUTE Media-Codecs 233 string # AG Projects 12 | ATTRIBUTE Media-Info 234 string # AG Projects 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import re 4 | import sys 5 | import mediaproxy 6 | 7 | from distutils.core import setup, Extension 8 | 9 | # Get the title and description from README 10 | readme = open('README').read() 11 | title, description = re.findall(r'^\s*([^\n]+)\s+(.*)$', readme, re.DOTALL)[0] 12 | 13 | # media-relay is not supported on non-linux platforms 14 | # 15 | if 'linux' in sys.platform: 16 | scripts = ['media-relay', 'media-dispatcher'] 17 | ext_modules = [Extension(name='mediaproxy.interfaces.system._conntrack', 18 | sources=['mediaproxy/interfaces/system/_conntrack.c'], 19 | libraries=['netfilter_conntrack', 'ip4tc'], 20 | define_macros=[('MODULE_VERSION', mediaproxy.__version__)])] 21 | else: 22 | print('WARNING: skipping the media relay component as this is a non-linux platform') 23 | scripts = ['media-dispatcher'] 24 | ext_modules = [] 25 | 26 | 27 | setup( 28 | name='mediaproxy', 29 | version=mediaproxy.__version__, 30 | 31 | description=title, 32 | long_description=description, 33 | url='http://mediaproxy.ag-projects.com', 34 | 35 | author='AG Projects', 36 | author_email='support@ag-projects.com', 37 | 38 | license='GPLv2', 39 | platforms=['Linux'], 40 | 41 | classifiers=[ 42 | 'Development Status :: 5 - Production/Stable', 43 | 'Intended Audience :: Service Providers', 44 | 'License :: GNU General Public License (GPLv2)', 45 | 'Operating System :: POSIX :: Linux', 46 | 'Programming Language :: Python', 47 | 'Programming Language :: C' 48 | ], 49 | 50 | packages=['mediaproxy', 'mediaproxy.configuration', 'mediaproxy.interfaces', 'mediaproxy.interfaces.accounting', 'mediaproxy.interfaces.system'], 51 | data_files=[('/etc/mediaproxy', ['config.ini.sample']), ('/etc/mediaproxy/radius', ['radius/dictionary']), ('/etc/mediaproxy/tls', ['tls/README'])], 52 | scripts=scripts, 53 | ext_modules=ext_modules 54 | ) 55 | -------------------------------------------------------------------------------- /test/common.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 AG Projects 2 | # 3 | 4 | import sys; sys.path.extend(['.', '..']) 5 | import os 6 | import random 7 | import string 8 | import struct 9 | 10 | import mediaproxy 11 | 12 | from application.configuration import * 13 | from application.process import process 14 | from application.system import host 15 | from twisted.internet import reactor 16 | from twisted.internet.defer import Deferred, DeferredList, succeed 17 | from twisted.internet.protocol import DatagramProtocol, ClientFactory 18 | from twisted.internet.task import LoopingCall 19 | from twisted.protocols.basic import LineOnlyReceiver 20 | 21 | from mediaproxy.headers import EncodingDict 22 | 23 | 24 | process.configuration.user_directory = None 25 | process.configuration.subdirectory = mediaproxy.mediaproxy_subdirectory 26 | 27 | 28 | class Config(ConfigSection): 29 | __cfgfile__ = mediaproxy.configuration_file 30 | __section__ = 'Dispatcher' 31 | 32 | socket = '/run/mediaproxy/dispatcher.sock' 33 | 34 | 35 | random_data = os.urandom(512) 36 | stun_data = struct.pack('!HHIIII', 0x0001, 0, 0x2112A442, 0, 0, 0) 37 | default_host_ip = host.default_ip 38 | 39 | 40 | class OpenSIPSControlClientProtocol(LineOnlyReceiver): 41 | 42 | def __init__(self): 43 | self.defer = None 44 | 45 | def lineReceived(self, line): 46 | line = line.decode() 47 | if line == 'error': 48 | print('got error from dispatcher!') 49 | reactor.stop() 50 | elif self.defer is not None: 51 | print(('got ip/ports from dispatcher: %s' % line)) 52 | ip, ports = line.split(' ', 1) 53 | defer = self.defer 54 | self.defer = None 55 | defer.callback((ip, [int(i) for i in ports.split()])) 56 | else: 57 | print(('got reply from dispatcher: %s' % line)) 58 | defer = self.defer 59 | self.defer = None 60 | defer.callback(line) 61 | 62 | def _send_command(self, command, headers): 63 | self.defer = Deferred() 64 | data = self.delimiter.decode().join([command] + ['%s: %s' % item for item in headers.items()]) + 2 * self.delimiter.decode() 65 | print('writing on socket:\n%s' % data) 66 | self.transport.write(data.encode()) 67 | return self.defer 68 | 69 | def update(self, **kw_args): 70 | return self._send_command('update', EncodingDict(kw_args)) 71 | 72 | def remove(self, **kw_args): 73 | return self._send_command('remove', EncodingDict(kw_args)) 74 | 75 | 76 | class OpenSIPSConnectorFactory(ClientFactory): 77 | protocol = OpenSIPSControlClientProtocol 78 | 79 | def __init__(self): 80 | self.defer = Deferred() 81 | 82 | def buildProtocol(self, addr): 83 | prot = ClientFactory.buildProtocol(self, addr) 84 | reactor.callLater(0, self.defer.callback, prot) 85 | return prot 86 | 87 | 88 | class MediaReceiverProtocol(DatagramProtocol): 89 | 90 | def __init__(self, endpoint, index): 91 | self.endpoint = endpoint 92 | self.index = index 93 | self.loop = None 94 | self.received_media = False 95 | self.defer = Deferred() 96 | 97 | def datagramReceived(self, data, addr): 98 | (host, port) = addr 99 | if not self.received_media: 100 | self.received_media = True 101 | print(('received media %d for %s from %s:%d' % (self.index, self.endpoint.name, host, port))) 102 | self.defer.callback(None) 103 | 104 | def connectionRefused(self): 105 | print(('connection refused for media %d for %s' % (self.index, self.endpoint.name))) 106 | 107 | 108 | class Endpoint(object): 109 | 110 | def __init__(self, sip_uri, user_agent, is_caller): 111 | if is_caller: 112 | self.name = 'caller' 113 | else: 114 | self.name = 'callee' 115 | self.sip_uri = sip_uri 116 | self.user_agent = user_agent 117 | self.tag = ''.join(random.sample(string.ascii_lowercase, 8)) 118 | self.connectors = [] 119 | self.media = [] 120 | self.cseq = 1 121 | 122 | def set_media(self, media): 123 | assert(len(self.connectors) == 0) 124 | self.media = media 125 | for index, (media_type, port, direction, parameters) in enumerate(self.media): 126 | if port != 0: 127 | protocol = MediaReceiverProtocol(self, index) 128 | connector = reactor.listenUDP(port, protocol) 129 | else: 130 | connector = None 131 | self.connectors.append(connector) 132 | return DeferredList([connector.protocol.defer for connector in self.connectors if connector is not None]) 133 | 134 | def get_media(self, use_old_hold): 135 | if use_old_hold: 136 | ip = '0.0.0.0' 137 | else: 138 | ip = default_host_ip 139 | return [(media_type, ip, port, direction, parameters) for media_type, port, direction, parameters in self.media] 140 | 141 | def start_media(self, ip, ports, send_stun=False): 142 | for port, connector in zip(ports, self.connectors): 143 | if connector is not None: 144 | protocol = connector.protocol 145 | if port != 0: 146 | protocol.transport.connect(ip, port) 147 | protocol.loop = LoopingCall(protocol.transport.write, send_stun and stun_data or random_data) 148 | protocol.loop.start(random.uniform(0.5, 1)) 149 | else: 150 | protocol.defer.callback(None) 151 | 152 | def stop_media(self): 153 | defers = [] 154 | for connector in self.connectors: 155 | if connector is not None: 156 | if connector.protocol.loop is not None: 157 | connector.protocol.loop.stop() 158 | connector.protocol.loop = None 159 | defer = connector.stopListening() 160 | if defer is not None: 161 | defers.append(defer) 162 | self.connectors = [] 163 | if defers: 164 | return DeferredList(defers) 165 | else: 166 | return succeed(None) 167 | 168 | 169 | class Session(object): 170 | 171 | def __init__(self, caller, callee): 172 | self.caller = caller 173 | self.callee = callee 174 | self.call_id = ''.join(random.sample(string.ascii_letters, 24)) 175 | 176 | def _get_parties(self, party): 177 | party = getattr(self, party) 178 | if party is self.caller: 179 | other = self.callee 180 | else: 181 | other = self.caller 182 | return party, other 183 | 184 | def do_update(self, opensips, party, type, is_final, use_old_hold=False): 185 | party, other = self._get_parties(party) 186 | if type == 'request': 187 | from_tag = party.tag 188 | to_tag = other.tag 189 | from_uri = party.sip_uri 190 | to_uri = other.sip_uri 191 | cseq = party.cseq 192 | else: 193 | from_tag = other.tag 194 | to_tag = party.tag 195 | from_uri = other.sip_uri 196 | to_uri = party.sip_uri 197 | cseq = other.cseq 198 | if is_final: 199 | defer = opensips.update(call_id=self.call_id, from_tag=from_tag, to_tag=to_tag, from_uri=from_uri, to_uri=to_uri, cseq=cseq, user_agent=party.user_agent, media=party.get_media(use_old_hold), type=type, dialog_id='1234567890') 200 | else: 201 | defer = opensips.update(call_id=self.call_id, from_tag=from_tag, to_tag=to_tag, from_uri=from_uri, to_uri=to_uri, cseq=cseq, user_agent=party.user_agent, media=party.get_media(use_old_hold), type=type, dialog_id='1234567890') 202 | if is_final: 203 | if type == 'request': 204 | party.cseq += 1 205 | else: 206 | other.cseq += 1 207 | return defer 208 | 209 | def do_remove(self, opensips, party): 210 | party, other = self._get_parties(party) 211 | opensips.remove(call_id=self.call_id, from_tag=party.tag, to_tag=other.tag) 212 | 213 | 214 | def connect_to_dispatcher(): 215 | factory = OpenSIPSConnectorFactory() 216 | connector = reactor.connectUNIX(Config.socket, factory) 217 | return connector, factory.defer 218 | -------------------------------------------------------------------------------- /test/holdtest1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a session that starts with 1 audio stream, then gets put 8 | on hold by the caller for 5 minutes, the gets taken out of hold again. This 9 | test uses the newer 'sendonly' direction attribute to indicate hold status. 10 | """ 11 | 12 | from common import * 13 | 14 | 15 | def phase1(protocol, session): 16 | print('setting up audio stream') 17 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 18 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {})]) 19 | media_defer = DeferredList([caller_media, callee_media]) 20 | defer = succeed(None) 21 | defer.addCallback(caller_update, protocol, session, media_defer, phase2) 22 | return defer 23 | 24 | 25 | def phase2(result, protocol, session): 26 | print('setting stream on hold') 27 | session.caller.set_media([('audio', 40000, 'sendonly', {})]) 28 | session.callee.set_media([('audio', 30000, 'recvonly', {})]) 29 | defer = session.do_update(protocol, 'caller', 'request', False) 30 | defer.addCallback(callee_update_hold, protocol, session) 31 | return defer 32 | 33 | 34 | def callee_update_hold(result, protocol, session): 35 | print('updating hold for callee') 36 | defer = session.do_update(protocol, 'callee', 'reply', True) 37 | defer.addCallback(wait_hold, protocol, session) 38 | return defer 39 | 40 | 41 | def wait_hold(result, protocol, session): 42 | print('on hold, waiting 5 minutes...') 43 | defer = Deferred() 44 | defer.addCallback(stop_media_hold, protocol, session) 45 | reactor.callLater(300, defer.callback, None) 46 | return defer 47 | 48 | 49 | def stop_media_hold(result, protocol, session): 50 | print('stopping media for hold') 51 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 52 | defer.addCallback(phase3, protocol, session) 53 | return defer 54 | 55 | 56 | def phase3(result, protocol, session): 57 | print('continuing audio stream') 58 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 59 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {})]) 60 | media_defer = DeferredList([caller_media, callee_media]) 61 | defer = succeed(None) 62 | defer.addCallback(caller_update, protocol, session, media_defer, kthxbye) 63 | return defer 64 | 65 | 66 | def caller_update(result, protocol, session, media_defer, do_after): 67 | print('doing update for caller') 68 | defer = session.do_update(protocol, 'caller', 'request', False) 69 | defer.addCallback(callee_update, protocol, session, media_defer, do_after) 70 | return defer 71 | 72 | 73 | def callee_update(callee_addr, protocol, session, media_defer, do_after): 74 | print('doing update for callee') 75 | defer = session.do_update(protocol, 'callee', 'reply', True) 76 | defer.addCallback(do_media, callee_addr, protocol, session, media_defer, do_after) 77 | return defer 78 | 79 | 80 | def do_media(caller_addr, callee_addr, protocol, session, media_defer, do_after): 81 | (caller_ip, caller_ports) = caller_addr 82 | (callee_ip, callee_ports) = callee_addr 83 | print('starting media for both parties') 84 | session.caller.start_media(caller_ip, caller_ports) 85 | session.callee.start_media(callee_ip, callee_ports) 86 | media_defer.addCallback(wait, protocol, session, do_after) 87 | return media_defer 88 | 89 | 90 | def wait(result, protocol, session, do_after): 91 | print('got media, waiting 5 seconds') 92 | defer = Deferred() 93 | defer.addCallback(stop_media, protocol, session, do_after) 94 | reactor.callLater(5, defer.callback, None) 95 | return defer 96 | 97 | 98 | def stop_media(result, protocol, session, do_after): 99 | print('stopping media') 100 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 101 | defer.addCallback(do_after, protocol, session) 102 | return defer 103 | 104 | 105 | def kthxbye(result, protocol, session): 106 | print('sending remove') 107 | return session.do_remove(protocol, 'caller') 108 | 109 | 110 | def disconnect(result, connector): 111 | print('disconnecting') 112 | connector.disconnect() 113 | reactor.callLater(1, reactor.stop) 114 | 115 | 116 | def catch_all_err(failure): 117 | print(failure) 118 | 119 | 120 | if __name__ == '__main__': 121 | caller = Endpoint('Alice ', 'Caller UA', True) 122 | callee = Endpoint('Bob ', 'Callee UA', False) 123 | session = Session(caller, callee) 124 | connector, defer = connect_to_dispatcher() 125 | defer.addCallback(phase1, session) 126 | defer.addCallback(disconnect, connector) 127 | defer.addErrback(catch_all_err) 128 | reactor.run() 129 | -------------------------------------------------------------------------------- /test/holdtest2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a session that starts with 1 audio stream, then gets put 8 | on hold by the caller for 5 minutes, the gets taken out of hold again. This 9 | test uses the older 0.0.0.0 IP address to indicate hold status. 10 | """ 11 | 12 | from common import * 13 | 14 | 15 | def phase1(protocol, session): 16 | print('setting up audio stream') 17 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 18 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {})]) 19 | media_defer = DeferredList([caller_media, callee_media]) 20 | defer = succeed(None) 21 | defer.addCallback(caller_update, protocol, session, media_defer, phase2) 22 | return defer 23 | 24 | 25 | def phase2(result, protocol, session): 26 | print('setting stream on hold') 27 | session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 28 | session.callee.set_media([('audio', 30000, 'sendrecv', {})]) 29 | defer = session.do_update(protocol, 'caller', 'request', False, True) 30 | defer.addCallback(callee_update_hold, protocol, session) 31 | return defer 32 | 33 | 34 | def callee_update_hold(result, protocol, session): 35 | print('updating hold for callee') 36 | defer = session.do_update(protocol, 'callee', 'reply', True, True) 37 | defer.addCallback(wait_hold, protocol, session) 38 | return defer 39 | 40 | 41 | def wait_hold(result, protocol, session): 42 | print('on hold, waiting 5 minutes...') 43 | defer = Deferred() 44 | defer.addCallback(stop_media_hold, protocol, session) 45 | reactor.callLater(300, defer.callback, None) 46 | return defer 47 | 48 | 49 | def stop_media_hold(result, protocol, session): 50 | print('stopping media for hold') 51 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 52 | defer.addCallback(phase3, protocol, session) 53 | return defer 54 | 55 | 56 | def phase3(result, protocol, session): 57 | print('continuing audio stream') 58 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 59 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {})]) 60 | media_defer = DeferredList([caller_media, callee_media]) 61 | defer = succeed(None) 62 | defer.addCallback(caller_update, protocol, session, media_defer, kthxbye) 63 | return defer 64 | 65 | 66 | def caller_update(result, protocol, session, media_defer, do_after): 67 | print('doing update for caller') 68 | defer = session.do_update(protocol, 'caller', 'request', False) 69 | defer.addCallback(callee_update, protocol, session, media_defer, do_after) 70 | return defer 71 | 72 | 73 | def callee_update(callee_addr, protocol, session, media_defer, do_after): 74 | print('doing update for callee') 75 | defer = session.do_update(protocol, 'callee', 'reply', True) 76 | defer.addCallback(do_media, callee_addr, protocol, session, media_defer, do_after) 77 | return defer 78 | 79 | 80 | def do_media(caller_addr, callee_addr, protocol, session, media_defer, do_after): 81 | (caller_ip, caller_ports) = caller_addr 82 | (callee_ip, callee_ports) = callee_addr 83 | print('starting media for both parties') 84 | session.caller.start_media(caller_ip, caller_ports) 85 | session.callee.start_media(callee_ip, callee_ports) 86 | media_defer.addCallback(wait, protocol, session, do_after) 87 | return media_defer 88 | 89 | 90 | def wait(result, protocol, session, do_after): 91 | print('got media, waiting 5 seconds') 92 | defer = Deferred() 93 | defer.addCallback(stop_media, protocol, session, do_after) 94 | reactor.callLater(5, defer.callback, None) 95 | return defer 96 | 97 | 98 | def stop_media(result, protocol, session, do_after): 99 | print('stopping media') 100 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 101 | defer.addCallback(do_after, protocol, session) 102 | return defer 103 | 104 | 105 | def kthxbye(result, protocol, session): 106 | print('sending remove') 107 | return session.do_remove(protocol, 'caller') 108 | 109 | 110 | def disconnect(result, connector): 111 | print('disconnecting') 112 | connector.disconnect() 113 | reactor.callLater(1, reactor.stop) 114 | 115 | 116 | def catch_all_err(failure): 117 | print(failure) 118 | 119 | 120 | if __name__ == '__main__': 121 | caller = Endpoint('Alice ', 'Caller UA', True) 122 | callee = Endpoint('Bob ', 'Callee UA', False) 123 | session = Session(caller, callee) 124 | connector, defer = connect_to_dispatcher() 125 | defer.addCallback(phase1, session) 126 | defer.addCallback(disconnect, connector) 127 | defer.addErrback(catch_all_err) 128 | reactor.run() 129 | -------------------------------------------------------------------------------- /test/holdtest3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a session that starts with 1 audio stream, then gets put 8 | on hold by the caller and stops without a BYE after 10 seconds. It is meant to 9 | test the on hold timeout. 10 | """ 11 | 12 | from common import * 13 | 14 | 15 | def phase1(protocol, session): 16 | print('setting up audio stream') 17 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 18 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {})]) 19 | media_defer = DeferredList([caller_media, callee_media]) 20 | defer = succeed(None) 21 | defer.addCallback(caller_update, protocol, session, media_defer, phase2) 22 | return defer 23 | 24 | 25 | def phase2(result, protocol, session): 26 | print('setting stream on hold') 27 | session.caller.set_media([('audio', 40000, 'sendonly', {})]) 28 | session.callee.set_media([('audio', 30000, 'recvonly', {})]) 29 | defer = session.do_update(protocol, 'caller', 'request', False) 30 | defer.addCallback(callee_update_hold, protocol, session) 31 | return defer 32 | 33 | 34 | def callee_update_hold(result, protocol, session): 35 | print('updating hold for callee') 36 | defer = session.do_update(protocol, 'callee', 'reply', True) 37 | defer.addCallback(wait_hold, protocol, session) 38 | return defer 39 | 40 | 41 | def wait_hold(result, protocol, session): 42 | print('on hold, waiting 10 seconds...') 43 | defer = Deferred() 44 | reactor.callLater(10, defer.callback, None) 45 | return defer 46 | 47 | 48 | def caller_update(result, protocol, session, media_defer, do_after): 49 | print('doing update for caller') 50 | defer = session.do_update(protocol, 'caller', 'request', False) 51 | defer.addCallback(callee_update, protocol, session, media_defer, do_after) 52 | return defer 53 | 54 | 55 | def callee_update(callee_addr, protocol, session, media_defer, do_after): 56 | print('doing update for callee') 57 | defer = session.do_update(protocol, 'callee', 'reply', True) 58 | defer.addCallback(do_media, callee_addr, protocol, session, media_defer, do_after) 59 | return defer 60 | 61 | 62 | def do_media(caller_addr, callee_addr, protocol, session, media_defer, do_after): 63 | (caller_ip, caller_ports) = caller_addr 64 | (callee_ip, callee_ports) = callee_addr 65 | print('starting media for both parties') 66 | session.caller.start_media(caller_ip, caller_ports) 67 | session.callee.start_media(callee_ip, callee_ports) 68 | media_defer.addCallback(wait, protocol, session, do_after) 69 | return media_defer 70 | 71 | 72 | def wait(result, protocol, session, do_after): 73 | print('got media, waiting 5 seconds') 74 | defer = Deferred() 75 | defer.addCallback(stop_media, protocol, session, do_after) 76 | reactor.callLater(5, defer.callback, None) 77 | return defer 78 | 79 | 80 | def stop_media(result, protocol, session, do_after): 81 | print('stopping media') 82 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 83 | defer.addCallback(do_after, protocol, session) 84 | return defer 85 | 86 | 87 | def disconnect(result, connector): 88 | print('disconnecting') 89 | connector.disconnect() 90 | reactor.callLater(1, reactor.stop) 91 | 92 | 93 | def catch_all_err(failure): 94 | print(failure) 95 | 96 | 97 | if __name__ == '__main__': 98 | caller = Endpoint('Alice ', 'Caller UA', True) 99 | callee = Endpoint('Bob ', 'Callee UA', False) 100 | session = Session(caller, callee) 101 | connector, defer = connect_to_dispatcher() 102 | defer.addCallback(phase1, session) 103 | defer.addCallback(disconnect, connector) 104 | defer.addErrback(catch_all_err) 105 | reactor.run() 106 | -------------------------------------------------------------------------------- /test/icetest1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2009 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a call flow with ICE where the relay is NOT selected as a candidate: 8 | - The caller sends an INVITE 9 | - the callee sends a 200 OK 10 | - Both parties will send probing STUN requests for a few seconds 11 | - Both parties will stop the probes and not send media through the relay 12 | - After 4 minutes, the callee will send a BYE 13 | """ 14 | 15 | from common import * 16 | 17 | 18 | def caller_update(protocol, session, caller_media, callee_media): 19 | print('doing update for caller') 20 | defer = session.do_update(protocol, 'caller', 'request', False) 21 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 22 | return defer 23 | 24 | 25 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 26 | print('doing update for callee') 27 | defer = session.do_update(protocol, 'callee', 'reply', True) 28 | defer.addCallback(do_stun, callee_addr, protocol, session, caller_media, callee_media) 29 | return defer 30 | 31 | 32 | def do_stun(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 33 | (caller_ip, caller_ports) = caller_addr 34 | (callee_ip, callee_ports) = callee_addr 35 | print('starting STUN probes for both parties') 36 | session.caller.start_media(caller_ip, caller_ports, send_stun=True) 37 | session.callee.start_media(callee_ip, callee_ports, send_stun=True) 38 | defer = DeferredList([caller_media, callee_media]) 39 | defer.addCallback(wait_stun, session, protocol) 40 | return defer 41 | 42 | 43 | def wait_stun(result, session, protocol): 44 | print('got STUN probes, waiting 3 seconds') 45 | defer = Deferred() 46 | defer.addCallback(stop_stun_caller, session, protocol) 47 | reactor.callLater(3, defer.callback, None) 48 | return defer 49 | 50 | 51 | def stop_stun_caller(result, session, protocol): 52 | print('stopping STUN probes for caller') 53 | defer = session.caller.stop_media() 54 | defer.addCallback(stop_stun_callee, session, protocol) 55 | return defer 56 | 57 | 58 | def stop_stun_callee(result, session, protocol): 59 | print('stopping STUN probes for callee') 60 | defer = session.callee.stop_media() 61 | defer.addCallback(wait_end, session, protocol) 62 | return defer 63 | 64 | 65 | def wait_end(result, session, protocol): 66 | print('media is flowing via a different path than the relay for 4 minutes') 67 | defer = Deferred() 68 | defer.addCallback(end, session, protocol) 69 | reactor.callLater(240, defer.callback, None) 70 | return defer 71 | 72 | 73 | def end(result, session, protocol): 74 | print('sending remove') 75 | return session.do_remove(protocol, 'callee') 76 | 77 | 78 | def disconnect(result, connector): 79 | print('disconnecting') 80 | connector.disconnect() 81 | reactor.callLater(1, reactor.stop) 82 | 83 | 84 | def catch_all_err(failure): 85 | print(failure) 86 | 87 | 88 | if __name__ == '__main__': 89 | caller = Endpoint('Alice ', 'Caller UA', True) 90 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {'ice': 'yes'})]) 91 | callee = Endpoint('Bob ', 'Callee UA', False) 92 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {'ice': 'yes'})]) 93 | session = Session(caller, callee) 94 | connector, defer = connect_to_dispatcher() 95 | defer.addCallback(caller_update, session, caller_media, callee_media) 96 | defer.addCallback(disconnect, connector) 97 | defer.addErrback(catch_all_err) 98 | reactor.run() 99 | -------------------------------------------------------------------------------- /test/icetest2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2009 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a call flow with ICE where the relay is selected as a candidate: 8 | - The caller sends an INVITE 9 | - the callee sends a 200 OK 10 | - Both parties will send probing STUN requests for a few seconds 11 | - Both parties will stop the probes and start sending media through the relay 12 | (Note that a re-INVITE will be sent, this is due to a limitatin in the test framework) 13 | - After 5 seconds, the caller will send a BYE 14 | """ 15 | 16 | from common import * 17 | 18 | 19 | def caller_update(protocol, session, caller_media, callee_media): 20 | print('doing update for caller') 21 | defer = session.do_update(protocol, 'caller', 'request', False) 22 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 23 | return defer 24 | 25 | 26 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 27 | print('doing update for callee') 28 | defer = session.do_update(protocol, 'callee', 'reply', True) 29 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 30 | return defer 31 | 32 | 33 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 34 | (caller_ip, caller_ports) = caller_addr 35 | (callee_ip, callee_ports) = callee_addr 36 | print('starting STUN probes for both parties') 37 | session.caller.start_media(caller_ip, caller_ports, send_stun=True) 38 | session.callee.start_media(callee_ip, callee_ports, send_stun=True) 39 | defer = DeferredList([caller_media, callee_media]) 40 | defer.addCallback(wait, protocol, session) 41 | return defer 42 | 43 | 44 | def wait(result, protocol, session): 45 | print('got STUN, waiting 5 seconds') 46 | defer = Deferred() 47 | defer.addCallback(stop_media, protocol, session) 48 | reactor.callLater(5, defer.callback, None) 49 | return defer 50 | 51 | 52 | def stop_media(result, protocol, session): 53 | print('stopping STUN probes') 54 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 55 | defer.addCallback(change_callee, protocol, session) 56 | return defer 57 | 58 | 59 | def change_callee(result, protocol, session): 60 | print('sending new update for callee') 61 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {'ice': 'yes'})]) 62 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {'ice': 'yes'})]) 63 | media_defer = DeferredList([caller_media, callee_media]) 64 | defer = session.do_update(protocol, 'callee', 'request', False) 65 | defer.addCallback(change_caller, protocol, session, media_defer) 66 | return defer 67 | 68 | 69 | def change_caller(caller_addr, protocol, session, media_defer): 70 | (caller_ip, caller_ports) = caller_addr 71 | print('sending new update for caller') 72 | defer = session.do_update(protocol, 'caller', 'reply', True) 73 | defer.addCallback(start_new_media, protocol, session, media_defer, caller_ip, caller_ports) 74 | return defer 75 | 76 | 77 | def start_new_media(callee_addr, protocol, session, media_defer, caller_ip, caller_ports): 78 | (callee_ip, callee_ports) = callee_addr 79 | print('starting media') 80 | session.caller.start_media(caller_ip, caller_ports) 81 | session.callee.start_media(callee_ip, callee_ports) 82 | media_defer.addCallback(wait2, protocol, session) 83 | return media_defer 84 | 85 | 86 | def wait2(result, protocol, session): 87 | print('got media, waiting 5 seconds') 88 | defer = Deferred() 89 | defer.addCallback(kthxbye, protocol, session) 90 | reactor.callLater(5, defer.callback, None) 91 | return defer 92 | 93 | 94 | def kthxbye(result, protocol, session): 95 | print('sending remove') 96 | return session.do_remove(protocol, 'caller') 97 | 98 | 99 | def disconnect(result, connector): 100 | print('disconnecting') 101 | connector.disconnect() 102 | reactor.callLater(1, reactor.stop) 103 | 104 | 105 | def catch_all_err(failure): 106 | print(failure) 107 | 108 | 109 | if __name__ == '__main__': 110 | caller = Endpoint('Alice ', 'Caller UA', True) 111 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {'ice': 'yes'})]) 112 | callee = Endpoint('Bob ', 'Callee UA', False) 113 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {'ice': 'yes'})]) 114 | session = Session(caller, callee) 115 | connector, defer = connect_to_dispatcher() 116 | defer.addCallback(caller_update, session, caller_media, callee_media) 117 | defer.addCallback(disconnect, connector) 118 | defer.addErrback(catch_all_err) 119 | reactor.run() 120 | -------------------------------------------------------------------------------- /test/multitest: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PYTHONPATH=.. ./multitest1.py & 4 | PYTHONPATH=.. ./multitest2.py & 5 | PYTHONPATH=.. ./multitest3.py & 6 | PYTHONPATH=.. ./multitest4.py & 7 | 8 | -------------------------------------------------------------------------------- /test/multitest1.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/python3 3 | 4 | # Copyright (C) 2008 AG Projects 5 | # 6 | 7 | """ 8 | This test simulates a normal call flow: 9 | - The caller sends an INVITE 10 | - the callee sends a 200 OK 11 | - Both parties will start sending media 12 | - Media will flow for 5 seconds 13 | - The callee will send a BYE 14 | """ 15 | 16 | from common import * 17 | 18 | 19 | def caller_update(protocol, session, caller_media, callee_media): 20 | print('doing update for caller') 21 | defer = session.do_update(protocol, 'caller', 'request', False) 22 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 23 | return defer 24 | 25 | 26 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 27 | print('doing update for callee') 28 | defer = session.do_update(protocol, 'callee', 'reply', True) 29 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 30 | return defer 31 | 32 | 33 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 34 | (caller_ip, caller_ports) = caller_addr 35 | (callee_ip, callee_ports) = callee_addr 36 | print('starting media for both parties') 37 | session.caller.start_media(caller_ip, caller_ports) 38 | session.callee.start_media(callee_ip, callee_ports) 39 | defer = DeferredList([caller_media, callee_media]) 40 | defer.addCallback(wait, protocol, session) 41 | return defer 42 | 43 | 44 | def wait(result, protocol, session): 45 | print('got media, waiting 30 seconds') 46 | defer = Deferred() 47 | defer.addCallback(kthxbye, protocol, session) 48 | reactor.callLater(30, defer.callback, None) 49 | return defer 50 | 51 | 52 | def kthxbye(result, protocol, session): 53 | print('sending remove') 54 | return session.do_remove(protocol, 'callee') 55 | 56 | 57 | def disconnect(result, connector): 58 | print('disconnecting') 59 | connector.disconnect() 60 | reactor.callLater(1, reactor.stop) 61 | 62 | 63 | def catch_all_err(failure): 64 | print(failure) 65 | 66 | 67 | if __name__ == '__main__': 68 | caller = Endpoint('Alice ', 'Caller UA', True) 69 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {})]) 70 | callee = Endpoint('Bob ', 'Callee UA', False) 71 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {})]) 72 | session = Session(caller, callee) 73 | connector, defer = connect_to_dispatcher() 74 | defer.addCallback(caller_update, session, caller_media, callee_media) 75 | defer.addCallback(disconnect, connector) 76 | defer.addErrback(catch_all_err) 77 | reactor.run() 78 | -------------------------------------------------------------------------------- /test/multitest2.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/python3 3 | 4 | # Copyright (C) 2008 AG Projects 5 | # 6 | 7 | """ 8 | This test simulates a normal call flow: 9 | - The caller sends an INVITE 10 | - the callee sends a 200 OK 11 | - Both parties will start sending media 12 | - Media will flow for 5 seconds 13 | - The callee will send a BYE 14 | """ 15 | 16 | from common import * 17 | 18 | 19 | def caller_update(protocol, session, caller_media, callee_media): 20 | print('doing update for caller') 21 | defer = session.do_update(protocol, 'caller', 'request', False) 22 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 23 | return defer 24 | 25 | 26 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 27 | print('doing update for callee') 28 | defer = session.do_update(protocol, 'callee', 'reply', True) 29 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 30 | return defer 31 | 32 | 33 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 34 | (caller_ip, caller_ports) = caller_addr 35 | (callee_ip, callee_ports) = callee_addr 36 | print('starting media for both parties') 37 | session.caller.start_media(caller_ip, caller_ports) 38 | session.callee.start_media(callee_ip, callee_ports) 39 | defer = DeferredList([caller_media, callee_media]) 40 | defer.addCallback(wait, protocol, session) 41 | return defer 42 | 43 | 44 | def wait(result, protocol, session): 45 | print('got media, waiting 35 seconds') 46 | defer = Deferred() 47 | defer.addCallback(kthxbye, protocol, session) 48 | reactor.callLater(35, defer.callback, None) 49 | return defer 50 | 51 | 52 | def kthxbye(result, protocol, session): 53 | print('sending remove') 54 | return session.do_remove(protocol, 'callee') 55 | 56 | 57 | def disconnect(result, connector): 58 | print('disconnecting') 59 | connector.disconnect() 60 | reactor.callLater(1, reactor.stop) 61 | 62 | 63 | def catch_all_err(failure): 64 | print(failure) 65 | 66 | 67 | if __name__ == '__main__': 68 | caller = Endpoint('Alice ', 'Caller UA', True) 69 | caller_media = caller.set_media([('audio', 40001, 'sendrecv', {})]) 70 | callee = Endpoint('Bob ', 'Callee UA', False) 71 | callee_media = callee.set_media([('audio', 30001, 'sendrecv', {})]) 72 | session = Session(caller, callee) 73 | connector, defer = connect_to_dispatcher() 74 | defer.addCallback(caller_update, session, caller_media, callee_media) 75 | defer.addCallback(disconnect, connector) 76 | defer.addErrback(catch_all_err) 77 | reactor.run() 78 | -------------------------------------------------------------------------------- /test/multitest3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a normal call flow: 8 | - The caller sends an INVITE 9 | - the callee sends a 200 OK 10 | - Both parties will start sending media 11 | - Media will flow for 5 seconds 12 | - The callee will send a BYE 13 | """ 14 | 15 | from common import * 16 | 17 | 18 | def caller_update(protocol, session, caller_media, callee_media): 19 | print('doing update for caller') 20 | defer = session.do_update(protocol, 'caller', 'request', False) 21 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 22 | return defer 23 | 24 | 25 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 26 | print('doing update for callee') 27 | defer = session.do_update(protocol, 'callee', 'reply', True) 28 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 29 | return defer 30 | 31 | 32 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 33 | (caller_ip, caller_ports) = caller_addr 34 | (callee_ip, callee_ports) = callee_addr 35 | print('starting media for both parties') 36 | session.caller.start_media(caller_ip, caller_ports) 37 | session.callee.start_media(callee_ip, callee_ports) 38 | defer = DeferredList([caller_media, callee_media]) 39 | defer.addCallback(wait, protocol, session) 40 | return defer 41 | 42 | 43 | def wait(result, protocol, session): 44 | print('got media, waiting 25 seconds') 45 | defer = Deferred() 46 | defer.addCallback(kthxbye, protocol, session) 47 | reactor.callLater(25, defer.callback, None) 48 | return defer 49 | 50 | 51 | def kthxbye(result, protocol, session): 52 | print('sending remove') 53 | return session.do_remove(protocol, 'callee') 54 | 55 | 56 | def disconnect(result, connector): 57 | print('disconnecting') 58 | connector.disconnect() 59 | reactor.callLater(1, reactor.stop) 60 | 61 | 62 | def catch_all_err(failure): 63 | print(failure) 64 | 65 | 66 | if __name__ == '__main__': 67 | caller = Endpoint('Alice ', 'Caller UA', True) 68 | caller_media = caller.set_media([('audio', 40002, 'sendrecv', {})]) 69 | callee = Endpoint('Bob ', 'Callee UA', False) 70 | callee_media = callee.set_media([('audio', 30002, 'sendrecv', {})]) 71 | session = Session(caller, callee) 72 | connector, defer = connect_to_dispatcher() 73 | defer.addCallback(caller_update, session, caller_media, callee_media) 74 | defer.addCallback(disconnect, connector) 75 | defer.addErrback(catch_all_err) 76 | reactor.run() 77 | -------------------------------------------------------------------------------- /test/multitest4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a normal call flow: 8 | - The caller sends an INVITE 9 | - the callee sends a 200 OK 10 | - Both parties will start sending media 11 | - Media will flow for 5 seconds 12 | - The callee will send a BYE 13 | """ 14 | 15 | from common import * 16 | 17 | 18 | def caller_update(protocol, session, caller_media, callee_media): 19 | print('doing update for caller') 20 | defer = session.do_update(protocol, 'caller', 'request', False) 21 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 22 | return defer 23 | 24 | 25 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 26 | print('doing update for callee') 27 | defer = session.do_update(protocol, 'callee', 'reply', True) 28 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 29 | return defer 30 | 31 | 32 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 33 | (caller_ip, caller_ports) = caller_addr 34 | (callee_ip, callee_ports) = callee_addr 35 | print('starting media for both parties') 36 | session.caller.start_media(caller_ip, caller_ports) 37 | session.callee.start_media(callee_ip, callee_ports) 38 | defer = DeferredList([caller_media, callee_media]) 39 | defer.addCallback(wait, protocol, session) 40 | return defer 41 | 42 | 43 | def wait(result, protocol, session): 44 | print('got media, waiting 40 seconds') 45 | defer = Deferred() 46 | defer.addCallback(kthxbye, protocol, session) 47 | reactor.callLater(40, defer.callback, None) 48 | return defer 49 | 50 | 51 | def kthxbye(result, protocol, session): 52 | print('sending remove') 53 | return session.do_remove(protocol, 'callee') 54 | 55 | 56 | def disconnect(result, connector): 57 | print('disconnecting') 58 | connector.disconnect() 59 | reactor.callLater(1, reactor.stop) 60 | 61 | 62 | def catch_all_err(failure): 63 | print(failure) 64 | 65 | 66 | if __name__ == '__main__': 67 | caller = Endpoint('Alice ', 'Caller UA', True) 68 | caller_media = caller.set_media([('audio', 40004, 'sendrecv', {})]) 69 | callee = Endpoint('Bob ', 'Callee UA', False) 70 | callee_media = callee.set_media([('audio', 30004, 'sendrecv', {})]) 71 | session = Session(caller, callee) 72 | connector, defer = connect_to_dispatcher() 73 | defer.addCallback(caller_update, session, caller_media, callee_media) 74 | defer.addCallback(disconnect, connector) 75 | defer.addErrback(catch_all_err) 76 | reactor.run() 77 | -------------------------------------------------------------------------------- /test/setuptest1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test scenario simulates the caller sending an INVITE, nothing is 8 | received in return. The relay should discard the session after a while. 9 | """ 10 | 11 | from common import * 12 | 13 | 14 | def caller_update(protocol, session): 15 | print('doing update for caller') 16 | return session.do_update(protocol, 'caller', 'request', False) 17 | 18 | 19 | def disconnect(result, connector): 20 | print('disconnecting') 21 | connector.disconnect() 22 | reactor.callLater(1, reactor.stop) 23 | 24 | 25 | def catch_all_err(failure): 26 | print(failure) 27 | 28 | 29 | if __name__ == '__main__': 30 | caller = Endpoint('Alice ', 'Caller UA', True) 31 | caller.set_media([('audio', 40000, 'sendrecv', {})]) 32 | callee = Endpoint('Bob ', 'Callee UA', False) 33 | callee.set_media([('audio', 30000, 'sendrecv', {})]) 34 | session = Session(caller, callee) 35 | connector, defer = connect_to_dispatcher() 36 | defer.addCallback(caller_update, session) 37 | defer.addCallback(disconnect, connector) 38 | defer.addErrback(catch_all_err) 39 | reactor.run() 40 | -------------------------------------------------------------------------------- /test/setuptest2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a normal call flow: 8 | - The caller sends an INVITE 9 | - the callee sends a 200 OK 10 | - Both parties will start sending media 11 | - Media will flow for 5 seconds 12 | - The callee will send a BYE 13 | """ 14 | 15 | from common import * 16 | 17 | 18 | def caller_update(protocol, session, caller_media, callee_media): 19 | print('doing update for caller') 20 | defer = session.do_update(protocol, 'caller', 'request', False) 21 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 22 | return defer 23 | 24 | 25 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 26 | print('doing update for callee') 27 | defer = session.do_update(protocol, 'callee', 'reply', True) 28 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 29 | return defer 30 | 31 | 32 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 33 | (caller_ip, caller_ports) = caller_addr 34 | (callee_ip, callee_ports) = callee_addr 35 | print('starting media for both parties') 36 | session.caller.start_media(caller_ip, caller_ports) 37 | session.callee.start_media(callee_ip, callee_ports) 38 | defer = DeferredList([caller_media, callee_media]) 39 | defer.addCallback(wait, protocol, session) 40 | return defer 41 | 42 | 43 | def wait(result, protocol, session): 44 | print('got media, waiting 5 seconds') 45 | defer = Deferred() 46 | defer.addCallback(kthxbye, protocol, session) 47 | reactor.callLater(5, defer.callback, None) 48 | return defer 49 | 50 | 51 | def kthxbye(result, protocol, session): 52 | print('sending remove') 53 | return session.do_remove(protocol, 'callee') 54 | 55 | 56 | def disconnect(result, connector): 57 | print('disconnecting') 58 | connector.disconnect() 59 | reactor.callLater(1, reactor.stop) 60 | 61 | 62 | def catch_all_err(failure): 63 | print(failure) 64 | 65 | 66 | if __name__ == '__main__': 67 | caller = Endpoint('Alice ', 'Caller UA', True) 68 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {})]) 69 | callee = Endpoint('Bob ', 'Callee UA', False) 70 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {})]) 71 | session = Session(caller, callee) 72 | connector, defer = connect_to_dispatcher() 73 | defer.addCallback(caller_update, session, caller_media, callee_media) 74 | defer.addCallback(disconnect, connector) 75 | defer.addErrback(catch_all_err) 76 | reactor.run() 77 | -------------------------------------------------------------------------------- /test/setuptest3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a normal call flow without a BYE: 8 | - The caller sends an INVITE 9 | - the callee sends a 200 OK 10 | - Both parties will start sending media 11 | - Media will flow for 5 seconds 12 | - Both parties will stop sending media 13 | """ 14 | 15 | from common import * 16 | 17 | 18 | def caller_update(protocol, session, caller_media, callee_media): 19 | print('doing update for caller') 20 | defer = session.do_update(protocol, 'caller', 'request', False) 21 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 22 | return defer 23 | 24 | 25 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 26 | print('doing update for callee') 27 | defer = session.do_update(protocol, 'callee', 'reply', True) 28 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 29 | return defer 30 | 31 | 32 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 33 | (caller_ip, caller_ports) = callee_addr 34 | (callee_ip, callee_ports) = callee_addr 35 | print('starting media for both parties') 36 | session.caller.start_media(caller_ip, caller_ports) 37 | session.callee.start_media(callee_ip, callee_ports) 38 | defer = DeferredList([caller_media, callee_media]) 39 | defer.addCallback(wait, protocol, session) 40 | return defer 41 | 42 | 43 | def wait(result, protocol, session): 44 | print('got media, waiting 5 seconds') 45 | defer = Deferred() 46 | defer.addCallback(stop_media, protocol, session) 47 | reactor.callLater(5, defer.callback, None) 48 | return defer 49 | 50 | 51 | def stop_media(result, protocol, session): 52 | print('stopping media for callee') 53 | return session.callee.stop_media() 54 | 55 | 56 | def disconnect(result, connector): 57 | print('disconnecting') 58 | connector.disconnect() 59 | reactor.callLater(1, reactor.stop) 60 | 61 | 62 | def catch_all_err(failure): 63 | print(failure) 64 | 65 | 66 | if __name__ == '__main__': 67 | caller = Endpoint('Alice ', 'Caller UA', True) 68 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {})]) 69 | callee = Endpoint('Bob ', 'Callee UA', False) 70 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {})]) 71 | session = Session(caller, callee) 72 | connector, defer = connect_to_dispatcher() 73 | defer.addCallback(caller_update, session, caller_media, callee_media) 74 | defer.addCallback(disconnect, connector) 75 | defer.addErrback(catch_all_err) 76 | reactor.run() 77 | -------------------------------------------------------------------------------- /test/setuptest4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a normal call flow, with an added ACK confirming 8 | the SDP: 9 | - The caller sends an INVITE 10 | - the callee sends a 200 OK 11 | - Both parties will start sending media 12 | - the caller sends an ACK with SDP 13 | - Media will flow for 5 seconds 14 | - The callee will send a BYE 15 | """ 16 | 17 | from common import * 18 | 19 | 20 | def caller_update(protocol, session, caller_media, callee_media): 21 | print('doing update for caller') 22 | defer = session.do_update(protocol, 'caller', 'request', False) 23 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 24 | return defer 25 | 26 | 27 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 28 | print('doing update for callee') 29 | defer = session.do_update(protocol, 'callee', 'reply', False) 30 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 31 | return defer 32 | 33 | 34 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 35 | (caller_ip, caller_ports) = caller_addr 36 | (callee_ip, callee_ports) = callee_addr 37 | print('starting media for both parties') 38 | session.caller.start_media(caller_ip, caller_ports) 39 | session.callee.start_media(callee_ip, callee_ports) 40 | defer = DeferredList([caller_media, callee_media]) 41 | defer.addCallback(caller_ack, protocol, session, callee_ip, callee_ports) 42 | return defer 43 | 44 | 45 | def caller_ack(result, protocol, session, callee_ip, callee_ports): 46 | print('got media, doing ACK for caller') 47 | defer = session.do_update(protocol, 'caller', 'request', True) 48 | defer.addCallback(wait, protocol, session, callee_ip, callee_ports) 49 | return defer 50 | 51 | 52 | def wait(callee_ack_addr, protocol, session, callee_ip, callee_ports): 53 | (callee_ack_ip, callee_ack_ports) = callee_ack_addr 54 | print('waiting 5 seconds') 55 | assert (callee_ack_ip == callee_ip) 56 | assert (callee_ack_ports == callee_ports) 57 | defer = Deferred() 58 | defer.addCallback(kthxbye, protocol, session) 59 | reactor.callLater(5, defer.callback, None) 60 | return defer 61 | 62 | 63 | def kthxbye(result, protocol, session): 64 | print('sending remove') 65 | return session.do_remove(protocol, 'callee') 66 | 67 | 68 | def disconnect(result, connector): 69 | print('disconnecting') 70 | connector.disconnect() 71 | reactor.callLater(1, reactor.stop) 72 | 73 | 74 | def catch_all_err(failure): 75 | print(failure) 76 | 77 | 78 | if __name__ == '__main__': 79 | caller = Endpoint('Alice ', 'Caller UA', True) 80 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {})]) 81 | callee = Endpoint('Bob ', 'Callee UA', False) 82 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {})]) 83 | session = Session(caller, callee) 84 | connector, defer = connect_to_dispatcher() 85 | defer.addCallback(caller_update, session, caller_media, callee_media) 86 | defer.addCallback(disconnect, connector) 87 | defer.addErrback(catch_all_err) 88 | reactor.run() 89 | -------------------------------------------------------------------------------- /test/setuptest5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates call setup where no SDP is sent in the INVITE: 8 | - the callee sends a 200 OK 9 | - the caller sends a ACK with SDP 10 | - Both parties will start sending media 11 | - Media will flow for 5 seconds 12 | - The callee will send a BYE 13 | """ 14 | 15 | from common import * 16 | 17 | 18 | def callee_update(protocol, session, caller_media, callee_media): 19 | print('doing update for callee') 20 | defer = session.do_update(protocol, 'callee', 'reply', False) 21 | defer.addCallback(caller_update, protocol, session, caller_media, callee_media) 22 | return defer 23 | 24 | 25 | def caller_update(caller_addr, protocol, session, caller_media, callee_media): 26 | print('doing update for caller') 27 | defer = session.do_update(protocol, 'caller', 'request', True) 28 | defer.addCallback(do_media, caller_addr, protocol, session, caller_media, callee_media) 29 | return defer 30 | 31 | 32 | def do_media(callee_addr, caller_addr, protocol, session, caller_media, callee_media): 33 | (callee_ip, callee_ports) = callee_addr 34 | (caller_ip, caller_ports) = caller_addr 35 | print('starting media for both parties') 36 | session.caller.start_media(caller_ip, caller_ports) 37 | session.callee.start_media(callee_ip, callee_ports) 38 | defer = DeferredList([caller_media, callee_media]) 39 | defer.addCallback(wait, protocol, session) 40 | return defer 41 | 42 | 43 | def wait(result, protocol, session): 44 | print('got media, waiting 5 seconds') 45 | defer = Deferred() 46 | defer.addCallback(kthxbye, protocol, session) 47 | reactor.callLater(5, defer.callback, None) 48 | return defer 49 | 50 | 51 | def kthxbye(result, protocol, session): 52 | print('sending remove') 53 | return session.do_remove(protocol, 'callee') 54 | 55 | 56 | def disconnect(result, connector): 57 | print('disconnecting') 58 | connector.disconnect() 59 | reactor.callLater(1, reactor.stop) 60 | 61 | 62 | def catch_all_err(failure): 63 | print(failure) 64 | 65 | 66 | if __name__ == '__main__': 67 | caller = Endpoint('Alice ', 'Caller UA', True) 68 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {})]) 69 | callee = Endpoint('Bob ', 'Callee UA', False) 70 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {})]) 71 | session = Session(caller, callee) 72 | connector, defer = connect_to_dispatcher() 73 | defer.addCallback(callee_update, session, caller_media, callee_media) 74 | defer.addCallback(disconnect, connector) 75 | defer.addErrback(catch_all_err) 76 | reactor.run() 77 | -------------------------------------------------------------------------------- /test/setuptest6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a session in which the caller proposes 3 streams in the 8 | INVITE and the callee rejects two of these. 9 | - The caller sends an INVITE with 1 video stream and 2 audio streams 10 | - the callee sends a 200 OK with the ports for two of the streams set to 0 11 | - Both parties start sending media 12 | - Media flows for 5 seconds 13 | - The callee sends a BYE 14 | """ 15 | 16 | from common import * 17 | 18 | 19 | def caller_update(protocol, session, caller_media, callee_media): 20 | print('doing update for caller') 21 | defer = session.do_update(protocol, 'caller', 'request', False) 22 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 23 | return defer 24 | 25 | 26 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 27 | print('doing update for callee') 28 | defer = session.do_update(protocol, 'callee', 'reply', True) 29 | defer.addCallback(change_caller1, callee_addr, protocol, session, caller_media, callee_media) 30 | return defer 31 | 32 | 33 | def change_caller1(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 34 | print('stopping media for caller') 35 | defer = session.caller.stop_media() 36 | defer.addCallback(change_caller2, caller_addr, callee_addr, protocol, session, callee_media) 37 | return defer 38 | 39 | 40 | def change_caller2(result, caller_addr, callee_addr, protocol, session, callee_media): 41 | print('setting new media for caller') 42 | caller_media = caller.set_media([('audio', 0, 'sendrecv', {}), ('video', 0, 'sendrecv', {}), ('audio', 40020, 'sendrecv', {})]) 43 | return do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media) 44 | 45 | 46 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 47 | (caller_ip, caller_ports) = caller_addr 48 | (callee_ip, callee_ports) = callee_addr 49 | print('starting media for both parties') 50 | session.caller.start_media(caller_ip, caller_ports) 51 | session.callee.start_media(callee_ip, callee_ports) 52 | defer = DeferredList([caller_media, callee_media]) 53 | defer.addCallback(wait, protocol, session) 54 | return defer 55 | 56 | 57 | def wait(result, protocol, session): 58 | print('got media, waiting 5 seconds') 59 | defer = Deferred() 60 | defer.addCallback(kthxbye, protocol, session) 61 | reactor.callLater(5, defer.callback, None) 62 | return defer 63 | 64 | 65 | def kthxbye(result, protocol, session): 66 | print('sending remove') 67 | return session.do_remove(protocol, 'callee') 68 | 69 | 70 | def disconnect(result, connector): 71 | print('disconnecting') 72 | connector.disconnect() 73 | reactor.callLater(1, reactor.stop) 74 | 75 | 76 | def catch_all_err(failure): 77 | print(failure) 78 | 79 | 80 | if __name__ == '__main__': 81 | caller = Endpoint('Alice ', 'Caller UA', True) 82 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {}), ('video', 40010, 'sendrecv', {}), ('audio', 40020, 'sendrecv', {})]) 83 | callee = Endpoint('Bob ', 'Callee UA', False) 84 | callee_media = callee.set_media([('audio', 0, 'sendrecv', {}), ('video', 0, 'sendrecv', {}), ('audio', 30020, 'sendrecv', {})]) 85 | session = Session(caller, callee) 86 | connector, defer = connect_to_dispatcher() 87 | defer.addCallback(caller_update, session, caller_media, callee_media) 88 | defer.addCallback(disconnect, connector) 89 | defer.addErrback(catch_all_err) 90 | reactor.run() 91 | -------------------------------------------------------------------------------- /test/setuptest7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a normal call flow: 8 | - The caller sends an INVITE with a media stream with port=0 9 | - The callee sends a 200 OK 10 | - The callee will send a BYE after 5 seconds 11 | """ 12 | 13 | from common import * 14 | 15 | 16 | def caller_update(protocol, session, caller_media, callee_media): 17 | print('doing update for caller') 18 | defer = session.do_update(protocol, 'caller', 'request', False) 19 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 20 | return defer 21 | 22 | 23 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 24 | print('doing update for callee') 25 | defer = session.do_update(protocol, 'callee', 'reply', True) 26 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 27 | return defer 28 | 29 | 30 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 31 | (caller_ip, caller_ports) = caller_addr 32 | (callee_ip, callee_ports) = callee_addr 33 | print('starting media for both parties') 34 | session.caller.start_media(caller_ip, caller_ports) 35 | session.callee.start_media(callee_ip, callee_ports) 36 | defer = DeferredList([caller_media, callee_media]) 37 | defer.addCallback(wait, protocol, session) 38 | return defer 39 | 40 | 41 | def wait(result, protocol, session): 42 | print('got media, waiting 5 seconds') 43 | defer = Deferred() 44 | defer.addCallback(kthxbye, protocol, session) 45 | reactor.callLater(5, defer.callback, None) 46 | return defer 47 | 48 | 49 | def kthxbye(result, protocol, session): 50 | print('sending remove') 51 | return session.do_remove(protocol, 'callee') 52 | 53 | 54 | def disconnect(result, connector): 55 | print('disconnecting') 56 | connector.disconnect() 57 | reactor.callLater(1, reactor.stop) 58 | 59 | 60 | def catch_all_err(failure): 61 | print(failure) 62 | 63 | 64 | if __name__ == '__main__': 65 | caller = Endpoint('Alice ', 'Caller UA', True) 66 | caller_media = caller.set_media([('audio', 0, 'sendrecv', {})]) 67 | callee = Endpoint('Bob ', 'Callee UA', False) 68 | callee_media = callee.set_media([('audio', 40000, 'sendrecv', {})]) 69 | session = Session(caller, callee) 70 | connector, defer = connect_to_dispatcher() 71 | defer.addCallback(caller_update, session, caller_media, callee_media) 72 | defer.addCallback(disconnect, connector) 73 | defer.addErrback(catch_all_err) 74 | reactor.run() 75 | -------------------------------------------------------------------------------- /test/updatetest1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a call setup with an updated reply from the callee: 8 | - The caller sends an INVITE 9 | - The callee replies with .e.g a 183 10 | - Both parties start sending media 11 | - Media flows for 5 seconds 12 | - Media stops 13 | - The callee sends a 200 OK with a new port 14 | - Media flows again for 5 seconds 15 | - The caller sends a BYE 16 | """ 17 | 18 | from common import * 19 | 20 | 21 | def caller_update(protocol, session, caller_media, callee_media): 22 | print('doing update for caller') 23 | defer = session.do_update(protocol, 'caller', 'request', False) 24 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 25 | return defer 26 | 27 | 28 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 29 | print('doing update for callee') 30 | defer = session.do_update(protocol, 'callee', 'reply', False) 31 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 32 | return defer 33 | 34 | 35 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 36 | (caller_ip, caller_ports) = caller_addr 37 | (callee_ip, callee_ports) = callee_addr 38 | print('starting media for both parties') 39 | session.caller.start_media(caller_ip, caller_ports) 40 | session.callee.start_media(callee_ip, callee_ports) 41 | defer = DeferredList([caller_media, callee_media]) 42 | defer.addCallback(wait, protocol, session, callee_ip, callee_ports) 43 | return defer 44 | 45 | 46 | def wait(result, protocol, session, callee_ip, callee_ports): 47 | print('got media, waiting 5 seconds') 48 | defer = Deferred() 49 | defer.addCallback(stop_media, protocol, session, callee_ip, callee_ports) 50 | reactor.callLater(5, defer.callback, None) 51 | return defer 52 | 53 | 54 | def stop_media(result, protocol, session, callee_ip, callee_ports): 55 | print('stopping media') 56 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 57 | defer.addCallback(change_callee, protocol, session, callee_ip, callee_ports) 58 | return defer 59 | 60 | 61 | def change_callee(result, protocol, session, callee_ip, callee_ports): 62 | print('sending new update for callee') 63 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 64 | callee_media = session.callee.set_media([('audio', 30020, 'sendrecv', {})]) 65 | media_defer = DeferredList([caller_media, callee_media]) 66 | defer = session.do_update(protocol, 'callee', 'reply', True) 67 | defer.addCallback(start_new_media, protocol, session, media_defer, callee_ip, callee_ports) 68 | return defer 69 | 70 | 71 | def start_new_media(caller_addr, protocol, session, media_defer, callee_ip, callee_ports): 72 | (caller_ip, caller_ports) = caller_addr 73 | print('starting new media') 74 | session.caller.start_media(caller_ip, caller_ports) 75 | session.callee.start_media(callee_ip, callee_ports) 76 | media_defer.addCallback(wait2, protocol, session) 77 | return media_defer 78 | 79 | 80 | def wait2(result, protocol, session): 81 | print('got new media, waiting 5 seconds') 82 | defer = Deferred() 83 | defer.addCallback(kthxbye, protocol, session) 84 | reactor.callLater(5, defer.callback, None) 85 | return defer 86 | 87 | 88 | def kthxbye(result, protocol, session): 89 | print('sending remove') 90 | return session.do_remove(protocol, 'caller') 91 | 92 | 93 | def disconnect(result, connector): 94 | print('disconnecting') 95 | connector.disconnect() 96 | reactor.callLater(1, reactor.stop) 97 | 98 | 99 | def catch_all_err(failure): 100 | print(failure) 101 | 102 | 103 | if __name__ == '__main__': 104 | caller = Endpoint('Alice ', 'Caller UA', True) 105 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {})]) 106 | callee = Endpoint('Bob ', 'Callee UA', False) 107 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {})]) 108 | session = Session(caller, callee) 109 | connector, defer = connect_to_dispatcher() 110 | defer.addCallback(caller_update, session, caller_media, callee_media) 111 | defer.addCallback(disconnect, connector) 112 | defer.addErrback(catch_all_err) 113 | reactor.run() 114 | -------------------------------------------------------------------------------- /test/updatetest2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a session with audio and video media flowing, after which 8 | the callee removes the video stream and only audio flows: 9 | - caller sends INVITE, callee sends 200 ok 10 | - audio and video media flows for 5 seconds 11 | - callee proposes to keep only the audio stream using a re-INVITE, caller 12 | sends OK 13 | - audio media flows for 5 seconds 14 | - caller sends BYE 15 | """ 16 | 17 | from common import * 18 | 19 | 20 | def caller_update(protocol, session, caller_media, callee_media): 21 | print('doing update for caller') 22 | defer = session.do_update(protocol, 'caller', 'request', False) 23 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 24 | return defer 25 | 26 | 27 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 28 | print('doing update for callee') 29 | defer = session.do_update(protocol, 'callee', 'reply', True) 30 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 31 | return defer 32 | 33 | 34 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 35 | (caller_ip, caller_ports) = caller_addr 36 | (callee_ip, callee_ports) = callee_addr 37 | print('starting media for both parties') 38 | session.caller.start_media(caller_ip, caller_ports) 39 | session.callee.start_media(callee_ip, callee_ports) 40 | defer = DeferredList([caller_media, callee_media]) 41 | defer.addCallback(wait, protocol, session) 42 | return defer 43 | 44 | 45 | def wait(result, protocol, session): 46 | print('got media, waiting 5 seconds') 47 | defer = Deferred() 48 | defer.addCallback(stop_media, protocol, session) 49 | reactor.callLater(5, defer.callback, None) 50 | return defer 51 | 52 | 53 | def stop_media(result, protocol, session): 54 | print('stopping media') 55 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 56 | defer.addCallback(change_callee, protocol, session) 57 | return defer 58 | 59 | 60 | def change_callee(result, protocol, session): 61 | print('sending new update for callee') 62 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 63 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {})]) 64 | media_defer = DeferredList([caller_media, callee_media]) 65 | defer = session.do_update(protocol, 'callee', 'request', False) 66 | defer.addCallback(change_caller, protocol, session, media_defer) 67 | return defer 68 | 69 | 70 | def change_caller(caller_addr, protocol, session, media_defer): 71 | (caller_ip, caller_ports) = caller_addr 72 | print('sending new update for caller') 73 | defer = session.do_update(protocol, 'caller', 'reply', True) 74 | defer.addCallback(start_new_media, protocol, session, media_defer, caller_ip, caller_ports) 75 | return defer 76 | 77 | 78 | def start_new_media(callee_addr, protocol, session, media_defer, caller_ip, caller_ports): 79 | (callee_ip, callee_ports) = callee_addr 80 | print('starting new media') 81 | session.caller.start_media(caller_ip, caller_ports) 82 | session.callee.start_media(callee_ip, callee_ports) 83 | media_defer.addCallback(wait2, protocol, session) 84 | return media_defer 85 | 86 | 87 | def wait2(result, protocol, session): 88 | print('got new media, waiting 5 seconds') 89 | defer = Deferred() 90 | defer.addCallback(kthxbye, protocol, session) 91 | reactor.callLater(5, defer.callback, None) 92 | return defer 93 | 94 | 95 | def kthxbye(result, protocol, session): 96 | print('sending remove') 97 | return session.do_remove(protocol, 'caller') 98 | 99 | 100 | def disconnect(result, connector): 101 | print('disconnecting') 102 | connector.disconnect() 103 | reactor.callLater(1, reactor.stop) 104 | 105 | 106 | def catch_all_err(failure): 107 | print(failure) 108 | 109 | 110 | if __name__ == '__main__': 111 | caller = Endpoint('Alice ', 'Caller UA', True) 112 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {}), ('video', 40010, 'sendrecv', {})]) 113 | callee = Endpoint('Bob ', 'Callee UA', False) 114 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {}), ('video', 30010, 'sendrecv', {})]) 115 | session = Session(caller, callee) 116 | connector, defer = connect_to_dispatcher() 117 | defer.addCallback(caller_update, session, caller_media, callee_media) 118 | defer.addCallback(disconnect, connector) 119 | defer.addErrback(catch_all_err) 120 | reactor.run() 121 | -------------------------------------------------------------------------------- /test/updatetest3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a session that starts with only video, then two audio 8 | streams are added and finally only one of the audio streams remains. 9 | """ 10 | 11 | from common import * 12 | 13 | 14 | def phase1(protocol, session): 15 | print('setting up 1 video stream') 16 | caller_media = session.caller.set_media([('video', 40000, 'sendrecv', {})]) 17 | callee_media = session.callee.set_media([('video', 30000, 'sendrecv', {})]) 18 | media_defer = DeferredList([caller_media, callee_media]) 19 | defer = succeed(None) 20 | defer.addCallback(caller_update, protocol, session, media_defer, phase2) 21 | return defer 22 | 23 | 24 | def phase2(result, protocol, session): 25 | print('adding 2 audio streams') 26 | caller_media = session.caller.set_media([('video', 40000, 'sendrecv', {}), ('audio', 40010, 'sendrecv', {}), ('audio', 40020, 'sendrecv', {})]) 27 | callee_media = session.callee.set_media([('video', 30000, 'sendrecv', {}), ('audio', 30010, 'sendrecv', {}), ('audio', 30020, 'sendrecv', {})]) 28 | media_defer = DeferredList([caller_media, callee_media]) 29 | defer = succeed(None) 30 | defer.addCallback(caller_update, protocol, session, media_defer, phase3) 31 | return defer 32 | 33 | 34 | def phase3(result, protocol, session): 35 | print('removing 1 video and 1 audio stream') 36 | caller_media = session.caller.set_media([('audio', 40020, 'sendrecv', {})]) 37 | callee_media = session.callee.set_media([('audio', 30020, 'sendrecv', {})]) 38 | media_defer = DeferredList([caller_media, callee_media]) 39 | defer = succeed(None) 40 | defer.addCallback(caller_update, protocol, session, media_defer, kthxbye) 41 | return defer 42 | 43 | 44 | def caller_update(result, protocol, session, media_defer, do_after): 45 | print('doing update for caller') 46 | defer = session.do_update(protocol, 'caller', 'request', False) 47 | defer.addCallback(callee_update, protocol, session, media_defer, do_after) 48 | return defer 49 | 50 | 51 | def callee_update(callee_addr, protocol, session, media_defer, do_after): 52 | print('doing update for callee') 53 | defer = session.do_update(protocol, 'callee', 'reply', True) 54 | defer.addCallback(do_media, callee_addr, protocol, session, media_defer, do_after) 55 | return defer 56 | 57 | 58 | def do_media(caller_addr, callee_addr, protocol, session, media_defer, do_after): 59 | (caller_ip, caller_ports) = caller_addr 60 | (callee_ip, callee_ports) = callee_addr 61 | print('starting media for both parties') 62 | session.caller.start_media(caller_ip, caller_ports) 63 | session.callee.start_media(callee_ip, callee_ports) 64 | media_defer.addCallback(wait, protocol, session, do_after) 65 | return media_defer 66 | 67 | 68 | def wait(result, protocol, session, do_after): 69 | print('got media, waiting 5 seconds') 70 | defer = Deferred() 71 | defer.addCallback(stop_media, protocol, session, do_after) 72 | reactor.callLater(5, defer.callback, None) 73 | return defer 74 | 75 | 76 | def stop_media(result, protocol, session, do_after): 77 | print('stopping media') 78 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 79 | defer.addCallback(do_after, protocol, session) 80 | return defer 81 | 82 | 83 | def kthxbye(result, protocol, session): 84 | print('sending remove') 85 | return session.do_remove(protocol, 'caller') 86 | 87 | 88 | def disconnect(result, connector): 89 | print('disconnecting') 90 | connector.disconnect() 91 | reactor.callLater(1, reactor.stop) 92 | 93 | 94 | def catch_all_err(failure): 95 | print(failure) 96 | 97 | 98 | if __name__ == '__main__': 99 | caller = Endpoint('Alice ', 'Caller UA', True) 100 | callee = Endpoint('Bob ', 'Callee UA', False) 101 | session = Session(caller, callee) 102 | connector, defer = connect_to_dispatcher() 103 | defer.addCallback(phase1, session) 104 | defer.addCallback(disconnect, connector) 105 | defer.addErrback(catch_all_err) 106 | reactor.run() 107 | -------------------------------------------------------------------------------- /test/updatetest4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a session with audio media flowing, after which 8 | the callee changes the port of the media, e.g. through an UPDATE: 9 | - caller sends INVITE, callee sends 200 ok 10 | - audio and video media flows for 5 seconds 11 | - callee changes the port of the audio stream through an UPATE or re-INVITE 12 | - audio media flows for 5 seconds 13 | - caller sends BYE 14 | """ 15 | 16 | from common import * 17 | 18 | 19 | def caller_update(protocol, session, caller_media, callee_media): 20 | print('doing update for caller') 21 | defer = session.do_update(protocol, 'caller', 'request', False) 22 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 23 | return defer 24 | 25 | 26 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 27 | print('doing update for callee') 28 | defer = session.do_update(protocol, 'callee', 'reply', True) 29 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 30 | return defer 31 | 32 | 33 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 34 | (caller_ip, caller_ports) = caller_addr 35 | (callee_ip, callee_ports) = callee_addr 36 | print('starting media for both parties') 37 | session.caller.start_media(caller_ip, caller_ports) 38 | session.callee.start_media(callee_ip, callee_ports) 39 | defer = DeferredList([caller_media, callee_media]) 40 | defer.addCallback(wait, protocol, session) 41 | return defer 42 | 43 | 44 | def wait(result, protocol, session): 45 | print('got media, waiting 5 seconds') 46 | defer = Deferred() 47 | defer.addCallback(stop_media, protocol, session) 48 | reactor.callLater(5, defer.callback, None) 49 | return defer 50 | 51 | 52 | def stop_media(result, protocol, session): 53 | print('stopping media') 54 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 55 | defer.addCallback(change_callee, protocol, session) 56 | return defer 57 | 58 | 59 | def change_callee(result, protocol, session): 60 | print('sending new update for callee') 61 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 62 | callee_media = session.callee.set_media([('audio', 30010, 'sendrecv', {})]) 63 | media_defer = DeferredList([caller_media, callee_media]) 64 | defer = session.do_update(protocol, 'callee', 'request', False) 65 | defer.addCallback(change_caller, protocol, session, media_defer) 66 | return defer 67 | 68 | 69 | def change_caller(caller_addr, protocol, session, media_defer): 70 | (caller_ip, caller_ports) = caller_addr 71 | print('sending new update for caller') 72 | defer = session.do_update(protocol, 'caller', 'reply', True) 73 | defer.addCallback(start_new_media, protocol, session, media_defer, caller_ip, caller_ports) 74 | return defer 75 | 76 | 77 | def start_new_media(callee_addr, protocol, session, media_defer, caller_ip, caller_ports): 78 | (callee_ip, callee_ports) = callee_addr 79 | print('starting new media') 80 | session.caller.start_media(caller_ip, caller_ports) 81 | session.callee.start_media(callee_ip, callee_ports) 82 | media_defer.addCallback(wait2, protocol, session) 83 | return media_defer 84 | 85 | 86 | def wait2(result, protocol, session): 87 | print('got new media, waiting 5 seconds') 88 | defer = Deferred() 89 | defer.addCallback(kthxbye, protocol, session) 90 | reactor.callLater(5, defer.callback, None) 91 | return defer 92 | 93 | 94 | def kthxbye(result, protocol, session): 95 | print('sending remove') 96 | return session.do_remove(protocol, 'caller') 97 | 98 | 99 | def disconnect(result, connector): 100 | print('disconnecting') 101 | connector.disconnect() 102 | reactor.callLater(1, reactor.stop) 103 | 104 | 105 | def catch_all_err(failure): 106 | print(failure) 107 | 108 | 109 | if __name__ == '__main__': 110 | caller = Endpoint('Alice ', 'Caller UA', True) 111 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {})]) 112 | callee = Endpoint('Bob ', 'Callee UA', False) 113 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {})]) 114 | session = Session(caller, callee) 115 | connector, defer = connect_to_dispatcher() 116 | defer.addCallback(caller_update, session, caller_media, callee_media) 117 | defer.addCallback(disconnect, connector) 118 | defer.addErrback(catch_all_err) 119 | reactor.run() 120 | -------------------------------------------------------------------------------- /test/updatetest5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a call setup with an updated reply from the callee: 8 | - The caller sends an INVITE 9 | - The callee replies with a provisional response containg SDP e.g. 183 10 | - Both parties start sending media 11 | - Media flows for 5 seconds 12 | - Media stops 13 | - The callee sends another 183 with new port and to-tag (e.g. when the first PSTN gateway failed) 14 | - Both parties start sending media 15 | - Media flows for 5 seconds 16 | - Media stops 17 | - The callee sends a 200 OK with a new port 18 | - Media flows again for 5 seconds 19 | - The caller sends a BYE 20 | """ 21 | 22 | from common import * 23 | 24 | 25 | def caller_update(protocol, session, caller_media, callee_media): 26 | print('doing update for caller') 27 | defer = session.do_update(protocol, 'caller', 'request', False) 28 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 29 | return defer 30 | 31 | 32 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 33 | print('doing update for callee') 34 | defer = session.do_update(protocol, 'callee', 'reply', False) 35 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 36 | return defer 37 | 38 | 39 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 40 | (caller_ip, caller_ports) = caller_addr 41 | (callee_ip, callee_ports) = callee_addr 42 | print('starting media for both parties') 43 | session.caller.start_media(caller_ip, caller_ports) 44 | session.callee.start_media(callee_ip, callee_ports) 45 | defer = DeferredList([caller_media, callee_media]) 46 | defer.addCallback(wait, protocol, session, callee_ip, callee_ports) 47 | return defer 48 | 49 | 50 | def wait(result, protocol, session, callee_ip, callee_ports): 51 | print('got media, waiting 5 seconds') 52 | defer = Deferred() 53 | defer.addCallback(stop_media, protocol, session, callee_ip, callee_ports) 54 | reactor.callLater(5, defer.callback, None) 55 | return defer 56 | 57 | 58 | def stop_media(result, protocol, session, callee_ip, callee_ports): 59 | print('stopping media') 60 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 61 | defer.addCallback(change_callee_prov, protocol, session, callee_ip, callee_ports) 62 | return defer 63 | 64 | 65 | def change_callee_prov(result, protocol, session, callee_ip, callee_ports): 66 | print('sending new provisional update for callee') 67 | session.callee.tag = 'newtotag' 68 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 69 | callee_media = session.callee.set_media([('audio', 30010, 'sendrecv', {})]) 70 | media_defer = DeferredList([caller_media, callee_media]) 71 | defer = session.do_update(protocol, 'callee', 'reply', False) 72 | defer.addCallback(start_new_media_prov, protocol, session, media_defer, callee_ip, callee_ports) 73 | return defer 74 | 75 | 76 | def start_new_media_prov(caller_addr, protocol, session, media_defer, callee_ip, callee_ports): 77 | (caller_ip, caller_ports) = caller_addr 78 | print('starting new media') 79 | session.caller.start_media(caller_ip, caller_ports) 80 | session.callee.start_media(callee_ip, callee_ports) 81 | media_defer.addCallback(wait2, protocol, session, callee_ip, callee_ports) 82 | return media_defer 83 | 84 | 85 | def wait2(result, protocol, session, callee_ip, callee_ports): 86 | print('got new media, waiting 5 seconds') 87 | defer = Deferred() 88 | defer.addCallback(stop_media_prov, protocol, session, callee_ip, callee_ports) 89 | reactor.callLater(5, defer.callback, None) 90 | return defer 91 | 92 | 93 | def stop_media_prov(result, protocol, session, callee_ip, callee_ports): 94 | print('stopping media') 95 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 96 | defer.addCallback(change_callee, protocol, session, callee_ip, callee_ports) 97 | return defer 98 | 99 | 100 | def change_callee(result, protocol, session, callee_ip, callee_ports): 101 | print('sending new update for callee') 102 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {})]) 103 | callee_media = session.callee.set_media([('audio', 30020, 'sendrecv', {})]) 104 | media_defer = DeferredList([caller_media, callee_media]) 105 | defer = session.do_update(protocol, 'callee', 'reply', True) 106 | defer.addCallback(start_new_media, protocol, session, media_defer, callee_ip, callee_ports) 107 | return defer 108 | 109 | 110 | def start_new_media(caller_addr, protocol, session, media_defer, callee_ip, callee_ports): 111 | (caller_ip, caller_ports) = caller_addr 112 | print('starting new media') 113 | session.caller.start_media(caller_ip, caller_ports) 114 | session.callee.start_media(callee_ip, callee_ports) 115 | media_defer.addCallback(wait3, protocol, session) 116 | return media_defer 117 | 118 | 119 | def wait3(result, protocol, session): 120 | print('got new media, waiting 5 seconds') 121 | defer = Deferred() 122 | defer.addCallback(kthxbye, protocol, session) 123 | reactor.callLater(5, defer.callback, None) 124 | return defer 125 | 126 | 127 | def kthxbye(result, protocol, session): 128 | print('sending remove') 129 | return session.do_remove(protocol, 'caller') 130 | 131 | 132 | def disconnect(result, connector): 133 | print('disconnecting') 134 | connector.disconnect() 135 | reactor.callLater(1, reactor.stop) 136 | 137 | 138 | def catch_all_err(failure): 139 | print(failure) 140 | 141 | 142 | if __name__ == '__main__': 143 | caller = Endpoint('Alice ', 'Caller UA', True) 144 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {})]) 145 | callee = Endpoint('Bob ', 'Callee UA', False) 146 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {})]) 147 | session = Session(caller, callee) 148 | connector, defer = connect_to_dispatcher() 149 | defer.addCallback(caller_update, session, caller_media, callee_media) 150 | defer.addCallback(disconnect, connector) 151 | defer.addErrback(catch_all_err) 152 | reactor.run() 153 | -------------------------------------------------------------------------------- /test/updatetest6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2008 AG Projects 4 | # 5 | 6 | """ 7 | This test simulates a session with audio and video media flowing, after which 8 | the callee removes the video stream and only audio flows. After a while, the 9 | video stream is introduced back and both audio and video flow for a while: 10 | - caller sends INVITE, callee sends 200 ok 11 | - audio and video media flows for 15 seconds 12 | - callee proposes to keep only the audio stream using a re-INVITE, caller 13 | sends OK 14 | - audio media flows for 15 seconds 15 | - callee proposes to reintroduce a video stream using a re-INVITE, caller 16 | sends OK 17 | - audio and video media flows for 15 seconds 18 | - caller sends BYE 19 | """ 20 | 21 | from common import * 22 | 23 | 24 | def caller_update(protocol, session, caller_media, callee_media): 25 | print('doing update for caller') 26 | defer = session.do_update(protocol, 'caller', 'request', False) 27 | defer.addCallback(callee_update, protocol, session, caller_media, callee_media) 28 | return defer 29 | 30 | 31 | def callee_update(callee_addr, protocol, session, caller_media, callee_media): 32 | print('doing update for callee') 33 | defer = session.do_update(protocol, 'callee', 'reply', True) 34 | defer.addCallback(do_media, callee_addr, protocol, session, caller_media, callee_media) 35 | return defer 36 | 37 | 38 | def do_media(caller_addr, callee_addr, protocol, session, caller_media, callee_media): 39 | (caller_ip, caller_ports) = caller_addr 40 | (callee_ip, callee_ports) = callee_addr 41 | print('starting media for both parties') 42 | session.caller.start_media(caller_ip, caller_ports) 43 | session.callee.start_media(callee_ip, callee_ports) 44 | defer = DeferredList([caller_media, callee_media]) 45 | defer.addCallback(wait, protocol, session) 46 | return defer 47 | 48 | 49 | def wait(result, protocol, session): 50 | print('got media, waiting 15 seconds') 51 | defer = Deferred() 52 | defer.addCallback(stop_media, protocol, session) 53 | reactor.callLater(15, defer.callback, None) 54 | return defer 55 | 56 | 57 | def stop_media(result, protocol, session): 58 | print('stopping media') 59 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 60 | defer.addCallback(change_callee, protocol, session) 61 | return defer 62 | 63 | 64 | def change_callee(result, protocol, session): 65 | print('sending new update for callee') 66 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {}), ('video', 0, 'sendrecv', {})]) 67 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {}), ('video', 0, 'sendrecv', {})]) 68 | media_defer = DeferredList([caller_media, callee_media]) 69 | defer = session.do_update(protocol, 'callee', 'request', False) 70 | defer.addCallback(change_caller, protocol, session, media_defer) 71 | return defer 72 | 73 | 74 | def change_caller(caller_addr, protocol, session, media_defer): 75 | (caller_ip, caller_ports) = caller_addr 76 | print('sending new update for caller') 77 | defer = session.do_update(protocol, 'caller', 'reply', True) 78 | defer.addCallback(start_new_media, protocol, session, media_defer, caller_ip, caller_ports) 79 | return defer 80 | 81 | 82 | def start_new_media(callee_addr, protocol, session, media_defer, caller_ip, caller_ports): 83 | (callee_ip, callee_ports) = calee_addr 84 | print('starting new media') 85 | session.caller.start_media(caller_ip, caller_ports) 86 | session.callee.start_media(callee_ip, callee_ports) 87 | media_defer.addCallback(wait2, protocol, session) 88 | return media_defer 89 | 90 | 91 | def wait2(result, protocol, session): 92 | print('got new media, waiting 15 seconds') 93 | defer = Deferred() 94 | defer.addCallback(stop_media2, protocol, session) 95 | reactor.callLater(15, defer.callback, None) 96 | return defer 97 | 98 | 99 | def stop_media2(result, protocol, session): 100 | print('stopping media') 101 | defer = DeferredList([session.caller.stop_media(), session.callee.stop_media()]) 102 | defer.addCallback(change_callee2, protocol, session) 103 | return defer 104 | 105 | 106 | def change_callee2(result, protocol, session): 107 | print('sending new update for callee') 108 | caller_media = session.caller.set_media([('audio', 40000, 'sendrecv', {}), ('video', 40010, 'sendrecv', {})]) 109 | callee_media = session.callee.set_media([('audio', 30000, 'sendrecv', {}), ('video', 30010, 'sendrecv', {})]) 110 | media_defer = DeferredList([caller_media, callee_media]) 111 | defer = session.do_update(protocol, 'callee', 'request', False) 112 | defer.addCallback(change_caller2, protocol, session, media_defer) 113 | return defer 114 | 115 | 116 | def change_caller2(caller_addr, protocol, session, media_defer): 117 | (caller_ip, caller_ports) = caller_addr 118 | print('sending new update for caller') 119 | defer = session.do_update(protocol, 'caller', 'reply', True) 120 | defer.addCallback(start_new_media2, protocol, session, media_defer, caller_ip, caller_ports) 121 | return defer 122 | 123 | 124 | def start_new_media2(callee_addr, protocol, session, media_defer, caller_ip, caller_ports): 125 | (callee_ip, callee_ports) = callee_addr 126 | print('starting new media') 127 | session.caller.start_media(caller_ip, caller_ports) 128 | session.callee.start_media(callee_ip, callee_ports) 129 | media_defer.addCallback(wait3, protocol, session) 130 | return media_defer 131 | 132 | 133 | def wait3(result, protocol, session): 134 | print('got new media, waiting 15 seconds') 135 | defer = Deferred() 136 | defer.addCallback(kthxbye, protocol, session) 137 | reactor.callLater(15, defer.callback, None) 138 | return defer 139 | 140 | 141 | def kthxbye(result, protocol, session): 142 | print('sending remove') 143 | return session.do_remove(protocol, 'caller') 144 | 145 | 146 | def disconnect(result, connector): 147 | print('disconnecting') 148 | connector.disconnect() 149 | reactor.callLater(1, reactor.stop) 150 | 151 | 152 | def catch_all_err(failure): 153 | print(failure) 154 | 155 | 156 | if __name__ == '__main__': 157 | caller = Endpoint('Alice ', 'Caller UA', True) 158 | caller_media = caller.set_media([('audio', 40000, 'sendrecv', {}), ('video', 40010, 'sendrecv', {})]) 159 | callee = Endpoint('Bob ', 'Callee UA', False) 160 | callee_media = callee.set_media([('audio', 30000, 'sendrecv', {}), ('video', 30010, 'sendrecv', {})]) 161 | session = Session(caller, callee) 162 | connector, defer = connect_to_dispatcher() 163 | defer.addCallback(caller_update, session, caller_media, callee_media) 164 | defer.addCallback(disconnect, connector) 165 | defer.addErrback(catch_all_err) 166 | reactor.run() 167 | -------------------------------------------------------------------------------- /tls/README: -------------------------------------------------------------------------------- 1 | Certificates used by the mediaproxy components: 2 | 3 | ca.pem - Certificate authority 4 | crl.pem - Certificate revocation list 5 | dispatcher.crt - Media dispatcher certificate 6 | dispatcher.key - Media dispatcher private key 7 | relay.crt - Media relay certificate 8 | relay.key - Media relay private key 9 | 10 | 11 | IMPORTANT NOTE: 12 | 13 | The certificates that come with mediaproxy are provided as samples, which are 14 | only meant to be used for testing/evaluation purposes or to serve as examples 15 | for what the certificates need to contain. 16 | 17 | Do _NOT_ use them in a production environment, as anyone who has downloaded 18 | mediaproxy will be able to connect to your servers using them. 19 | 20 | The included certificates can either be found in the source tree in the tls 21 | subdirectory or in /usr/share/doc/mediaproxy-common/tls (on a Debian/Ubuntu 22 | system; on other Linux distributions the path might be different). 23 | 24 | To generate your own certificates, we recommend you use tinyca available at 25 | https://opsec.eu/src/tinyca/ or directly available as a Debian package. 26 | 27 | Using tinyca, you should first generate a certificate authority. Next you 28 | should go to the Preferences menu and edit the OpenSSL configuration. There 29 | in the "Server Certificate Settings" change "Netscape Certificate Type" to 30 | "SSL Server, SSL Client" and press OK. 31 | 32 | Next go to the Certificates tab and then press the New button on the toolbar. 33 | Choose "Create Key and Certificate (Server)" to generate the certificate and 34 | private key for the MediaProxy dispatcher. Repeat the same to generate the 35 | certificate and private key for the MediaProxy relay. 36 | 37 | Next export your dispatcher certificate in PEM format to dispatcher.crt (do 38 | not include the private key in it), and the dispatcher private key in PEM 39 | format to dispatcher.key (also do not include the certificate with it and 40 | select to save it without a passphrase). Repeat the same for the relay, 41 | but this time name the file relay.crt and relay.key. 42 | You also need to export the certificate authority in PEM format to ca.pem as 43 | well as the CRL list into crl.pem. 44 | 45 | Then you can use all the exported certificates and private keys by placing 46 | them in /etc/mediaproxy/tls/ (or /path-to-mediaproxy/tls for a stand alone 47 | installation). Additionally you can configure passport entries for the 48 | dispatcher and the relay in config.ini to perform extra checks on the 49 | certificates (like for example checking the subject organization or the 50 | common name) to take advantage of improved security. 51 | 52 | The CA, CRL, certificates and private keys must be named like below (names 53 | are not configurable, only the path where they reside can be configured): 54 | 55 | ca.pem, crl.pem, dispatcher.crt, dispatcher.key, relay.crt, relay.key 56 | 57 | The names are self explanatory. 58 | 59 | -------------------------------------------------------------------------------- /tls/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIHTzCCBTegAwIBAgIJAKuHxCInz9OjMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD 3 | VQQGEwJOTDEWMBQGA1UECBMNTm9vcmQtSG9vbGFuZDEQMA4GA1UEBxMHSGFhcmxl 4 | bTEUMBIGA1UEChMLQUcgUHJvamVjdHMxEzARBgNVBAsTCk1lZGlhUHJveHkxEzAR 5 | BgNVBAMTCk1lZGlhUHJveHkxJjAkBgkqhkiG9w0BCQEWF3N1cHBvcnRAYWctcHJv 6 | amVjdHMuY29tMB4XDTE4MDUyOTE4MzM0M1oXDTM4MDUyNDE4MzM0M1owgZ8xCzAJ 7 | BgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib29sYW5kMRAwDgYDVQQHEwdIYWFy 8 | bGVtMRQwEgYDVQQKEwtBRyBQcm9qZWN0czETMBEGA1UECxMKTWVkaWFQcm94eTET 9 | MBEGA1UEAxMKTWVkaWFQcm94eTEmMCQGCSqGSIb3DQEJARYXc3VwcG9ydEBhZy1w 10 | cm9qZWN0cy5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDPHyob 11 | 0nCaBT7azcpXLbfCQDnCjL/D/wdeT/g0hPvjBa6A+ISUiomR0MkmaooGMKw++cj/ 12 | e+uPGvR6vUvffOB22PHXzZzo1UZKduqVeZncv5PcnOdgh7Uq6tS2iSI8sY9cSRzp 13 | WO68gsmUzu0Hd5P+VCUsY/aOmL1WVIKg87aFQcreBflrtZJeKjxq/7I4cp0QFoYT 14 | RVo5ZAJ1rxVX6VLthlrU1uP5W/vtG9TbqmcJvi9HQJIuA4lxLE5Q3tvyWXWvBfav 15 | kwZKDZHNwaCY1M4OD1W+Ilxpu8zCxgOdMcsVmJjXvNPgUYenGRAl5MugvWaZyznA 16 | FOXUFcTrL1gpbE60HqPVD6FWBo7vuoe4IvUvt1R0zCpsI+iQAaDAF2qb2DGhmsYD 17 | e1Jfx2OobiPzpcEHY0X/eN3ADFicTu27XZqHZu489oCLtamHfeR026v1j7iT6rCI 18 | xdd/lCIJQaBL/BA1/CN0DVOMGGJ6LxsOWt6TgzLFbyUQR3Fv8cBFCpDuOErv0MW4 19 | 0O7P4AYUTPYSM4zGEqVjzJxpQrZ3NXWIl3fHrmC+LsiG2eZGt60Ln/8gisTJvrg1 20 | 56Qqyp9k/RF0JTQsv211ZUaVBUwDrVkPKlqHoorN4lqmJWU8iY2cZDUAXr/p/z/0 21 | cqRTQCtS6Wg4cJIX8fNB94pm6I/8s0zYJDL7aQIDAQABo4IBijCCAYYwHQYDVR0O 22 | BBYEFPWDRoWZp9I+e8xKqpswYAruUoF5MIHUBgNVHSMEgcwwgcmAFPWDRoWZp9I+ 23 | e8xKqpswYAruUoF5oYGlpIGiMIGfMQswCQYDVQQGEwJOTDEWMBQGA1UECBMNTm9v 24 | cmQtSG9vbGFuZDEQMA4GA1UEBxMHSGFhcmxlbTEUMBIGA1UEChMLQUcgUHJvamVj 25 | dHMxEzARBgNVBAsTCk1lZGlhUHJveHkxEzARBgNVBAMTCk1lZGlhUHJveHkxJjAk 26 | BgkqhkiG9w0BCQEWF3N1cHBvcnRAYWctcHJvamVjdHMuY29tggkAq4fEIifP06Mw 27 | DwYDVR0TAQH/BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAQYwCQYDVR0SBAIwADAr 28 | BglghkgBhvhCAQ0EHhYcVGlueUNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAiBgNV 29 | HREEGzAZgRdzdXBwb3J0QGFnLXByb2plY3RzLmNvbTAOBgNVHQ8BAf8EBAMCAQYw 30 | DQYJKoZIhvcNAQELBQADggIBAB+fQ3T7ZGsHOwdJLptBT2LfDE4Tmq/is79fQY9a 31 | fCq7NS5T1zX59MYI9nFE23b6QQmCKw2NzhMNzNN3zONVJG+og69gdSZyeFlf6DND 32 | kQFSR32n4s7yRv7yNLRj0GJRTyi2rdlR8ARUn196YRtbKCjKj27IDBuu17gTO4Tc 33 | wkQNvpo8aqECpU6GtGSV6fj9hADU9H2NUYlQA/bnPxkayyNSL+lW+R1QCcp5KJ71 34 | 5dLmz0UZzFm3aIF6a8nRFbOTiD/FiGIYDFq/qrkW28TOQVndy2IbUE3Q6NlUAmhS 35 | YUQjThR/pt1p5pyhVp7gZNlBNwUw1peN93y9O1rkbOF5RSL9tGgfedW/e88Bj/zC 36 | DQMkVUfnO7rko/IpEmqkQTe12PXs3Wm+TCdt+mniHpNJjkLJTLkMtNcMQgqOvVEy 37 | rywUKQESR5j3i2qPG1HukbOP5nFuQ8hkTFp62MzIRsPsZ+Z75VNsyXCaaYGCHKvl 38 | 9l1pJHp3o391/mnqPViAbkNDbnStqAKuxnvLxhGp2i/7qHlWm5PVzW7SaPDulAf6 39 | tRu9aw5H9vQjNvZxo2y4CxwnOhEWubIDaNwK0Qk55KvvNRBuw8KPtUYd7dyuKT09 40 | 9LCssNfEpHem99L41P5N27qJWoCVa3n3YkHTJ1YfgzGsN6ctOHW+IZlZdVWnXcq5 41 | p4RK 42 | -----END CERTIFICATE----- 43 | -------------------------------------------------------------------------------- /tls/crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIIC5jCBzzANBgkqhkiG9w0BAQsFADCBnzELMAkGA1UEBhMCTkwxFjAUBgNVBAgT 3 | DU5vb3JkLUhvb2xhbmQxEDAOBgNVBAcTB0hhYXJsZW0xFDASBgNVBAoTC0FHIFBy 4 | b2plY3RzMRMwEQYDVQQLEwpNZWRpYVByb3h5MRMwEQYDVQQDEwpNZWRpYVByb3h5 5 | MSYwJAYJKoZIhvcNAQkBFhdzdXBwb3J0QGFnLXByb2plY3RzLmNvbRcNMTgwNTI5 6 | MTg0MDExWhcNMjgwNTI2MTg0MDExWjANBgkqhkiG9w0BAQsFAAOCAgEAZyN2nyYc 7 | sjUr2ucQ2j6LkDYO1DolX1T9KkkvBRKtXYc4STvAF9mEUws8OrWp5SBJMZB0JokT 8 | qR859Gkp2fMIRoXwWFlTcJh7k2RKrl2OF4wFmBe11C5y6AQ4L5eS9orvYHilXm9e 9 | mpTLYhfxO5p8zHp71Jbh6JYyPPrR9yPlTSxmxAFLnKiV5Qj1PVI5rqHUR4QiFarW 10 | AtmMVSnq7Jv3WPDjbNazZ9gaiLQ55ANDBxqmO1PqeUjuzwISRNZc8DGQcLe41PhD 11 | RlCK8RN5Gtk8Q4NiD/3QFuMWVpE2AHkm5tNTWLyFKYVeS1lGMvzdpB2DZowngXMb 12 | 0hRYTZsIziRT+Nc/vKmLkb4OO481cEQCKDGuOm+JQnJZPy8Jq6jAK0+OmUjEIx7O 13 | A+SYQTnaOauVQ6lpPjI7A0c/G+PipphQd79xxI04GYfAxSlY7XqbWLN19r8HyD3y 14 | FxJwewxvzpYVw3xidhO6GoU38fonwqz+8L8r2IjGobWjqWLvZxdeiHe/Zvi6Esfy 15 | 1380RL8iKuBYIW4q23+MlEu2MPNz1R4ZoS6uZ9KUBiKZKADJPTRQ8izvFkQNYWJW 16 | DsuOnms3IdxVHVOIOJY5z6Szv6pz1YHlQHj/gwMWolDmeXAsKjYoYBvQO+xLLXJr 17 | 7SovDkKiRYyGLpfkFQY7Ugmxp2dZOnNIpM8= 18 | -----END X509 CRL----- 19 | -------------------------------------------------------------------------------- /tls/dispatcher.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 41 | yorXfM1geS0I 42 | -----END CERTIFICATE----- 43 | -------------------------------------------------------------------------------- /tls/dispatcher.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /tls/relay.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 41 | s8Ni3g== 42 | -----END CERTIFICATE----- 43 | -------------------------------------------------------------------------------- /tls/relay.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /web/README: -------------------------------------------------------------------------------- 1 | This is a simple web page that uses the MediaProxy dispatcher management 2 | interface to gather information about media relays and media sessions. 3 | It displayes both summaries per relay, as well as complete information about 4 | all the active media sessions. 5 | -------------------------------------------------------------------------------- /web/config/media_sessions.conf.sample: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /web/images/30/Arris_TM722b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/Arris_TM722b.png -------------------------------------------------------------------------------- /web/images/30/G580.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/G580.png -------------------------------------------------------------------------------- /web/images/30/Nokia810.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/Nokia810.png -------------------------------------------------------------------------------- /web/images/30/Telephone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/Telephone.png -------------------------------------------------------------------------------- /web/images/30/aastra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/aastra.png -------------------------------------------------------------------------------- /web/images/30/asterisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/asterisk.png -------------------------------------------------------------------------------- /web/images/30/audiocodes-mp124.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/audiocodes-mp124.png -------------------------------------------------------------------------------- /web/images/30/avm-fritzbox-wlan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/avm-fritzbox-wlan.png -------------------------------------------------------------------------------- /web/images/30/avm-fritzbox-wlan2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/avm-fritzbox-wlan2.png -------------------------------------------------------------------------------- /web/images/30/blink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/blink.png -------------------------------------------------------------------------------- /web/images/30/budgetone100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/budgetone100.png -------------------------------------------------------------------------------- /web/images/30/cirpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/cirpack.png -------------------------------------------------------------------------------- /web/images/30/cisco-5380.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/cisco-5380.png -------------------------------------------------------------------------------- /web/images/30/cisco-7960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/cisco-7960.png -------------------------------------------------------------------------------- /web/images/30/cisco-ata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/cisco-ata.png -------------------------------------------------------------------------------- /web/images/30/cisco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/cisco.png -------------------------------------------------------------------------------- /web/images/30/copperjet16162p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/copperjet16162p.png -------------------------------------------------------------------------------- /web/images/30/draytek-vigor2600v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/draytek-vigor2600v.png -------------------------------------------------------------------------------- /web/images/30/draytek-vigor2600vg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/draytek-vigor2600vg.png -------------------------------------------------------------------------------- /web/images/30/draytek-vigor2800g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/draytek-vigor2800g.png -------------------------------------------------------------------------------- /web/images/30/draytek-vigor2900g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/draytek-vigor2900g.png -------------------------------------------------------------------------------- /web/images/30/droid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/droid.png -------------------------------------------------------------------------------- /web/images/30/eStara.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/eStara.png -------------------------------------------------------------------------------- /web/images/30/ekiga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/ekiga.png -------------------------------------------------------------------------------- /web/images/30/eyebeam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/eyebeam.png -------------------------------------------------------------------------------- /web/images/30/fring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/fring.png -------------------------------------------------------------------------------- /web/images/30/genexis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/genexis.png -------------------------------------------------------------------------------- /web/images/30/handytone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/handytone.png -------------------------------------------------------------------------------- /web/images/30/hitachi-wip5000-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/hitachi-wip5000-2.png -------------------------------------------------------------------------------- /web/images/30/hitachi-wip5000-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/hitachi-wip5000-3.png -------------------------------------------------------------------------------- /web/images/30/hitachi-wip5000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/hitachi-wip5000.png -------------------------------------------------------------------------------- /web/images/30/innomedia-mta5000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/innomedia-mta5000.png -------------------------------------------------------------------------------- /web/images/30/ipDialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/ipDialog.png -------------------------------------------------------------------------------- /web/images/30/ipkall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/ipkall.png -------------------------------------------------------------------------------- /web/images/30/linksys-pap2-vert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/linksys-pap2-vert.png -------------------------------------------------------------------------------- /web/images/30/linksys-pap2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/linksys-pap2.png -------------------------------------------------------------------------------- /web/images/30/messenger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/messenger.png -------------------------------------------------------------------------------- /web/images/30/nimbuzz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/nimbuzz.png -------------------------------------------------------------------------------- /web/images/30/nokia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/nokia.png -------------------------------------------------------------------------------- /web/images/30/session.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/session.png -------------------------------------------------------------------------------- /web/images/30/siemens-3610.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/siemens-3610.png -------------------------------------------------------------------------------- /web/images/30/sip-communicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/sip-communicator.png -------------------------------------------------------------------------------- /web/images/30/sipps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/sipps.png -------------------------------------------------------------------------------- /web/images/30/sjphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/sjphone.png -------------------------------------------------------------------------------- /web/images/30/snom100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/snom100.png -------------------------------------------------------------------------------- /web/images/30/snom200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/snom200.png -------------------------------------------------------------------------------- /web/images/30/snom320-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/snom320-front.png -------------------------------------------------------------------------------- /web/images/30/snom320-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/snom320-left.png -------------------------------------------------------------------------------- /web/images/30/snom320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/snom320.png -------------------------------------------------------------------------------- /web/images/30/snom360-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/snom360-front.png -------------------------------------------------------------------------------- /web/images/30/snom360-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/snom360-left.png -------------------------------------------------------------------------------- /web/images/30/snom360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/snom360.png -------------------------------------------------------------------------------- /web/images/30/spa2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/spa2000.png -------------------------------------------------------------------------------- /web/images/30/teles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/teles.png -------------------------------------------------------------------------------- /web/images/30/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/unknown.png -------------------------------------------------------------------------------- /web/images/30/unknown3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/unknown3.png -------------------------------------------------------------------------------- /web/images/30/vizufon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/vizufon.png -------------------------------------------------------------------------------- /web/images/30/vizufon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/vizufon2.png -------------------------------------------------------------------------------- /web/images/30/webstarepx2203.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/webstarepx2203.png -------------------------------------------------------------------------------- /web/images/30/xten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/xten.png -------------------------------------------------------------------------------- /web/images/30/zoep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/zoep.png -------------------------------------------------------------------------------- /web/images/30/zyxel-p2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/30/zyxel-p2000.png -------------------------------------------------------------------------------- /web/images/PoweredbyAGProjects.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGProjects/mediaproxy/b76c069477ac9e7514fd50122fe1c2e1be1fbdc7/web/images/PoweredbyAGProjects.gif -------------------------------------------------------------------------------- /web/library/phone_images.php: -------------------------------------------------------------------------------- 1 | "asterisk.png", 5 | "AVM FRITZ" => "avm-fritzbox-wlan.png", 6 | "Cisco ATA" => "cisco-ata.png", 7 | "Cisco.*Gateway" => "cisco.png", 8 | "CSCO" => "cisco-7960.png", 9 | "DrayTek" => "draytek-vigor2600vg.png", 10 | "eStara" => "eStara.png", 11 | "Eyebeam" => "eyebeam.png", 12 | "Grandstream" => "handytone.png", 13 | "ipDialog" => "ipDialog.png", 14 | "Linksys" => "linksys-pap2.png", 15 | "Session.*Wave" => "session.png", 16 | "SIPPS" => "sipps.png", 17 | "Sipura" => "spa2000.png", 18 | "sjphone" => "sjphone.png", 19 | "Snom100" => "snom100.png", 20 | "Snom(190|200)" => "snom200.png", 21 | "Snom320" => "snom320.png", 22 | "Snom360" => "snom360.png", 23 | "snomSoft" => "snom360.png", 24 | "Talkin" => "xten.png", 25 | "unidata" => "hitachi-wip5000.png", 26 | "Vizufon" => "vizufon.png", 27 | "voipster" => "zoep.png", 28 | "Windows RTC" => "messenger.png", 29 | "X-Lite" => "xten.png", 30 | "X-PRO|bria" => "xten.png", 31 | "ZyXEL P2000W" => "zyxel-p2000.png", 32 | "-IMVP" => "innomedia-mta5000.png", 33 | "3610\/" => "siemens-3610.png", 34 | "cirpack" => "cirpack.png", 35 | "Brcm-Callctrl|EPX2203|EPC2203" => "webstarepx2203.png", 36 | "Audiocodes-Sip-Gateway-MP" => "audiocodes-mp124.png", 37 | "Nokia|E71" => "nokia.png", 38 | "CopperJet" => "copperjet16162p.png", 39 | "Ekiga" => "ekiga.png", 40 | "sofia-sip" => "Nokia810.png", 41 | "sipsimple" => "blink.png", 42 | "blink" => "blink.png", 43 | "fring" => "fring.png", 44 | "ARRIS-TM722" => "Arris_TM722b.png", 45 | "Genexis OCG118" => "genexis.png", 46 | "droid" => "droid.png", 47 | "SIP Communicator"=> "sip-communicator.png", 48 | "telephone" => "Telephone.png", 49 | "nimbuzz" => "nimbuzz.png", 50 | "ipkall" => "ipkall.png", 51 | "a580" => "G580.png", 52 | "teles" => "teles.png" 53 | ); 54 | 55 | ?> 56 | -------------------------------------------------------------------------------- /web/media_sessions.phtml: -------------------------------------------------------------------------------- 1 | $_REQUEST['user']); 32 | 33 | $MediaSessions = new MediaSessions($dispatcher,$allowedDomains,$filters); 34 | $MediaSessions->show(); 35 | 36 | ?> 37 | --------------------------------------------------------------------------------